# Ze: every published page > The full text of the Ze website, as one file. Each page is the Markdown mirror the site publishes beside its HTML, preceded by the page title and the canonical URL a person opens. llms.txt beside this file is the same curation as links alone. The order is a reading order, not the site's route order: what Ze is and why it is worth evaluating comes first, how to use it comes second. Talk decks are left out, because a deck is its own document and publishes no Markdown mirror. Every section opens with `## Section: ` and every page with `### Page: ` followed by its URL on the next line. The headings inside a page body carry neither prefix. ## Section: Overview --- ### Page: Ze, an OpenNOS https://ze-software.net/ # Ze, an OpenNOS Plugin extensible Appliance option ExaBGP compatible Ze speaks [**BGP**](https://ze-software.net/guides/bgp-peering/), [**ISIS**](https://ze-software.net/guides/isis/), [**OSPF**](https://ze-software.net/guides/ospf/), manages [**interfaces**](https://ze-software.net/features/interfaces/) and tunnels ([**IPsec VPN**](https://ze-software.net/guides/ipsec/), [**WireGuard**](https://ze-software.net/features/interfaces/#wireguard-configuration)), programs the [**FIB**](https://ze-software.net/reference/plugins/fib-kernel/), and exposes one [**YANG**](https://ze-software.net/features/bgp-configuration/)-modeled configuration through builtin SSH, a [**web**](https://ze-software.net/features/web-interface/) interface, [**APIs**](https://ze-software.net/features/api-commands/) ([**REST**](https://ze-software.net/guides/api/#rest-endpoints), [**gRPC**](https://ze-software.net/guides/api/#grpc-services), [**gNMI**](https://ze-software.net/guides/gnmi/)), [**CLI**](https://ze-software.net/features/cli-commands/), and [**MCP**](https://ze-software.net/features/mcp-integration/). It is built on a configuration and protocol engine that runs as a [**daemon**](https://ze-software.net/architecture/) or [**immutable appliance**](https://ze-software.net/guides/appliance/). **Live BGP dashboard** Replayable Ze terminal lab [Read transcript](https://ze-software.net/demos/terminal/#live-bgp-dashboard) [**Why Ze exists** Learn how Ze came to be.](https://ze-software.net/project/why-ze/) [**Watch a demo** Discover the web interface.](https://ze-software.net/demos/terminal/#web-configuration-commit) [**Search the site**Docs and commands.](https://ze-software.net/search/) [**Join Discord**Ask questions, get help.](https://discord.gg/T8s7CjPDne) [![Zeledon, the Ze bird mascot](https://ze-software.net/assets/zeledon.svg)](https://ze-software.net/zeledon/) Expected initial release: Q4 2026. No stable release yet. [Protocol-agnostic core](https://ze-software.net/architecture/) [YANG per subsystem](https://ze-software.net/reference/configuration/) [BGP, interfaces, FIB](https://ze-software.net/features/#routing) [CLI, SSH, web, API, MCP](https://ze-software.net/features/ai-first/) [Compiled or external plugins](https://ze-software.net/reference/plugins/) [AGPLv3 source](https://ze-software.net/license/) ## Latest news [All updates](https://ze-software.net/project/changes/) Engineering note ### [Reference stays attached to code](https://ze-software.net/blog/reference-from-the-system/) Ze's command and configuration declarations also feed its reference pages. That leaves the writing for the… Recently shipped ### [Week of 2026-08-31](https://ze-software.net/project/changes/2026-08-31/) Reading the standards documents end to end is finding real defects faster than it is finding paperwork, and… RFC compliance progress ### [Every MUST-level requirement, and what proves it](https://ze-software.net/quality/rfc-compliance/) One page per RFC, naming each requirement, the test evidence behind it, and the ones that have none. ## Release claims stay checkable. Every homepage number links to the page where you can inspect the test layer, transcript, peer list, RFC gate, or generated source evidence behind it. [Read the evidence map](https://ze-software.net/quality/) [Watch product demos](https://ze-software.net/demos/terminal/) [**29,800+ unit tests** - Wire encoding, parsing - Config, FSM, plugins - gomu mutates code to check assertions Local test, fuzz, and mutation evidence.](https://ze-software.net/quality/unit-fuzz-mutation/) [**1,834 of 3,047 RFC MUSTs** - 60.2% tested - 146 RFCs Ze claims support for - 181 RFCs with requirements extracted RFC requirement ledger.](https://ze-software.net/quality/rfc-compliance/) [**2,000+ end to end tests** - Peering, sessions, updates - Editor, commits, reloads - Commands checked as operators run them Functional transcript format and rerun path.](https://ze-software.net/quality/functional-ci/) [**83 fuzz targets** - Parsers, external inputs - Wire formats, config files - Saved crashes become regression cases Fuzz crashes kept as regression cases.](https://ze-software.net/quality/unit-fuzz-mutation/#fuzz-targets-are-still-tests) [**9 interop targets** - BGP, OSPF, IS-IS, BFD, BMP, RPKI, VRRP - IPsec, L2TP, PPPoE, RADIUS - Real daemons in Docker, checked by peer CLIs Docker interop peer list.](https://ze-software.net/quality/qemu-interop-release/#docker-interop) Tested against **BGP**[FRR](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[BIRD](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[GoBGP](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[OpenBGPd](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[FreeRtr](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[RustyBGP](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[rustbgpd](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[ExaBGP](https://ze-software.net/features/exabgp-compatibility/) **BGP support**[StayRTR](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[pmacct](https://ze-software.net/quality/qemu-interop-release/#docker-interop) **VPN**[strongSwan](https://ze-software.net/quality/qemu-interop-release/#docker-interop) **Access**[accel-ppp](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[xl2tpd](https://ze-software.net/quality/qemu-interop-release/#docker-interop)[FreeRADIUS](https://ze-software.net/quality/qemu-interop-release/#docker-interop) **Redundancy**[keepalived](https://ze-software.net/quality/qemu-interop-release/#docker-interop) ## [Why Ze?](https://ze-software.net/project/why-ze/) Ze is a network operating system, and more: a routing daemon, appliance runtime, lab router, and protocol engine that brings policy data, generated references, APIs, MCP tools, and product evidence into one operator workflow. **Decision** ### [Reasons to consider Ze](https://ze-software.net/project/why-ze/) Start here for the selling points: powerful CLI, operator protocols, in-engine IRR filtering, PeeringDB data, looking glass APIs, one YANG model, and when to choose another NOS instead. **Runtime** ### [Small core, registered subsystems](https://ze-software.net/architecture/) The core holds the supervisor, message bus, config provider, and plugin manager. BGP and interface management register into it. **Routing** ### [Network OS built on the engine](https://ze-software.net/architecture/) The shipped daemon speaks BGP, manages Linux interfaces, programs the FIB, and serves its configuration through SSH and the web UI. **Honest** ### [Plugins keep their own contract](https://ze-software.net/reference/plugins/) Plugins can be compiled Go modules or external processes. Compiled modules load their YANG into the daemon validator; external plugins can expose their model through `ze schema`. ## Run it as a lab, daemon, or appliance. The same binary and configuration support each path, from a [netlab topology](https://ze-software.net/labs/netlab/) or BGP interop lab to spare hardware. **Lab** ### [Run reproducible labs](https://ze-software.net/labs/bgp-interop/) The netlab integration brings up a three-node Ze topology under containerlab. The BGP interop lab also runs Ze beside FRR, BIRD, and GoBGP and checks routes through each peer's own CLI. `netlab` `containerlab` `Docker` `FRR` `BIRD` `GoBGP` Run BGP lab BGP interop **Daemon** ### [Run as a daemon](https://ze-software.net/guides/quickstart/) Ze runs on any existing Linux distro, managed by systemd or your chosen process manager. This is the easiest route when Ze has to fit into infrastructure you already run. `Existing Linux` `systemd-ready` Quickstart two BGP peers **Appliance** ### [Run as an appliance](https://ze-software.net/guides/ze-install/) A bootable gokrazy image for appliance hardware: read-only root filesystem, no shell, no package manager, and automatic process supervision. `gokrazy image` `Read-only root filesystem` Install guide PXE, ISO, or Ventoy ## Generated references are part of the product. Read the generated references before you run Ze. Ze is an open-source configuration and protocol engine. The network operating system built on it speaks BGP, manages Linux interfaces, programs the FIB, and serves the same YANG-modeled configuration through SSH, web, API, and MCP. [Operate (6)](https://ze-software.net/features/#operate) [Routing (9)](https://ze-software.net/features/#routing) [Services (9)](https://ze-software.net/features/#services) [Automate (7)](https://ze-software.net/features/#automate) [Observe (9)](https://ze-software.net/features/#observe) [Secure (6)](https://ze-software.net/features/#secure) [Platform (6)](https://ze-software.net/features/#platform) ## First paths for routing feedback. The BGP lab, ExaBGP migration, and appliance install are good starting points. ``` # build from source $ git clone https://github.com/ze-software/ze.git $ cd ze && make build # set up credentials and configure $ bin/ze init $ bin/ze config import router.conf # start $ bin/ze start # from another terminal $ bin/ze cli -c "show bgp peer list" $ bin/ze cli -c "monitor event" ``` `Good starting points` A lab peer, a migrated ExaBGP config, or a looking-glass instance can produce useful reports from people who know routing operations. - [BGP interop lab FRR, BIRD, and GoBGP in Docker](https://ze-software.net/labs/bgp-interop/) - [ExaBGP migration try an existing config and process script](https://ze-software.net/use-cases/exabgp-migration/) - [Appliance install ISO media, PXE provisioning, spare hardware](https://ze-software.net/guides/appliance/) - [Looking glass publish read-only BGP visibility](https://ze-software.net/guides/public-looking-glass/) - [AI-assisted operations MCP exposes Ze commands to tools](https://ze-software.net/features/ai-first/) ## Safe ways to try Ze before the first release. Ze is early enough that routing feedback can still change the system. These cards give each reader a low-risk starting point. **Lab** ### [Lab router](https://ze-software.net/guides/quickstart/) Network builders can bring up two BGP peers, inspect routes, and check whether Ze's operator tools fit their workflow. `Quickstart` `BGP peers` Quickstart two peer lab **Migration** ### [ExaBGP replacement](https://ze-software.net/use-cases/exabgp-migration/) ExaBGP users can try the migrator against an existing config and see which process scripts still translate cleanly. `Migration` `Plugins` Migration path config and process scripts **Appliance** ### [White-box appliance](https://ze-software.net/guides/appliance/) People with spare x86 hardware can boot the appliance image and test the same configuration model without a general-purpose shell. `ISO` `PXE` `gokrazy` Appliance guide spare hardware **Observe** ### [Looking glass](https://ze-software.net/guides/public-looking-glass/) Operators who need read-only BGP visibility can publish a looking glass and inspect routes without giving shell access. `Read-only` `BGP visibility` Looking glass read-only BGP **Interop** ### [Protocol testbed](https://ze-software.net/labs/bgp-interop/) Protocol implementers can run Docker interop scenarios against FRR, BIRD, and GoBGP, then turn a failure into a test case. `Interop` `Docker` Interop lab real peer daemons **MCP** ### [AI-assisted operations](https://ze-software.net/features/ai-first/) MCP exposes Ze commands and structured output to AI tools without a separate command set. `MCP` `CLI catalogue` AI via MCP same command catalogue ## Recent engineering notes. Weekly updates come from git history and Discord's `ze-news`. They stay specific and technical. **Update** 01 Week of 2026-08-31 ### [Reading the standards documents end to end is finding real defects faster than it is finding paperwork, and most of the week went on fixing what it found. Three of them mattered: an authentication bypass on IKE logins, a redistribute block that discarded every route from every peer, and subscriber IPv6 that never worked at all.](https://ze-software.net/project/changes/2026-08-31/) BGP ExaBGP Migration Security RADIUS **Update** 02 Week of 2026-08-24 ### [Standards closure was the plan for the week. The build and test tooling took it instead: the Makefile and 256 shell and Python scripts are gone, replaced by Go, and that move is still in progress. Around it, output formatting moved off flags and onto the pipe operators, and a TACACS+ authentication bypass was closed.](https://ze-software.net/project/changes/2026-08-24/) CLI BGP PPPoE IPsec **Update** 03 Week of 2026-08-17 ### [The CLI gained a clearer BGP workflow, traffic tools gained history and source-AS context, and IPsec changes now reach running tunnels.](https://ze-software.net/project/changes/2026-08-17/) BGP CLI API Flow Export - [See all updates](https://ze-software.net/project/changes/) `Try safely` ## Try Ze before the first release. Start where a mistake cannot affect a live network. [Run a BGP lab](https://ze-software.net/labs/bgp-interop/) [Read the quickstart](https://ze-software.net/guides/quickstart/) - **Release:** Expected Q4 2026. No stable release has shipped yet, and configuration may change. - **BGP lab:** Exercise Ze against FRR, BIRD, and GoBGP without touching a production router. - **Migration:** Try the ExaBGP migration path against an existing config before changing automation. - **Appliance:** Boot Ze on spare hardware with the same binary and configuration model as daemon mode. - **Source:** Read the code, generated docs, RFC gate, and test evidence before deciding where Ze belongs. ## Section: Start --- ### Page: Quick Start https://ze-software.net/guides/quickstart/ # Quick Start Get Ze running with two BGP peers in under 5 minutes. ## Build ```bash git clone https://github.com/ze-software/ze.git cd ze CGO_ENABLED=0 go build -tags 'ze_core ze_distro ze_anomaly ze_as112 ze_bfd ze_bgp ze_bmp ze_copp ze_cos ze_ddos ze_dhcpserver ze_exabgp ze_flowexport ze_geodns ze_gnmi ze_grpc ze_ike ze_isis ze_l2tp ze_ldp ze_lg ze_mcp ze_mpls ze_mrt ze_ntp ze_ospf ze_policyroute ze_pxe ze_radius ze_rest ze_rsvpte ze_ssh ze_tacacs ze_telemetry ze_trafficusage ze_vpp ze_vrrp ze_web' -o bin/ze ./cmd/ze ``` Requires **Go 1.27+** on a macOS or Linux development host. Windows is not a supported development platform. ### Or: go install To get a `ze` binary without cloning the repository, use `go install` with the default feature tags derived from `feature-gates.txt`: <!-- source: internal/le/featuretags/daemontags.go -- DaemonTags --> ```bash CGO_ENABLED=0 go install -tags 'ze_core ze_distro ze_anomaly ze_as112 ze_bfd ze_bgp ze_bmp ze_copp ze_cos ze_ddos ze_dhcpserver ze_exabgp ze_flowexport ze_geodns ze_gnmi ze_grpc ze_ike ze_isis ze_l2tp ze_ldp ze_lg ze_mcp ze_mpls ze_mrt ze_ntp ze_ospf ze_policyroute ze_pxe ze_radius ze_rest ze_rsvpte ze_ssh ze_tacacs ze_telemetry ze_trafficusage ze_vpp ze_vrrp ze_web' github.com/ze-software/ze/cmd/ze@latest ``` This tracks the module's default branch (development version), not a tagged release -- there are no tagged releases yet. ## Initialize Ze runs an SSH server on localhost for CLI access (`ze cli`, `ze show`, `ze signal`). This keeps the control plane authenticated even in multi-user environments. Set up credentials once: ```bash ./ze init ``` This prompts for username, password, SSH host (default `127.0.0.1`), port (default `2222`), and node name (default: hostname). Credentials are stored locally with bcrypt-hashed passwords. For scripting (later fields fall back to their defaults): <!-- source: internal/plugins/init/main.go -- Run, defaultHost, defaultPort, bcrypt hashing --> ```bash echo -e "admin\nsecret" | bin/ze init ``` Running `ze init` a second time will refuse with `error: database already exists`. To reinitialize, use `--force` -- this backs up the old database as `database.zefs.replaced-<date>` before creating a new one: ```bash ./ze signal stop # stop daemon first ./ze init --force # prompts for confirmation, then backs up and reinitializes ``` <!-- source: internal/plugins/init/main.go -- forceFlag --> ### Demo: Create ZeFS and commit over SSH Create the ZeFS database, edit the active configuration through Ze's SSH management plane, and verify the committed setting. [Download the asciicast recording](../../assets/demos/zefs-config.cast?v=2c132bab3e) · [Plain-text transcript](../../assets/demos/zefs-config.txt?v=e55d622677) Recorded with Ze 26.08.31 on macOS and Linux using Ze recorder. Duration: 2 minutes 58 seconds. ```console $ cat $ZE_INIT_INPUT admin secret123 127.0.0.1 2222 ze-demo $ ze init < $ZE_INIT_INPUT $ ze config list ze.conf $ ze data check $ ssh ze-demo show bgp $ ssh ze-demo ze# set environment cli format default table ze# show | compare ze# commit Session committed ze# exit ze> exit $ ze cli -c 'show bgp' $ ze cli -c 'show bgp | text' $ ze cli -c 'show bgp | display router-id local-as peers-established' $ ze cli -c 'show bgp | display router-id | fill alpha' $ ze cli -c 'show bgp | peers' $ ze cli -c 'show bgp | raw' | head -14 $ ze cli -c 'show bgp | raw' | ze pipe text The five lines answer `ze init`'s prompts in order: username, password, host, port, and name. It reads them from a file here so the recording is reproducible, and it prints nothing when its input is not a terminal, so the file is shown first rather than left as an unexplained redirection. `ze init` creates `database.zefs`. The first BGP summary uses the default text format. The SSH editor commits the format setting back to ZeFS, not to a second flat file, and the same operational command immediately uses the committed default. The last commands show the two ways to override that default, and they are different pipes. `show bgp | text` is Ze's own operator, inside the quoted command, and it wins over the committed setting. Then `| raw` on its own shows what every one of these renderings is made from: the payload as the daemon holds it, unrendered. The last command sends that same payload across a real shell pipe, and `ze pipe text` formats it on this side, which is how output captured earlier is formatted later. The command is `ze pipe` rather than `ze format` because the operator language also carries `match`, `count`, `first`, `last` and `resolve`, so `format` would name one clause of it. Every command answers with structured data, so `text`, `table`, `json`, `yaml` and `ndjson` all render the same payload. Three commands in the middle choose WHICH of that payload to read. `display` names the fields wanted, in the order wanted, and shows those alone. `fill` brings back the fields it did not name, and `alpha` orders them by field name. `peers` is an alias, which is a name for a pipe expression, so one word answers the per-peer rows without the totals beside them. Each command declares its own column order, so a table leads with the fields an operator reads first rather than with the alphabet. ``` ## Minimal Config Save as `example.conf`: ``` static { table default { route 172.16.0.0/24 { next { hop 10.0.0.1 { } } } } } redistribute { destination bgp { import static } } bgp { router-id 10.0.0.1 session { asn { local 65000 } } peer test-peer { connection { remote { ip 10.0.0.2 } local { ip 10.0.0.1 } } session { asn { local 65000 remote 65001 } family { ipv4/unicast { prefix { maximum 1000000 } } } } update { attribute { origin igp next-hop 10.0.0.1 } nlri { ipv4/unicast add 192.168.1.0/24 } } } } ``` This advertises two prefixes to `test-peer`, one by each of the routes Ze gives you to get a prefix into BGP: - **Redistribution** (`172.16.0.0/24`): a `static` route pulled into BGP by the top-level `redistribute { destination bgp { import static } }` block. Every source protocol (`static`, `connected`, `kernel`, `ospf`, `isis`, ...) redistributes into any destination the same way -- this is how interface and interior-gateway routes reach your peers. <!-- source: internal/plugins/static/register.go -- registerStaticSources; internal/component/config/loader_redistribute.go -- ExtractRedistributeRules --> - **Direct announcement** (`192.168.1.0/24`): a prefix declared inline on the peer in its `update {}` block, sent as soon as the session establishes. <!-- source: internal/component/bgp/reactor/peer_initial_sync.go -- sendInitialRoutes --> > Advanced: a `process` binding attaches a plugin or your own external program to a peer (the > ExaBGP-style event API), including making a peer RIB-backed so it stores and re-advertises > received routes. It is not needed for the config above; see [Plugins](../plugins/index.md). ## Validate ```bash ./ze config validate example.conf ``` <!-- source: internal/component/config/cli/cmd_validate.go -- cmdValidate --> Expected output: ``` configuration valid: example.conf ``` ## Start ```bash ./ze start example.conf ``` <!-- source: cmd/ze/ze_core_dispatch.go -- registerLocalCommands, "start" root handler; cmd/ze/ze_core_start.go -- cmdStart, startConfigPath --> The config path goes behind the `start` keyword. A bare `./ze example.conf` is rejected with `unknown command: example.conf` (exit 1); global flags such as `-d` are consumed before the keyword, so they stay ahead of it. Ze logs to stderr. You should see something like: ``` level=INFO msg="hub ready" subsystem=hub plugins=1 peers=1 listen=":179" level=INFO msg="peer connecting" subsystem=bgp.reactor peer=test-peer address=10.0.0.2 ``` Silence means the default log level (`warn`) has nothing to report -- that's normal. To see all activity: ```bash ./ze -d start example.conf # debug logging ``` <!-- source: cmd/ze/main.go -- "-d" debug flag sets ze.log=debug --> ## Verify In another terminal: ```bash # Check daemon is running ./ze status # List peers ./ze cli -c "show bgp peer list" # Show peer details ./ze cli -c "show bgp peer test-peer detail" # Watch live events (streams until Ctrl-C) ./ze cli -c "monitor event" ``` <!-- source: internal/component/cli/client/main.go -- Execute, StreamMonitor --> ## Test Without a Real Peer Use the built-in test peer to accept any BGP session: ```bash # Terminal 1: start a sink peer (accepts sessions, replies keepalive) bin/ze-test peer --mode sink --port 1179 --asn 65001 # Terminal 2: start ze with config pointing to localhost:1179 ./ze start example-local.conf ``` <!-- source: internal/test/cli/cmd_peer.go -- ze-test peer command --> Where `example-local.conf` is the config above with the peer's `connection` block pointed at the local sink, so ze dials `127.0.0.1:1179` instead of `10.0.0.2`: ``` connection { remote { ip 127.0.0.1 port 1179 } local { ip 127.0.0.2 } } ``` The sink's `--asn 65001` matches the peer's `session { asn { remote 65001 } }`. **The two ends carry different addresses on purpose, even here.** One machine can hold both, so a loopback demo is the one place a session can be written with one address at each end, and BGP does not work that way: `next-hop self` then puts the peer's OWN address on the wire, and Ze refuses to advertise that, because RFC 4271 Section 5.1.3 forbids telling a peer to reach a destination through itself. The session still establishes, so the symptom is routes that never arrive rather than an error. On Linux the whole 127.0.0.0/8 range is available; on macOS add the alias once with `sudo ifconfig lo0 alias 127.0.0.2`, which `./le setup install` also does. The refusal is `originatedNextHopIsPeerOwn` (`internal/component/bgp/reactor/forward_next_hop.go`), and `precomputeNextHop` (`internal/component/bgp/reactor/peer_forward_facts.go`) is what resolves `next-hop self` to the local address. The sink accepts whatever it is sent, so this demo works either way until you announce a route; the address split is what keeps it working when you do. ## Stop ```bash ./ze signal stop # graceful shutdown ./ze signal restart # graceful restart (preserves routes via GR) ``` <!-- source: internal/plugins/signal/main.go -- Run --> ## Next Steps - [Configuration](../configuration-model/index.md) -- peer groups, capabilities, static routes - [Plugins](../plugins/index.md) -- RIB, route server, RPKI, graceful restart - [CLI Reference](../cli/index.md) -- interactive CLI, route injection, monitoring - [Logging](../logging/index.md) -- log levels, backends, per-subsystem tuning - [Operations](../operations/index.md) -- SSH setup, signals, health checks, troubleshooting --- ### Page: Installation https://ze-software.net/guides/ze-install/ # Installation Ze provides commands for local installation, remote PXE provisioning, and appliance ISO installer media. ## Local Installation `ze install local` copies the ze binary to a standard system location and creates the config directory. ### Quick Start ```bash sudo ze install local ``` This presents an interactive menu to select the installation prefix: ``` Select installation prefix: 1) /usr/local (recommended) 2) /usr (system) 3) /opt/ze (self-contained) Choice [1]: ``` Use `--prefix` for non-interactive use: ```bash sudo ze install local --prefix /usr/local ``` ### Flags | Flag | Default | Description | |------|---------|-------------| | `--prefix` | interactive | Installation prefix (binary goes to `<prefix>/bin/ze`) | | `--dry-run` | | Print what would be done without making changes | ### What It Does 1. Copies the running ze binary to `<prefix>/bin/ze` 2. If no `database.zefs` exists at the config path: creates the config directory The config directory is resolved from the binary path following GNU prefix conventions: | Binary location | Config directory | |-----------------|-----------------| | `/usr/local/bin/ze` | `/etc/ze` | | `/usr/bin/ze` | `/etc/ze` | | `/opt/ze/bin/ze` | `/opt/ze/etc/ze` | After installation, run `ze init` to bootstrap the database. ### Systemd Service After installing the binary and bootstrapping the database, use `ze install systemd` to set up the systemd service: ```bash sudo ze install local --prefix /usr/local sudo ze init sudo ze install systemd --start ``` The generated service-management unit file: ```ini [Unit] Description=Ze Network OS After=network-online.target Wants=network-online.target [Service] Type=simple User=ze Group=ze ExecStart=<prefix>/bin/ze start ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=5 LimitNOFILE=65536 LimitCORE=infinity LimitMEMLOCK=infinity WorkingDirectory=<config-dir> Environment=ZE_CONFIG_DIR=<config-dir> Environment=XDG_RUNTIME_DIR=/run/ze AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE NoNewPrivileges=true ProtectSystem=true ProtectHome=true RuntimeDirectory=ze [Install] WantedBy=multi-user.target ``` `ze install systemd` refuses to run unless `<config-dir>/database.zefs` exists. It creates the `ze` user and group if missing, changes ownership of the config directory and `database.zefs` to `ze:ze`, writes `/etc/systemd/system/ze.service`, runs `systemctl daemon-reload`, and enables the service. Use `--dry-run` to print the unit file without root or systemd, `--config <dir>` to override the config directory in the unit, `--force` to overwrite an existing unit, and `--start` to start the service after enabling it. The systemd unit sets `XDG_RUNTIME_DIR=/run/ze`, so the daemon socket is `/run/ze/ze.socket`. For local operator CLI access, configure `daemon { socket "/run/ze/ze.socket"; }` or export `XDG_RUNTIME_DIR=/run/ze`. ## Uninstalling `ze uninstall systemd` removes the systemd service; `ze uninstall local` removes the binary and optionally the config directory. Always remove the service before the binary, so nothing is left running (or trying to restart) a binary that is gone. ```bash ze uninstall systemd # stop, disable, and remove the systemd unit ze uninstall systemd --purge # also remove the ze user and group ze uninstall local # remove binary ze uninstall local --purge # also remove config directory and database ze uninstall local --dry-run # preview what would be removed ``` ### Flags | Flag | Default | Description | |------|---------|-------------| | `--prefix` | detect from running binary | Installation prefix | | `--purge` | | Also remove config directory and database | | `--dry-run` | | Print what would be done without making changes | | `--yes` | | Skip confirmation prompt | Without `--yes`, uninstall shows what will be removed and asks for confirmation before proceeding. To check the service status, use `systemctl status ze.service` directly. ## Installing on Real Hardware (End to End) This bare-metal PXE walkthrough follows the same chain as `./le qemu install-test`: build an image, serve it, boot an installer kernel and initrd, write the disk, then log in over SSH. The reference sections below describe each piece. ### 1. Build the disk image Use the structured appliance builder (full reference: [appliance guide](../appliance/index.md), "ze appliance"): ```bash ze appliance init prod # For real hardware: set image.kernel-profile to "hardware" and # image.arch to match the target CPU in appliance.json before build. ze appliance build prod ``` This produces `~/.config/ze/appliances/prod/ze-<timestamp>.img` with TLS, SSH credentials, and a seed config baked into its `/perm` zefs. Match `image.arch` in `appliance.json` to the target CPU. ### 2. Build the installer kernel and initrd The target PXE-boots a kernel + initrd, *not* the disk image. Build both for the target architecture: ```bash ze appliance kernel prod # reads arch + profile from appliance.json ze appliance initrd # build/initrd/initrd.img.gz (pure Go) ``` Choose the profile and architecture through the native command: ```bash ze appliance kernel --profile hardware --arch amd64 prod ze appliance kernel --profile hardware --arch arm64 prod ze appliance initrd ``` The `PROFILE` selects the driver set: `qemu` (default, virtio only) or `hardware` (EFI stub, framebuffer, Intel/Realtek/Broadcom/Mellanox NICs, AHCI, NVMe). A stock distro kernel will **not** boot the module-free initrd; see [Installer Kernel](#installer-kernel). ### 3. Start the provisioning server On a ze device on the (isolated) provisioning network: <!-- source: internal/plugins/provision/main.go -- Run --> ```bash sudo ze install remote \ --interface eth0 \ --network 192.168.50.0/24 \ --image ~/.config/ze/appliances/prod/ze-<timestamp>.img \ --kernel build/kernel/Image \ --initrd build/initrd/initrd.img.gz \ --ssh-username admin \ --ssh-password 'choose-a-strong-one' ``` `--kernel` and `--initrd` copy the installer files to `build/pxe/boot/`. Stock iPXE binaries from `tools/ipxe-binaries/` are copied to `build/pxe/tftp/` if not already present. If the files are already staged from a previous run, omit `--kernel` and `--initrd`. <!-- source: internal/plugins/provision/staging.go -- stageArtifacts --> This runs DHCP (with PXE options and iPXE chainloading), TFTP (bootloaders), and the HTTP image server on `eth0`. It serves the image at `/install/image/<filename>`, a dynamically generated iPXE boot script at `/install/boot/boot.ipxe`, and a credential `database.zefs` (generated from `--ssh-*`, password stored hashed) at `/install/database.zefs`. See [Remote Provisioning (PXE)](#remote-provisioning-pxe). <!-- source: internal/plugins/imageserver/handler.go -- serveBootIPXE --> ### 4. Net-boot the target Set the target firmware to network boot. It then: 1. DHCPs an address and TFTP bootfile, chainloads iPXE. 2. iPXE sends a second DHCP request; the server detects iPXE via option 77 and responds with the HTTP boot script URL instead of the TFTP bootfile. 3. iPXE fetches `boot.ipxe` from the image server, which contains the kernel command line with `ze.server`, `ze.image`, and `ip=dhcp`. 4. iPXE loads the installer kernel + initrd via HTTP and boots. 5. The initrd downloads the image and `database.zefs`, writes the first fixed disk (`/dev/sda`, `/dev/nvme0n1`, `/dev/mmcblk0` ...; removable, virtual, optical and `mtdblock` flash devices are skipped), and reboots. 4. The target boots ze in [bootstrap mode](#bootstrap-mode) and starts SSH. ### 5. Log in and configure ```bash ssh admin@<target-ip> # the password given to --ssh-password ``` ze's SSH endpoint is the network-OS CLI. Configure with `ze config edit` and commit; the committed config replaces the bootstrap config on the next restart. ### Troubleshooting - **Installer drops to a shell** instead of rebooting on a bad `ze.server`, no writable disk, or a download that fails 3 times. Read the serial/VGA console for the `[ze-install] FATAL:` line. - **No disk chosen**: with more than one non-removable disk and no `ze.target`, the installer stops and names the candidates rather than picking one. Pass `ze.target=/dev/vda` in the cmdline, or detach the extra fixed disks. - **Download stalls / non-standard port**: confirm the image server's port and pass `ze.port=` in the iPXE cmdline. - **Dry-run first**: `./le qemu install-test` reproduces the whole chain and reports a broken image, kernel, or initrd before hardware is touched. ## Appliance ISO Install For appliances built with `ze appliance build`, ISO media is an offline install transport for the gokrazy image. The image is gzip-compressed inside the ISO; the installer initrd decompresses it during installation. Create it with: <!-- source: internal/appliance/cmd_iso.go -- runIso --> ```bash ze appliance build prod ze appliance kernel --profile hardware prod # download or build installer kernel (reads arch from config) ze appliance initrd # download or build installer initrd ze appliance iso prod ``` Use `ze appliance iso --check` to verify all prerequisites (kernel, initrd, grub, xorriso) are available before building. The `kernel` and `initrd` commands download pre-built artifacts when configured, then fall back to the native local QEMU or Docker kernel builder and the Go initrd builder. Cached artifacts are stored under `$XDG_CACHE_HOME/ze/`. For arm64 targets (the kernel version is single-sourced in `internal/appliance/kernel.version`; `--version` only overrides it and must be a kernel >= 7): ```bash ze appliance kernel --arch arm64 ze appliance iso --kernel build/kernel/Image prod ``` The ISO installer decompresses and writes the embedded image to the target disk. Unlike PXE provisioning, it does not download `/install/database.zefs` or write a separate database after the disk image, because the appliance build already injected `/perm/ze/database.zefs` into the image. <!-- source: internal/install/disk/run.go -- runISO --> <!-- source: internal/appliance/cmd_build.go -- injectZeFS --> The ISO bootloader target follows `image.arch`: amd64 images produce `BOOTX64.EFI`, arm64 images produce `BOOTAA64.EFI`. By default the command checks for a cached kernel under `$XDG_CACHE_HOME/ze/installer-kernel/` then falls back to `build/kernel/Image`. Pass `--kernel` to use a specific kernel path. <!-- source: internal/appliance/cmd_iso.go -- isoGRUBTarget, defaultISOKernelPath --> If the target has more than one fixed disk, create the ISO with an explicit whole-disk target. The initrd rejects ambiguous implicit disk selection in ISO mode, excludes the ISO source media from target candidates, and requires a builder-generated media id match before it trusts a mounted installer volume. <!-- source: internal/install/disk/detect.go -- findTargetDisk; internal/install/disk/iso.go -- find ISO media --> ```bash ze appliance iso --target /dev/vda prod ``` The generated ISO includes the installer kernel, initrd, the selected image, its checksum, and metadata. It contains the full provisioned appliance image, so handle it like the `.img` artifact. **USB write method:** the ISO can be written with `dd`, Etcher, or Rufus in DD mode. Ventoy is also supported when the installer kernel includes loop device and FAT/exFAT filesystem support (the `hardware` kernel profile has this). The initrd detects the ISO file on the Ventoy data partition, loop-mounts it, and proceeds with the installation. When using the `qemu` kernel profile, Ventoy is not supported. ISO installs power off after the disk write so the removable installer media can be removed before the next boot. They do not auto-reboot while the ISO is still present. <!-- source: internal/install/disk/run.go -- runISO --> <!-- source: internal/appliance/cmd_iso.go -- stageISO --> ## Remote Provisioning (PXE) `ze install remote` is a one-command provisioning server that PXE-boots target machines with a gokrazy image containing ze. > **Warning: the provisioning network MUST be isolated.** Run `ze install > remote` only on a dedicated segment with no other DHCP server. On a shared > network a foreign DHCP server (for example a corporate one) can win the > boot-time race and hand the target a lease with no route back to `ze.server`; > the installer then probes an unreachable server until `ze.wait` expires and > drops to a debug shell, and the disk is never written. The initrd now > re-validates the kernel lease and recovers when a reachable interface exists > (see [Kernel Command Line](#kernel-command-line)), but a second DHCP server on > the same L2 segment is unsupported and can still break provisioning. ### How It Works 1. The operator runs `ze install remote` on an existing ze device connected to the provisioning network. 2. Ze generates a config enabling DHCP (with PXE extensions), TFTP, and an HTTP image server, then forks itself (`ze -`) with the config piped to stdin. 3. A target machine PXE-boots: DHCP assigns an IP and directs it to the TFTP bootloader, which chain-loads the installer kernel and initrd via HTTP. 4. The installer writes the gokrazy image to disk and reboots. 5. The target boots into ze in bootstrap mode: discovers all interfaces, enables DHCP client on each ethernet NIC, and starts SSH for operator access. ### Quick Start ```bash ze install remote \ --interface eth0 \ --network 192.168.1.0/24 \ --image /path/to/gokrazy.img \ --ssh-username admin \ --ssh-password changeme ``` This starts three servers on `eth0`: | Protocol | Port | Purpose | |----------|------|---------| | DHCP | 67/udp | IP assignment with PXE options (bootfile, next-server) | | TFTP | 69/udp | Bootloader delivery (iPXE for BIOS/UEFI) | | HTTP | 80/tcp | Disk image and boot file serving | The server IP is resolved from the interface's first IPv4 address. If the interface has no IPv4 address, the address from `--network` is added via netlink and removed on exit (if `--network` is a network address like `192.168.1.0/24`, the first host `192.168.1.1` is used). Use `--address` to override. ### Flags | Flag | Required | Default | Description | |------|----------|---------|-------------| | `--interface` | Yes | | Network interface to bind all servers | | `--network` | Yes | | Provisioning subnet CIDR (/8 to /30) | | `--image` | Yes | | Path to gokrazy disk image file | | `--ssh-username` | Yes | | Admin username for the installed target | | `--ssh-password` | Yes | | Admin password (bcrypt-hashed before embedding) | | `--address` | No | First IPv4 on interface (or from `--network` if none) | Server IP override | | `--kernel` | No | | Path to installer kernel (copied to boot directory) | | `--initrd` | No | | Path to installer initrd (copied to boot directory) | | `--pxe-dir` | No | `build/pxe` | PXE serve root: boot files under `<dir>/boot`, TFTP under `<dir>/tftp` | ### DHCP Pool The DHCP pool range scales with the subnet size. The server IP is excluded from the pool. Examples: | Network | Server | Pool Start | Pool Stop | |---------|--------|------------|-----------| | 10.0.0.0/24 | 10.0.0.1 | 10.0.0.2 | 10.0.0.254 | | 192.168.1.0/28 | 192.168.1.1 | 192.168.1.2 | 192.168.1.14 | | 10.1.1.0/30 | 10.1.1.1 | 10.1.1.2 | 10.1.1.2 | ### PXE Boot The DHCP server detects PXE clients via option 60 (`PXEClient:`) and reads option 93 (client architecture) to select the bootfile: <!-- source: internal/plugins/dhcpserver/handler.go -- appendPXEOptions --> | Architecture | Bootfile | |-------------|----------| | UEFI x64 (type 7) | `ipxe.efi` | | Everything else, including BIOS (type 0) and UEFI types 6 and 9 | `ipxe.pxe` | When the PXE client is iPXE (detected via option 77 user-class prefix "iPXE") and `boot-script-url` is configured, the DHCP server sends the HTTP boot script URL as the bootfile instead of the TFTP binary. This two-stage chainload (firmware -> iPXE via TFTP -> boot.ipxe via HTTP) eliminates the need for custom-embedded iPXE builds. <!-- source: internal/plugins/dhcpserver/handler.go -- isIPXE --> The image server generates `boot.ipxe` dynamically at `/install/boot/boot.ipxe` with the correct `ze.server`, `ze.image` (lexicographically last `.img` file), and `ze.port` (when not 80). A static `boot.ipxe` file in the boot directory takes precedence over dynamic generation for operator customization. <!-- source: internal/plugins/imageserver/handler.go -- serveBootIPXE --> Bootfiles are served from `build/pxe/tftp/` via TFTP. The installer kernel and initrd are served from `build/pxe/boot/` via HTTP. Stock iPXE binaries are bundled in `tools/ipxe-binaries/` and staged automatically by `ze install remote`. The default `--pxe-dir build/pxe` is relative to the working directory. Run `ze install remote` from the repository root, or pass an absolute `--pxe-dir`. The command stages bundled iPXE files and the explicit `--kernel` and `--initrd` artifacts under that root. ### SSH Credentials The `--ssh-password` is bcrypt-hashed (cost 10) before being embedded in the generated config as `ssh-password-hash`. The plaintext password is never written to disk or config. It is visible in the process listing (`ps aux`) while `ze install remote` is running. ### Generated Config `ze install remote` generates a standard ze config in brace format and pipes it to a child `ze` process. The generated config enables three plugins: - **dhcpserver** with PXE options, a shared-network, and the computed pool - **tftpserver** bound to the provisioning interface - **imageserver** bound to the provisioning interface with SSH credentials The same provisioning setup can be achieved by writing the config manually and running `ze <config-file>`. ### Watching Provisioning Activity `ze install remote` runs the install servers at `info` log level by default, so the terminal shows the live boot sequence with no extra flags: - **dhcpserver** logs each lease: `dhcpserver: lease request=DISCOVER reply=OFFER mac=… ip=… bootfile=…` - **tftpserver** logs each bootloader fetch: `tftpserver: read request file=ipxe.efi client=…` - **imageserver** logs each HTTP request (`boot.ipxe`, `vmlinuz`, `initrd.img.gz`, the disk image, `database.zefs`) and, at startup, the image it will serve with its build identity: `imageserver: image to install image=ze-<ts>.img built=<ts> ze-version=… sha256=…` Set `ze.log` to change verbosity (`ze.log=debug`, `ze.log=warn`); an explicit `ze.log` or per-subsystem `ze.log.{dhcpserver,tftpserver,imageserver}` overrides the info default. <!-- source: internal/plugins/provision/main.go -- withInstallLogDefaults --> If you see the iPXE downloads (`boot.ipxe`, `vmlinuz`, `initrd.img.gz`) but never an `/install/image/<name>` request, the target booted the kernel but its initrd could not reach the server to pull the image (commonly a foreign DHCP lease on a non-isolated network) and the install did not complete. ### Confirming Which Image Is Installed A failed PXE install never writes the disk, so the target reboots into whatever was on it before (for example a previous ISO install) and can look unchanged. To confirm the build actually running, `ze appliance build` bakes a manifest into the image at `/perm/ze/build.json`, which `ze version` reports: ``` ze 26.06.17 (built …) … image: ze-20260617-160000.img (built 20260617-160000) appliance: prod ``` The baked manifest omits the image checksum (it would be self-referential, since baking changes the image); the full `sha256` is in the external `build.json` next to the image and in the provisioning server's `imageserver: image to install` log. Compare the `image:`/`built` line on the target with that server log line to verify the latest image installed. <!-- source: internal/core/version/version.go -- Extended, ReadManifestFile --> <!-- source: internal/appliance/cmd_build.go -- injectZeFS baked manifest --> ### Requirements - An **isolated** provisioning network: no second DHCP server on the L2 segment, or a foreign lease can leave the target with no route to `ze.server` - Root privileges (DHCP, TFTP, and HTTP bind to privileged ports; interface auto-configuration uses netlink) - Disk image at the path specified by `--image` - Installer kernel and initrd: pass `--kernel` and `--initrd` on first run, or pre-stage files in `build/pxe/boot/` - iPXE binaries: bundled in `tools/ipxe-binaries/`, auto-staged to `build/pxe/tftp/` if not present ### Shutdown SIGTERM or SIGINT sent to `ze install remote` is forwarded to the child ze process. The child shuts down cleanly (closes listeners, drains connections). Closing the parent also sends EOF on the stdin pipe, which ze treats as a shutdown signal. ## Bootstrap Mode When ze starts with a zefs database but no config file and no template, it enters bootstrap mode automatically. This is the expected state after a PXE-provisioned device boots for the first time. ### What Happens 1. Ze detects no config in zefs (no `file/active/ze.conf`, no `file/template/ze.conf`). 2. Interface discovery enumerates all OS network interfaces. 3. A minimal config is generated: DHCP client enabled on every ethernet interface, SSH server enabled. 4. Ze starts with this config. DHCP clients acquire addresses, SSH becomes reachable. ### Operator Workflow 1. SSH into the device using the credentials pre-provisioned by `ze install remote`. 2. Configure ze via the CLI (`ze config edit`, then commit). 3. The committed config replaces the bootstrap config. On the next restart, ze starts in normal mode. ### Constraints - Only ethernet interfaces get DHCP. Bridge, veth, dummy, loopback, wireguard, and xfrm interfaces are skipped. - SSH credentials come from zefs (written by the installer initrd), not from the generated config. - Bootstrap mode is only intended for trusted/provisioning networks. SSH is enabled on all interfaces. - If no ethernet interfaces are found (or the netlink backend is not available), bootstrap mode does not activate and ze falls through to the next startup path (error; use `--web-only` for standalone web UI). ### Limitations - Single image: all targets receive the same gokrazy image - No per-MAC image selection (future) - No post-install hooks (future) - Assumes an isolated provisioning network (no proxy DHCP) ## Installer Initrd The installer initrd is a minimal Linux image that performs the actual disk write on target hardware. It is the final step in the PXE chain: the bootloader fetches the kernel and initrd via HTTP, the kernel boots, and the initrd's init script installs ze. ### What It Does 1. Parses `ze.source`, `ze.server`, `ze.port`, `ze.image`, `ze.target`, and `ze.media-id` from the kernel command line 2. In HTTP mode, downloads the gokrazy disk image from `http://<server>:<port>/install/image/<name>` 3. In ISO mode, mounts local ISO media read-only and selects the embedded compressed image 4. Writes the image to the selected non-removable block device (decompressing in ISO mode) 5. In HTTP mode only, re-reads the partition table, mounts partition 4 (ext4, `/perm`), downloads `database.zefs`, and writes it to `/perm/ze/database.zefs` 6. In HTTP mode, reboots. In ISO mode, powers off so the operator can remove the installer media before the next boot. ### Building the Initrd Prerequisites: the Go toolchain only. The initrd is built in pure Go, so no `busybox`, `cpio`, or `gzip` host tools are required. ```bash ze appliance initrd ``` This cross-compiles `cmd/ze-installer` for the target architecture and packs the single static binary into `build/initrd/initrd.img.gz` (the `/init` entry of a pure-Go newc cpio written through `compress/gzip`). Copy it alongside a Linux kernel to the boot directory served by the image server (`build/pxe/boot/`). ### Kernel Command Line The bootloader sets these parameters: | Parameter | Required | Default | Purpose | |-----------|----------|---------|---------| | `ze.source` | No | `http` | Source mode: `http` for PXE or `iso` for local ISO media | | `ze.server` | HTTP only | | IPv4 address of the ze-install server | | `ze.port` | No | `80` | TCP port of the install HTTP server (1-65535) | | `ze.image` | No | `ze.img` | Name of the disk image to install | | `ze.target` | No | | Explicit whole-disk target such as `/dev/vda` | | `ze.wait` | No | `30` | Max server probe attempts before giving up (0 = skip probe) | | `ze.media-id` | ISO only | | Builder-generated 32-hex token that identifies the booted installer ISO | | `ip=dhcp` | HTTP only | | Kernel-level network configuration (with userspace fallback) | `ze.port` exists for install servers that cannot bind the privileged port 80 (for example an unprivileged HTTP server, or a QEMU test harness that serves on an ephemeral port). ISO mode does not use `ze.server`, `ze.port`, or `ip=dhcp`. On some hardware (e.g. Intel I226-V) the kernel `ip=dhcp` autoconfiguration races against NIC carrier detection and times out before the link comes up; on a non-isolated network it can also win a lease from a foreign DHCP server (for example a corporate one) whose default route cannot reach `ze.server`. The initrd guards against both. It trusts the kernel-provided default route only when `ze.server` actually answers an HTTP probe; if there is no default route, or the route present cannot reach the server, it brings up all non-loopback interfaces, waits up to 10 seconds for carrier, and runs an in-process DHCP client (`nclient4`) on each interface, verifying the server is reachable before accepting the lease. Interfaces that obtain a lease but cannot route to `ze.server:ze.port` are flushed and skipped. If no interface produces a working route, the installer drops to the recovery console. (A foreign DHCP server on a shared segment can still defeat this if it also wins the per-interface lease race, which is why the provisioning network must be isolated.) Even after the network is configured, the install server may not be reachable yet (switch STP port transitions can block traffic for 30-50 seconds after a reboot). The initrd probes the server with a 2-second timeout, retrying up to 30 times. Each probe distinguishes "TCP unreachable" from "HTTP error response" so a server that returns 404 is still considered reachable. Interface state and routing tables are logged every 10 attempts for diagnostics. <!-- source: internal/install/disk/network.go -- ensureNetwork --> The installer fans its progress and `FATAL` lines to every console listed in `/sys/class/tty/console/active`, not just the single `/dev/console` the kernel marks preferred. On a headless box the preferred console can be a dead VGA tty while kernel messages still reach the serial line, which would otherwise hide all installer output. The generated `boot.ipxe` also selects the console set per client architecture via iPXE's `${buildarch}`: x86 clients get `console=tty0 console=ttyS0`, while arm64 also keeps `console=ttyAMA0` (the ARM PL011 UART, which never registers on x86 and can dead-end `/dev/console` if left on an x86 cmdline). <!-- source: internal/install/disk/console_linux.go -- setupConsoles; internal/plugins/imageserver/handler.go -- serveBootIPXE --> Existing `ze install remote` deployments need no `ze.source` change because the default source is `http`. <!-- source: internal/install/disk/cmdline.go -- parseCmdline --> The installer selects a non-removable block device via sysfs. Virtual devices (loop, ram, dm, zram, md), optical drives (sr), floppies (fd), and firmware/CFI flash (`mtdblock`, the QEMU `virt` machine's pflash) are skipped. The installer refuses to choose implicitly when more than one fixed candidate remains, in both HTTP and ISO mode, and names the candidates it found. ISO mode also excludes the ISO source media before it counts them. Use `ze.target=/dev/vda` to name an explicit whole disk. Supported target disk forms include `/dev/sda` (SATA/SCSI), `/dev/vda` (virtio-blk, used by QEMU/KVM), `/dev/nvme0n1` (NVMe), and `/dev/mmcblk0` (eMMC). <!-- source: internal/install/disk/detect.go -- findTargetDisk --> ### Error Handling If the installer encounters an error (missing server IP, no disk found, download failure), it does not silently reboot. It opens a Go recovery console on every active console (preferring a serial console, since headless installs are driven over serial) so the operator can diagnose network or hardware issues. The recovery console is a fixed menu (retry network, diagnostics, reboot, power off), not a shell. Its policy has three branches: with `ze.rescue-auth` set the console is gated on a rescue token; with no credential on ISO media it opens ungated (the operator is physically present); with no credential on a network install it prints the error, waits ~30 seconds, and reboots, so an unattended box never hangs waiting for a credential nobody can supply. <!-- source: internal/install/disk/rescue_linux.go -- fatalInitrd, rescueOnConsoles --> `ze plugin provision` mints the rescue token, prints it once, and writes only its salted argon2id digest into the generated config as `rescue-auth`. The image server puts that digest on the installer kernel cmdline as `ze.rescue-auth`, and the installer derives the typed token under the same salt to compare. Record the token when provisioning: it is not recoverable afterwards, and without it a failed network install offers no shell. The credential is deliberately a dedicated token rather than the admin password. The iPXE script carrying it is served unauthenticated over plain HTTP on the provisioning network, so the digest is a public value; committing it to the admin password would put that password within reach of anyone on that network. Verifying a typed token costs **64 MiB of RAM** for the duration of the derivation (measured: 67,120,208 bytes, the configured argon2id arena plus scratch). The installer initrd must have that much free at the rescue prompt, which is worth knowing because that prompt appears on a machine that has just failed to install. A malformed `ze.rescue-auth` is never gated on: the installer reboots instead, so a typo in the credential can neither open an unauthenticated shell nor strand an unattended box at a prompt nobody can answer. <!-- source: internal/core/rescueauth/rescueauth.go -- argonMemory, digestOf --> <!-- source: internal/install/disk/rescue_linux.go -- selectFatalBranch --> <!-- source: internal/core/rescueauth/rescueauth.go -- Value, Check, NewValue --> <!-- source: internal/plugins/imageserver/handler.go -- serveBootIPXE, rescueAuth --> ### Running Tests The installer logic has Go unit tests alongside each source file in `internal/install/disk` (cmdline parsing, disk detection, the netlink lease apply, the stall-timeout download, the partition-node wait, and the recovery console / fatal policy): ```bash CGO_ENABLED=0 go test ./internal/install/disk/... ``` End-to-end boot and install are covered by the QEMU evidence harness, which boots the real Go initrd: ```bash ./le qemu install-test # HTTP PXE install ./le qemu install-iso-test # ISO install ``` ### No External Binaries The initrd contains exactly one file: the `cmd/ze-installer` Go binary as `/init` (PID 1). There is no busybox and no shell. Every operation the old shell init shelled out to (mount, DHCP, HTTP download, link/address/route, reboot) is an in-process `golang.org/x/sys/unix` syscall or a vendored library call, so there is no applet that can be `not found` at install time. See `ai/rules/platform-linux.md`. ## Installer Kernel The initrd carries **no kernel modules**, so the kernel it boots alongside must have NIC drivers, disk drivers, ext4, devtmpfs, initramfs and `ip=dhcp` autoconfiguration all built in (`=y`). Stock distro/cloud kernels ship these as modules and cannot boot the initrd. `ze` deliberately ships no installer kernel: the right kernel is site-specific. `tools/installer-kernel/` builds a reference kernel with two profiles: | Profile | File | Drivers | Use case | |---------|------|---------|----------| | `qemu` (default) | `qemu.config` | virtio NIC + block | QEMU tests, fast build | | `hardware` | `hardware.config` | virtio + EFI + framebuffer + Intel/Realtek/Broadcom/Mellanox NICs + AHCI + NVMe | Bare metal PXE/ISO install | Both profiles merge onto a shared base (`kernel.config`) that provides IP autoconfiguration, SCSI, ext4, initramfs, devtmpfs and serial console. ```bash ze appliance kernel prod ze appliance kernel --profile hardware --arch amd64 ze appliance kernel --builder qemu --arch arm64 prod ``` Set `image.kernel-profile` to `"hardware"` in `appliance.json` so `ze appliance kernel <name>` picks it up automatically. The CLI selects Docker first, then the shared QEMU backend, unless `--builder` forces one path. `ze appliance kernel` resolves the open profile registry in Go and passes the fragments to `internal/appliance/kernelbuilder`. The worker merges and checks the config, then rejects any required symbol that is not built in. Docker and QEMU are native backends of the same Go driver. Output is cached by target, architecture, profile, config, and kernel version. <!-- source: internal/appliance/kernelreg.go -- resolveKernelProfile --> <!-- source: internal/appliance/kernelreq.go -- enforceKernelRequirements --> <!-- source: internal/appliance/kernelbuilder/driver.go -- Build --> <!-- source: internal/appliance/kernelbuilder/worker.go -- RunWorker --> <!-- source: internal/appliance/kernelbuilder/qemu.go -- runQEMU --> ## End-to-End QEMU Verification `./le qemu install-test` exercises the entire chain with no hardware. It builds the initrd and an appliance image, boots the installer kernel and initrd against a blank virtio disk, downloads and writes the image and ZeFS over HTTP, then boots the written disk and logs in over SSH. ```bash ZE_INSTALL_KERNEL=$PWD/build/kernel/Image ./le qemu install-test ``` `./le qemu install-iso-test` exercises the ISO transport. It creates an ISO through `ze appliance iso`, boots it, verifies the embedded image is written without the PXE-only branch, checks safe poweroff and the GPT layout, then logs in with the embedded ZeFS credentials. <!-- source: internal/le/qemu/actions.go -- Actions --> ```bash ZE_INSTALL_KERNEL=$PWD/build/kernel/Image ./le qemu install-iso-test ``` The ISO evidence self-skips with `INSTALL-ISO-QEMU: SKIP` when QEMU, a suitable installer kernel, UEFI firmware, `grub-mkstandalone`/`grub2-mkstandalone`, `xorriso`, or image-build tooling is unavailable. <!-- source: internal/le/qemu/actions.go -- Answer --> The test self-skips (does not fail) when `ZE_INSTALL_KERNEL` is unset or a container runtime / `qemu-system-*` is unavailable, because there is no safe default installer kernel. ### Environment Knobs | Variable | Default | Purpose | |----------|---------|---------| | `ZE_INSTALL_KERNEL` | none; self-skips | Path to the installer kernel `Image`/`vmlinuz` | | `ZE_INSTALL_ARCH` | host arch (`amd64` for ISO evidence) | Target architecture for QEMU installer evidence and generated appliance config (`arm64`/`amd64`) | | `ZE_INSTALL_BOOT_TIMEOUT` | `300` | Seconds to wait for the installer to write the disk | | `ZE_INSTALL_IMAGE_SIZE` | appliance default (2 GiB) | Override image `size-bytes` (must stay large enough for the gokrazy A/B layout) | | `ZE_INSTALL_SSH_USER` / `ZE_INSTALL_SSH_PASS` | `admin` / `secret` | Power-user credentials provisioned into the image and used for the AC login | | `ZE_INSTALL_NIC` | `virtio-net-pci` | QEMU NIC model for the installer boot | | `ZE_INSTALL_KEEP` | unset | Keep the work directory (image, written disk, serial logs) for inspection | | `ZE_INSTALL_IMAGE` / `ZE_INSTALL_ZEFS` | unset | Reuse a prebuilt image + zefs instead of building one | ### QEMU Networking Note The test points the guest at the slirp gateway (`ze.server=10.0.2.2 ze.port=<ephemeral>`) rather than a `guestfwd` forward. A `guestfwd` services only the first guest connection, which stalls the installer's second and third downloads (image, zefs); the gateway handles the sequential connections the installer makes. This is only a test-harness concern; real PXE installs use a real network and `ze install remote` on port 80. --- ### Page: Build and install Ze on Ubuntu https://ze-software.net/guides/ubuntu-build-install/ # Build and install Ze on Ubuntu This page starts from a blank Ubuntu server and leaves you with an installed `ze` binary, a `database.zefs`, an SSH listener, a systemd service, and one place to add Ze features. The commands assume Ubuntu 24.04 or newer, `sudo`, and an `amd64` or `arm64` host. Replace `edge-01` and every password before running them on a real box. <!-- source: internal/le/setup/actions.go -- Answer --> <!-- source: internal/le/setup/actions.go -- Answer --> <!-- source: internal/le/featuretags/daemontags.go -- DaemonTags --> <!-- source: internal/plugins/init/main.go -- ze init input format and database.zefs creation --> <!-- source: internal/component/authz/yang/ze-authz-conf.yang -- system.authentication.user base fields and system.authorization.profile --> <!-- source: internal/component/ssh/yang/ze-ssh-conf.yang -- environment.ssh and public-keys augmentation --> ## 1. Install build tools Ubuntu's packaged Go may lag the version Ze needs, so install Go from `go.dev` and let `apt` provide the rest. ```bash sudo apt-get update sudo apt-get install -y \ ca-certificates \ curl \ git \ build-essential \ jq \ protobuf-compiler ``` Install Go 1.27, the version `go.mod` requires. Pick the current 1.27 patch release from <https://go.dev/dl/> if a newer patch exists. ```bash GO_VERSION=1.27.0 GO_ARCH="$(dpkg --print-architecture)" case "$GO_ARCH" in amd64|arm64) ;; *) echo "unsupported Go architecture: $GO_ARCH" >&2; exit 1 ;; esac curl -fsSLO "https://go.dev/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf "go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" cat >> "$HOME/.profile" <<'EOF' export PATH=/usr/local/go/bin:$HOME/go/bin:$PATH EOF export PATH=/usr/local/go/bin:$HOME/go/bin:$PATH go version ``` Optional tools for appliance and ISO work: ```bash sudo apt-get install -y qemu-system-x86 e2fsprogs xorriso grub-efi-amd64-bin ``` On an arm64 host, ask for `grub-efi-arm64-bin` instead: Debian packages one GRUB module set per architecture, and the amd64 one has no installation candidate there. Ze also ships a setup checker. It uses the same tool list as the developer and appliance checks. ```bash git clone https://github.com/ze-software/ze.git cd ze ./le setup check ``` The `check` action lists anything missing as `[missing] <tool>` and exits non-zero. It changes nothing. `./le setup install` installs missing packages and prints each command first. Commands that need root use `sudo -n`, so nothing waits on a prompt it cannot answer. When sudo wants a password it asks once through `sudo -v`, and only when a terminal is attached; without a terminal it prints the command and exits non-zero. ## 2. Build Ze Build the daemon directly with Go: ```bash cd ~/ze CGO_ENABLED=0 go build -tags 'ze_core ze_distro ze_anomaly ze_as112 ze_bfd ze_bgp ze_bmp ze_copp ze_cos ze_ddos ze_dhcpserver ze_exabgp ze_flowexport ze_geodns ze_gnmi ze_grpc ze_ike ze_isis ze_l2tp ze_ldp ze_lg ze_mcp ze_mpls ze_mrt ze_ntp ze_ospf ze_policyroute ze_pxe ze_radius ze_rest ze_rsvpte ze_ssh ze_tacacs ze_telemetry ze_trafficusage ze_vpp ze_vrrp ze_web' -o bin/ze ./cmd/ze ``` The default feature-tag list is derived from `feature-gates.txt`. `./le feature-tags check` verifies every checked-in consumer of that list, and `./le repository tracked-build matrix` shows the shipped build flavors. For a deliberately smaller custom binary, name its feature tags explicitly: ```bash go build -tags 'ze_core ze_ssh ze_lg ze_web' -o bin/ze ./cmd/ze ``` ## 3. Install the binary Install the built binary into `/usr/local/bin/ze` and create the default config directory. ```bash sudo ./bin/ze install local --prefix /usr/local /usr/local/bin/ze version ``` `/usr/local/bin/ze` uses `/etc/ze` as its config directory. ## 4. Create `database.zefs` `ze init` creates `/etc/ze/database.zefs`. It stores the bootstrap admin user, the SSH client defaults, and the instance name. The input lines are: 1. username 2. password 3. SSH host for local CLI credentials, empty means `127.0.0.1` 4. SSH port for local CLI credentials, empty means `2222` 5. instance name, used as the default config filename ```bash sudo install -d -m 0700 /etc/ze printf 'admin\nCHANGE_ME_BOOTSTRAP\n\n\nedge-01\n' | sudo /usr/local/bin/ze init sudo test -s /etc/ze/database.zefs ``` This creates the zefs bootstrap admin. Keep it as a recovery user until you have tested the configured users below. ## 5. Create the first config in zefs Keep the active configuration inside `database.zefs`. Do not create `/etc/ze/edge-01.conf` as a second source of truth. Build the candidate with set-format lines, render the import file, validate it, then load it into zefs with one `ze config import` command. Hash the configured user passwords before writing the candidate. This keeps plaintext out of shell history, process arguments, and the zefs command history. ```bash ADMIN_HASH="$(printf '%s\n' 'CHANGE_ME_BOOTSTRAP' | /usr/local/bin/ze passwd)" NOC_HASH="$(printf '%s\n' 'CHANGE_ME_NOC' | /usr/local/bin/ze passwd)" umask 077 CONFIG_SET="$(mktemp)" CONFIG_IMPORT="$(mktemp)" trap 'rm -f "$CONFIG_SET" "$CONFIG_IMPORT"' EXIT cat >"$CONFIG_SET" <<EOF set environment ssh enabled enable set environment ssh server main ip 0.0.0.0 set environment ssh server main port 2222 set environment ssh idle-timeout 600 set environment ssh max-sessions 32 set system authentication user admin password "$ADMIN_HASH" set system authentication user admin profile admin set system authentication user noc password "$NOC_HASH" set system authentication user noc profile read-only set system authorization profile admin run default-action allow set system authorization profile admin edit default-action allow set system authorization profile read-only run default-action allow set system authorization profile read-only edit default-action deny EOF /usr/local/bin/ze config migrate -o "$CONFIG_IMPORT" format hierarchical "$CONFIG_SET" /usr/local/bin/ze config validate "$CONFIG_IMPORT" sudo /usr/local/bin/ze config import --name edge-01.conf "$CONFIG_IMPORT" sudo /usr/local/bin/ze config list ``` Expected validation output: ```text configuration valid: /tmp/tmp.XXXXXXXXXX ``` The explicit `admin` user matters. Once any configured user has profile assignments, unassigned users are denied by local RBAC. Defining `admin` in config keeps the bootstrap name usable with an explicit `admin` profile. `ze start` reads the active `edge-01.conf` config from zefs. ## 6. Install and start systemd ```bash sudo /usr/local/bin/ze install systemd --start systemctl status ze.service --no-pager ``` The generated unit starts `/usr/local/bin/ze start`, uses `/etc/ze` as `ZE_CONFIG_DIR`, and sets the runtime directory to `/run/ze`. For local operator commands from the shell, either run as root with the same config directory, or point the CLI at the systemd runtime socket: ```bash export XDG_RUNTIME_DIR=/run/ze /usr/local/bin/ze status /usr/local/bin/ze cli -c "help" ``` ## 7. Test SSH login and RBAC The `admin` user can run operational and edit commands. The `noc` user can run operational commands but cannot edit config. ```bash export XDG_RUNTIME_DIR=/run/ze ZE_SSH_PASSWORD='CHANGE_ME_BOOTSTRAP' /usr/local/bin/ze cli --user admin -c "help" ZE_SSH_PASSWORD='CHANGE_ME_NOC' /usr/local/bin/ze cli --user noc -c "help" ``` If the server listens on a management address, connect remotely with `--remote host:port`, using the host and port from `environment ssh`. ```bash ZE_SSH_PASSWORD='CHANGE_ME_NOC' /usr/local/bin/ze cli --remote 192.0.2.10:2222 --user noc -c "help" ``` ## 8. Add features There are two kinds of feature work. | Feature type | What you change | Example | | --- | --- | --- | | Compiled service | Explicit Go build tags | `ze_lg` compiles the looking glass server | | Runtime feature | Config lines and plugin declarations | `environment looking-glass`, `plugin internal bgp-rr`, `firewall backend nft` | For normal installs, keep the default binary and add runtime features to the active zefs config. The feature pages use this pattern: read the current zefs entry, normalize it to set format, append set-format lines, render a checked import file, validate, import, and reload. ```bash set -euo pipefail umask 077 CONFIG_SET="$(mktemp)" CONFIG_IMPORT="$(mktemp)" trap 'rm -f "$CONFIG_SET" "$CONFIG_IMPORT"' EXIT sudo /usr/local/bin/ze config cat edge-01.conf | /usr/local/bin/ze config migrate -o "$CONFIG_SET" - cat >>"$CONFIG_SET" <<'EOF' set plugin internal bgp-rr use bgp-rr EOF /usr/local/bin/ze config migrate -o "$CONFIG_IMPORT" format hierarchical "$CONFIG_SET" /usr/local/bin/ze config validate "$CONFIG_IMPORT" sudo /usr/local/bin/ze config import --name edge-01.conf "$CONFIG_IMPORT" sudo systemctl reload ze.service ``` Use the pages below as feature-specific starting points: | Page | Adds | | --- | --- | | [Operator access with SSH and RBAC](../operator-access-rbac/index.md) | Local users, profiles, public keys, TACACS+ pointers | | [FlowSpec route reflector](../flowspec-route-reflector/index.md) | `bgp-rr`, iBGP FlowSpec clients, route-reflector-client flags | | [FlowSpec protected router](../flowspec-protected-router/index.md) | nft firewall backend, control-plane policing, FlowSpec firewall bridge | | [Looking glass](../public-looking-glass/index.md) | public read-only HTTP looking glass and birdwatcher-compatible API | ## 9. Stop or roll back ```bash sudo systemctl stop ze.service sudo /usr/local/bin/ze uninstall systemd sudo /usr/local/bin/ze uninstall local ``` Use `--purge` only when you want to remove `/etc/ze` and `database.zefs`. --- ### Page: Ze Architecture https://ze-software.net/architecture/ # Ze Architecture A one-page guide to how Ze is structured. For the full design document with rationale, wire format details, and performance analysis, see [DESIGN.md](https://github.com/ze-software/ze/blob/main/docs/DESIGN.md). For the canonical architecture reference, see [architecture/core-design.md](https://github.com/ze-software/ze/blob/main/docs/architecture/core-design.md). ## What Ze Is Ze is an open-source configuration and protocol engine. The network operating system built on it speaks BGP, manages Linux network interfaces, programs the FIB, and serves configuration over SSH and the web UI. None of that is in the core. The core is a supervisor which holds a typed message bus, a config provider, and a plugin manager. BGP, interface management and the rest register as subsystems and plugins. Each one brings its own YANG and augments the configuration tree where it belongs. ## Component Map [![Ze component architecture. Operator surfaces share one command and configuration contract. The protocol-agnostic Engine composes a ConfigProvider, Plugin Server and EventBus, and PluginManager. Registry-loaded routing and system plugins feed route selection and the FIB. An internal Dataplane group contains Netfilter, VPP, ARP, ND, and DHCP; Netfilter and VPP point to external Linux and VPP targets.](https://ze-software.net/assets/ze-components.svg)](https://ze-software.net/assets/ze-components.svg) [![Ze component architecture. Operator surfaces share one command and configuration contract. The protocol-agnostic Engine composes a ConfigProvider, Plugin Server and EventBus, and PluginManager. Registry-loaded routing and system plugins feed route selection and the FIB. An internal Dataplane group contains Netfilter, VPP, ARP, ND, and DHCP; Netfilter and VPP point to external Linux and VPP targets.](https://ze-software.net/assets/ze-components-dark.svg)](https://ze-software.net/assets/ze-components-dark.svg) Ze components and their runtime relationships. Dashed outlines mark configured, build-dependent, or optional paths. Open the diagram for a full-size view; the text map below preserves the same structure for text readers. ``` ze daemon (registry-driven YANG runtime) | +-- Engine (protocol-agnostic lifecycle supervisor) | +-- ConfigProvider (YANG tree and transactions) | +-- PluginManager / ProcessManager | +-- Configured subsystems (PPPoE and L2TP) | +-- Plugin Server (shared runtime backbone) | +-- Typed EventBus, subscriptions, and event history | +-- YANG command / RPC dispatcher | +-- Registry discovery and five-stage plugin startup | +-- BGP reactor and feature plugins (RIB, RS/RR, policy, RPKI, ASPA, BMP) | +-- OSPF | +-- IS-IS | +-- BFD | +-- Loc-RIB -> sysrib -> FIB | +-- Dataplane: Netfilter -> Linux; VPP -> VPP; ARP; ND; DHCP | +-- Interface, firewall, traffic, and other ConfigRoot plugins | +-- Internal plugins (goroutines, DirectBridge runtime path) | +-- External plugins (managed processes, five-stage control over authenticated TLS) | +-- Northbound listeners +-- SSH CLI/editor, Web UI, and Looking Glass +-- REST, gRPC, gNMI, and MCP +-- Prometheus metrics and BMP routing telemetry ``` ## Key Design Choices | Principle | What it means | | --- | --- | | Wire-first | BGP messages are byte buffers, not parsed structs. Parsing is lazy via offset iterators. | | Buffer-first encoding | All wire writes go into pooled, bounded buffers via `WriteTo(buf, off) int`. No `append()` in encoding. | | Zero-copy forwarding | When source and destination peers share the same `ContextID` (negotiated capabilities hash), UPDATE bytes are forwarded unchanged. | | Pool-based dedup | Per-attribute-type memory pools with refcounted handles. ORIGIN has 3 values; dedup saves memory at scale. | | Registration pattern | Components and plugins register at startup via `init()`. Core discovers through registries, never imports directly. | | YANG-modeled everything | Config schemas, CLI dispatch, plugin registration, and RPC discovery all flow from YANG modules. | ## Key Wire Abstractions | Abstraction | Purpose | Location | | --- | --- | --- | | `WireUpdate` | Lazy-parsed BGP UPDATE: iterators over wire bytes, no intermediate structs | `internal/component/bgp/wireu/` | | `EncodingContext` | Negotiated capabilities that determine encoding (ASN4, ADD-PATH, extended next hop) | `internal/core/bgp/context/` | | `ContextID` | uint16 hash of capabilities. Same ID = forward wire bytes unchanged. | `internal/core/bgp/context/` | | `Pool` / `Handle` | Per-attribute-type pools with refcounted handles and incremental compaction | `internal/component/bgp/attrpool/` | | `DirectBridge` | Bypasses IPC serialization for internal plugins (direct function calls) | `pkg/plugin/rpc/` | ## Plugin Server / EventBus The Plugin Server is Ze's central runtime junction. It implements `ze.EventBus` for typed publishers and subscribers, owns the YANG-derived command and RPC dispatchers, coordinates plugin startup, and maintains subscription and event history state. In-process subscribers receive typed values directly; plugin process subscribers receive lazily encoded messages through the same fan-out. There is no separate standalone bus in the primary YANG runtime. Components avoid importing each other's implementation packages. Internal plugins bootstrap through `net.Pipe` and use `DirectBridge` for runtime calls; external plugins connect back over authenticated TLS. Route-producing plugins write selected paths into the shared Loc-RIB. `sysrib` performs system-wide arbitration, then publishes typed best-change events to FIB consumers. ## BGP Subsystem The BGP subsystem owns the protocol: TCP connections, FSM state machines, wire parsing, capability negotiation, and the reactor event loop. It produces structured events and consumes commands. It never imports plugin code. For a walk-through of the BGP state machine, see [bgp-fsm.md](https://github.com/ze-software/ze/blob/main/docs/bgp-fsm.md). ## Configuration JUNOS-like hierarchical syntax: `{}` blocks, `;` terminators, `#` comments. YANG-driven parsing. Three-level inheritance: BGP globals, group defaults, peer overrides. Configuration is stored in ZeFS (a blob store with commit/rollback) and managed through an interactive editor accessible over SSH. For the full config syntax reference, see [config-reference.md](https://github.com/ze-software/ze/blob/main/docs/config-reference.md). ## Plugin Architecture Most protocol and system features are registry-loaded plugins: RIB storage, route reflection, graceful restart, RPKI validation, NLRI encoding, FIB programming, firewall, traffic control, and more. PPPoE and L2TP are Engine-managed subsystems. Internal plugins start as goroutines, bootstrap over `net.Pipe`, and then use `DirectBridge` for structured runtime calls without serialization. External plugins are managed child processes controlled through the same five-stage protocol and runtime RPC over authenticated TLS. For the plugin architecture overview, see [plugin-overview.md](https://github.com/ze-software/ze/blob/main/docs/plugin-overview.md). For writing plugins, see [plugin-development/](https://github.com/ze-software/ze/tree/main/docs/plugin-development). ## Data Flow ``` Receive: Network -> WireUpdate -> Reactor -> EventDispatcher -> Plugins Announce: Text command -> ParseUpdate() -> WireUpdate -> Peer Forward: Cache lookup -> Egress filters -> Wire (zero-copy when ContextID matches) ``` The reactor fires a single `StructuredEvent` per received UPDATE. Forwarders (route server, route reflector) and state trackers (RIB plugin) both subscribe to the same dispatch but consume it differently: forwarders make per-peer forwarding decisions, state trackers update best-path state. ## FIB Pipeline Selected routes move through a shared route-decision pipeline: 1. **Protocol route producers** insert BGP best paths and OSPF or IS-IS SPF paths into the shared Loc-RIB 2. **System RIB** observes Loc-RIB changes and selects system-wide routes by administrative distance and recursive next-hop resolution 3. **FIB consumers** receive typed `system-rib/best-change` events from the Plugin Server/EventBus 4. **Kernel FIB** programs OS routes through netlink or route sockets; optional VPP FIB uses the GoVPP API ## Programs | Binary | Purpose | | --- | --- | | `ze` | Network OS: BGP, CLI, config, hub, interface, ExaBGP migration, plugin, schema, signal, completion | | `ze-chaos` | Chaos testing orchestrator: fault injection, scheduling | | `ze-perf` | Performance benchmarking: UPDATE throughput tracking | | `ze-analyze` | MRT/RIB analysis: attributes, communities, density, dump | | `ze-test` | Functional test runner: BGP, editor, peer, MCP, web, RPKI, managed | ## Source Layout | Area | Location | | --- | --- | | Components | `internal/component/` (api, bgp, cli, config, firewall, gnmi, iface, ike, l2tp, lg, mcp, pki, resolve, ssh, storage, telemetry, traffic, vpp, web, ...) | | BGP engine | `internal/component/bgp/` (reactor, FSM, wire, message, capability) | | Plugin implementations | `internal/plugins/` and `internal/component/bgp/plugins/` | | Plugin infrastructure | `internal/component/plugin/` (registry, process, hub, SDK) | | Programs | `cmd/ze/` (build tags: `ze_core`, `ze_test`, `ze_chaos`, `ze_perf`, `ze_analyze`) | | Public SDKs | `pkg/plugin/sdk/`, `pkg/plugin/rpc/`, `pkg/zefs/` | | Tests | `test/` (.ci files), `*_test.go` | ## Deeper Reading | Topic | Document | | --- | --- | | Full design document | [DESIGN.md](https://github.com/ze-software/ze/blob/main/docs/DESIGN.md) | | Canonical architecture reference | [architecture/core-design.md](https://github.com/ze-software/ze/blob/main/docs/architecture/core-design.md) | | System architecture (hub mode) | [architecture/system-architecture.md](https://ze-software.net/architecture/system-architecture/) | | Buffer-first architecture | [architecture/buffer-architecture.md](https://github.com/ze-software/ze/blob/main/docs/architecture/buffer-architecture.md) | | Pool architecture | [architecture/pool-architecture.md](https://github.com/ze-software/ze/blob/main/docs/architecture/pool-architecture.md) | | Wire format details | [architecture/wire/](https://github.com/ze-software/ze/tree/main/docs/architecture/wire) | | Hub architecture | [architecture/hub-architecture.md](https://github.com/ze-software/ze/blob/main/docs/architecture/hub-architecture.md) | | Config syntax | [config-reference.md](https://github.com/ze-software/ze/blob/main/docs/config-reference.md) | | BGP FSM | [bgp-fsm.md](https://github.com/ze-software/ze/blob/main/docs/bgp-fsm.md) | | Plugin architecture | [plugin-overview.md](https://github.com/ze-software/ze/blob/main/docs/plugin-overview.md) | | Feature inventory | [features.md](https://ze-software.net/reference/feature-status/) | --- ### Page: API Commands https://ze-software.net/architecture/api/commands/ # API Commands **Source:** ExaBGP `reactor/api/command/`, `reactor/api/dispatch/` **Purpose:** Document all API commands for compatibility --- ## Overview Ze uses verb-first command paths with JSON or text encoding. ### Transport Request Metadata REST and gRPC remain thin adapters over the shared API engine. `Execute` and `Stream` pass the caller's `context.Context` together with trusted auth metadata (`Username`, `RemoteAddr`) into the engine; hub wiring then builds `pluginserver.CommandContext` for dispatcher/accounting use. Request bodies and plugin RPC payloads do not carry identity fields. Caller identity is injected only by trusted transport wiring. ### ExaBGP Differences | Aspect | ExaBGP | Ze | |--------|--------|-------| | Syntax styles | v4 (action-first) and v6 (target-first) | Verb-first only | | Encoder | json or text (v4), json only (v6) | json or text | | Peer selectors | `*`, IP, filters (`[local-as ...]`) | `*`, IP, negated (`!IP`) | | Multi-session filters | Supported (draft) | Not supported | | Forward command | Not available | `send bgp <selector> cached <id>` for route reflection | <!-- source: internal/component/plugin/server/command.go -- Dispatcher.Dispatch, IsReadOnlyPath --> --- ## Command Verb Taxonomy Commands follow a **verb-first** convention: `<action> <module> [args...]`. The action verb determines the command's behavior; the module implements it. | Verb | Purpose | Examples | |------|---------|---------| | `show` | Read-only display (returns data, exits) | `show bgp peer <selector> detail`, `show warnings` | | `set` | Create or modify | `set bgp peer X ...` | | `create` | Add to the running daemon | `create bgp peer X asn 65001` | | `delete` | Remove | `delete bgp peer X` | | `update` | Route operations (announce, withdraw, refresh), firmware, prefix data | `update system firmware check`, `update bgp peer * prefix` | | `monitor` | Long-running auto-refreshing display | `monitor bgp` (TUI dashboard) | <!-- source: internal/component/cmd/show/doc.go -- show verb --> <!-- source: internal/component/cmd/set/doc.go -- set verb --> <!-- source: internal/component/cmd/delete/doc.go -- delete verb --> <!-- source: internal/component/cmd/update/doc.go -- update verb --> **Internal dispatch:** `<action> <module>` is dispatched as the module implementing the action. For example, `monitor bgp` is handled by the BGP module's monitor implementation. Legacy noun-first RPCs (`peer list`, `bgp summary`) remain for internal dispatch but user-facing commands use verb-first syntax. **Streaming vs polling:** `monitor` commands keep the display active and auto-refresh. `monitor event` streams live events line-by-line. `monitor bgp` polls summary data every second and renders a dashboard. Both use the `monitor` verb because they produce continuously-updating output. ## Command Categories | Category | Commands | |----------|----------| | Daemon | shutdown, reload, restart, status | | Session | ack, sync, reset, ping, bye | | System | help, version, api version | | Peer | list, detail, capabilities, statistics, set (add/save), delete, teardown, flush | | Announce | route, flow, vpls, eor, operational | | Withdraw | route, flow, vpls, watchdog | | RIB | routes, best, status, clear | | Log | levels, set (runtime log levels) | | Metrics | values, list (Prometheus metrics) | | Group | start, end (batching) | | Monitor | monitor bgp (TUI dashboard), monitor event (live event streaming), monitor system netlink (kernel events) | | Subscribe | request subscribe, request unsubscribe (event filtering) | | OSPF | show ospf neighbor, interface, database, route, border-routers, spf | | Reports | show warnings, show errors (cross-subsystem operational report bus) | <!-- source: internal/component/plugin/server/command.go -- AllBuiltinRPCs --> <!-- source: internal/plugins/ospf/register.go -- sdk.CommandDecl show ospf commands --> ### Dispatch keys are YANG paths The daemon registers each built-in handler under its **YANG command path**, derived from the tree (`LoadBuiltins`: `d.RegisterWithOptions(wireToPath[wireMethod], ...)`). The path — not the wire method — is the dispatch key. Moving a `ze:command` container in the YANG tree therefore renames the command and breaks anything that sends the old path. Operator commands are safe to migrate; commands a plugin sends by their bare path (over `dispatch-command` or an interactive plugin CLI session) are a wire break. Before a verb-first migration of a noun-first built-in, grep for senders. See `ai/rules/cli.md` "Migrating a Built-in Command's Path". <!-- source: internal/component/plugin/server/command.go -- LoadBuiltins, IsReadOnlyPath --> ### A parent command never swallows a registered child The daemon serves the LONGEST registered key that prefixes the input and hands the rest to that handler as arguments. A parent registered at a short path would therefore own the whole subtree below it, children another owner registered included. `show bgp` is a builtin key; `show bgp rpki status` is a PLUGIN name that `Dispatch` reaches only after the builtin match fails, so an unguarded match sends the rpki subtree to the summary handler, which reads its first argument as an address family. `matchBuiltinTokens` refuses its match when a LONGER prefix of the input is itself a registered command. It asks all three registries the dispatcher resolves from: the builtin keys, the plugin registry, and the subsystem handlers. The test is a registered PATH, never the presence of leftover tokens, because leftovers are how every argument-taking command works: `show bgp ipv4` still reaches its handler with the family. The client-side lookup carries the same rule, and the two are separate because they read different registries. Neither is derived from the other. <!-- source: internal/component/plugin/server/command.go -- matchBuiltinTokens, longerCommandPath, isCommandPath --> <!-- source: internal/component/command/registry/registry.go -- LookupLocal --> ### Offline fallback (read-only commands without a daemon) Some read-only `show` commands must work with **no daemon reachable** — `show crashes` (you inspect a crash precisely when the daemon has died) and `show host` (hardware inventory before the daemon is up). Their owner registers an in-process handler with `registry.RegisterOfflineFallback(path, handler)`. The CLI serves the command from the daemon when it is reachable and calls the fallback **only after a connection failure**, so the fallback never shadows the daemon. Because `cmdutil.RunCommand` rejects commands absent from the CLI binary's tree before reaching the daemon path, it makes an exception for paths that have a registered fallback, routing them through so the fallback is reachable. Output is identical to the daemon RPC (both read the same detection library / crash files), so online and offline results match. `show crashes` holds that identity to one call on each side: both build their rows from `crashlog.CrashListFields` and their readiness block from `crashes.Readiness`, so the answer cannot vary with the health of the box being diagnosed. <!-- source: internal/core/crashlog/list.go -- CrashListFields, the one row builder --> <!-- source: internal/plugins/crashes/readiness.go -- Readiness, the one readiness answer --> <!-- source: internal/component/command/registry/registry.go -- RegisterOfflineFallback, LookupOfflineFallback --> <!-- source: cmd/ze/internal/cmdutil/cmdutil.go -- RunCommand offline-fallback routing --> <!-- source: internal/component/cli/client/main.go -- runOfflineFallback (invoked on daemon-unreachable) --> ### Local-data commands (answered in the caller's own process) A local-data command never asks a daemon. Its handler reads a registry that `init()` filled in this process, so the answer exists before `main()` does. It registers with `cmdregistry.MustRegisterLocalData(path, handler, meta, command.RenderLocalAnswer)` and returns DATA, which is what lets `| json`, `| yaml` and `| table` be three renderings of one payload. The handler returns a payload AND an exit code, and the two are independent. The code carries the verdict, the payload carries the evidence, and `validate config` is the command that needs both: it answers the diagnostics of a configuration it rejects and exits 1. The answer therefore goes to stdout whatever the code is, because a payload on stderr is a payload no pipe operator can reach. A handler with nothing to say writes its reason to stderr itself and returns a nil payload, which prints nothing. The one local result stdout never sees is a pipe error, which is a diagnostic about the operator's own chain rather than an answer to their question. <!-- source: internal/component/cli/client/main.go -- emitLocalResult --> <!-- source: internal/component/command/registry/registry.go -- LocalDataHandler --> This differs from the offline fallback above: a fallback is a second answer for a command the daemon normally serves, tried only after the connection fails. A local-data command has no daemon side at all, registers no RPC, and therefore owes no `wire-methods.snapshot` row. `internal/component/plugin/register.go` serves one of them, and it is the template: `show plugin list` answers which plugins this binary carries and what each plugin's own `init()` recorded about its setup. It is owned by the package that owns the registry it reads, so removing the plugin host removes the command with it. <!-- source: internal/component/plugin/register.go -- dataPlugins, pluginRows --> <!-- source: internal/component/command/local_data.go -- RenderLocalAnswer --> --- ## Operational Report Bus (ze-show:warnings, ze-show:errors) The report bus is a single in-process place for Ze subsystems to push operator-visible issues. Any subsystem (BGP, config, interface, plugins) can call `report.RaiseWarning` / `report.RaiseError` / `report.ClearWarning`, and operators query the aggregate via two RPCs. <!-- source: internal/core/report/report.go -- package godoc --> ### Severity contract | Severity | Lifecycle | Storage | Cleared by | |----------|-----------|---------|------------| | `warning` | State-based. Condition is currently problematic. | Deduped map on `(Source, Code, Subject)`. Bounded by `warningCap` (default 1024, max 10000). Oldest-by-Updated evicted at cap. | `ClearWarning(source, code, subject)`, `ClearSource(source)`, or implicit eviction. | | `error` | Event-based. Something already happened. | Ring buffer of `errorCap` events (default 256, max 10000). Oldest evicted on overflow. | Never; ring buffer aging only. | Producers MUST pick the right severity. The bus does not auto-promote. "Did anything actually fail or behave unexpectedly?" Yes -> error. No, but it might soon -> warning. <!-- source: internal/core/report/report.go -- Severity, RaiseWarning, RaiseError, ClearWarning, ClearSource --> ### Push API (for subsystems) Subsystems import `internal/core/report` and call: | Function | Purpose | |----------|---------| | `RaiseWarning(source, code, subject, message string, detail ...map[string]any)` | Add or refresh an active warning. Deduplicated. | | `ClearWarning(source, code, subject string)` | Remove an active warning. No-op if missing. | | `ClearSource(source string)` | Remove all active warnings for one source (shutdown cleanup). | | `RaiseError(source, code, subject, message string, detail ...map[string]any)` | Append an error event to the ring buffer. No dedup. | | `Warnings() []Issue` | Snapshot of all active warnings, most-recently-updated first. Consumed by ze-show:warnings handler. | | `Errors(limit int) []Issue` | Recent N error events, newest first. limit 0 or negative returns all retained. Consumed by ze-show:errors handler. | Empty or oversized fields (Source/Code > 64 bytes, Subject > 256, Message > 1024, Detail > 16 keys) are rejected at the boundary with a debug log, protecting the bus from buggy or malicious producers. <!-- source: internal/core/report/report.go -- validFields, RaiseWarning, RaiseError, ClearWarning, ClearSource, Warnings, Errors --> ### Query RPCs | WireMethod | Handler | Response shape | |------------|---------|----------------| | `ze-show:warnings` | `handleShowWarnings` in `internal/component/cmd/show/show.go` | `{"warnings": [Issue, ...], "count": N}` | | `ze-show:errors` | `handleShowErrors` in `internal/component/cmd/show/show.go` | `{"errors": [Issue, ...], "count": N}` | | `ze-show:traffic` | `handleShowTraffic` in `internal/component/traffic/cmd/traffic.go` | `{"interfaces": [...], "count": N}` or single interface detail | | `ze-show:static` | `forwardShowStatic` in `internal/plugins/static/cmd_show.go` | JSON array of configured static routes (proxy to static plugin) | | `ze-show:policy-routes` | `forwardShowPolicyRoutes` in `internal/plugins/policyroute/cmd_show.go` | JSON array of PBR policy routes (proxy to policyroute plugin) | | `ze-show:policy-chain` | `handleShowPolicyChain` in `internal/component/bgp/plugins/cmd/policy/handler.go` | `{"chains": [{"peer": "...", "name": "...", "import": [{"name": "...", "canonical": "..."}], "export": [...]}]}` — per-peer effective filter chains, plain name plus canonical ref | | `ze-show:policy-test` | `handleShowPolicyTest` in `internal/component/bgp/plugins/cmd/policy/handler.go` | `{"direction": "...", "peer": "...", "action": "accept\|reject\|modify", "trace": [PolicyTraceEntry], "text-before": "...", "text-after": "...", "changed-attrs": [...], "wire-changes": ["AS4_PATH suppressed", ...]}` — read-only policy dry-run, no forwarding or mutation | | `ze-show:bmp-sessions` | `forwardShowBMPSessions` in `internal/component/bgp/plugins/bmp/cmd_show.go` | JSON array of BMP receiver sessions (proxy to BMP plugin) | | `ze-show:bmp-peers` | `forwardShowBMPPeers` in `internal/component/bgp/plugins/bmp/cmd_show.go` | JSON array of BMP monitored peers (proxy to BMP plugin) | | `ze-show:bmp-collectors` | `forwardShowBMPCollectors` in `internal/component/bgp/plugins/bmp/cmd_show.go` | JSON array of BMP sender collectors (proxy to BMP plugin) | | `ze-show:bmp-rib` | `forwardShowBMPRib` in `internal/component/bgp/plugins/bmp/cmd_show.go` | BMP-monitored routes (proxy to BMP plugin, dispatches to RIB) | | `ze-show:rr-status` | `forwardShowRRStatus` in `internal/component/bgp/plugins/rr/cmd_show.go` | `{"running": true}` (proxy to RR plugin) | | `ze-show:rr-peers` | `forwardShowRRPeers` in `internal/component/bgp/plugins/rr/cmd_show.go` | JSON array of RR peer states (proxy to RR plugin) | | `ze-show:reject-asn` | `forwardShowRejectASN` in `internal/component/bgp/plugins/filter_path_asn/register_command.go` | `{"lists": [{"name": "...", "import-peers": N, "export-peers": N, "entries": [{"asn": N, "positions": ["transit", "origin"], "network": "..."}], "patterns": ["..."]}]}` (proxy to the reject-asn filter plugin). `network` is written for every ASN and is EMPTY for one the curated table does not hold | | `ze-show:reject-asn-name` (selector: `name`) | `forwardShowRejectASNName` in the same file | One list record, the same shape as a row of `lists`. The value after the `name` keyword arrives as a SELECTOR rather than a positional, because `name` is both the last token of the path and the leaf under it, so the forwarder reads `ctx.Selector("name")` | | `ze-show:reject-asn-known-transit-free` | `forwardShowRejectASNTransitFree` in the same file | `{"curated": "YYYY-MM-DD", "sources": [...], "networks": [{"asn": N, "name": "...", "contested": bool}], "block": ["# ...", "indirect [ ... ];"]}`. `block` is an array of config LINES an operator pastes | | `ze-show:system-sockets` | `handleShowSystemSockets` in `sockets_linux.go` | `{"sockets": [...], "count": N}` (Linux only) | | `ze-show:system-kernel-log` | `handleShowSystemKernelLog` in `kernel_log_linux.go` | `{"entries": [...], "count": N}` (Linux only) | | `ze-show:system-goroutines` | `handleShowSystemGoroutines` in `goroutines.go` | `{"total": N, "by-state": {...}, "mode": "..."}` | | `ze-show:tcp-check` | `HandleTCPCheck` in `internal/plugins/diag/cmd/tcp_check.go` | `{"host": "...", "port": N, "result": "...", "latency-ms": N}` | | `ze-show:traceroute` | `handleTraceroute` in `traceroute.go` | `{"target": "...", "hops": [{"hop": N, "addr": "...", "rtt-ms": N, "ttl": N}, ...]}` | | `ze-show:capture-interface` | `handleCaptureInterface` in `capture_interface_linux.go` | pcap: `{"format": "pcap", "packets": N, "pcap": "base64...", "snap-len": N}`; text: `{"format": "text", "packets": N, "lines": [...]}` (Linux only) | | `ze-show:system-file-descriptors` | `handleShowSystemFD` in `fd_linux.go` | `{"total": N, "by-type": {...}, "soft-limit": N, "hard-limit": N}` (Linux only) | | `ze-show:dns-lookup` | `handleDNSLookup` in `internal/component/resolve/cmd/show_dns.go` | `{"name": "...", "type": "...", "records": [...], "query-time-ms": N}` | | `ze-show:dns-cache-stats` | `handleDNSCacheStats` in `internal/component/resolve/cmd/show_dns.go` | `{"entries": N, "capacity": N, "hits": N, "misses": N, "hit-rate": N, "miss-rate": N, "evictions": N, "expired": N}` | | `ze-show:dns-cache-list` | `handleDNSCacheList` in `internal/component/resolve/cmd/show_dns.go` | `{"entries": [...], "count": N}` | | `ze-show:dns-cache-record` | `handleDNSCacheRecord` in `internal/component/resolve/cmd/show_dns.go` | `{"entries": [...], "count": N, "filter": "name"}` | | `ze-clear:dns-cache` | `handleClearDNSCache` in `internal/component/resolve/cmd/dns.go` | `{"action": "clear-all"}` | | `ze-clear:dns-cache-stats` | `handleClearDNSCacheStats` in `internal/component/resolve/cmd/dns.go` | `{"action": "reset-stats"}` | | `ze-clear:dns-cache-record` | `handleClearDNSCacheRecord` in `internal/component/resolve/cmd/dns.go` | `{"action": "delete-entry", "name": "...", "removed": N}` or `{"action": "delete-entry", "name": "...", "type": "...", "found": bool}` | | `ze-show:resolve-rir` (args: `<asn>`) | `handleRIRASN` in `internal/component/resolve/cmd/rir.go` | `{"asn": N, "registry": "ARIN", "whois": "whois.arin.net", "range-start": N, "range-end": N}`. Two distinct errors: `AS<n> is in no delegated range` for a table that was read, `RIR delegation table unreadable: ...` for one that was not | | `ze-update:resolve-rir` (no args) | `handleRIRRefresh` in `internal/component/resolve/cmd/rir.go` | `{"key": "meta/rir/delegation", "ranges": N, "generated": "YYYY-MM-DD"}`. `ranges` is how many ranges the stored table holds and `generated` is the date it stored. All or nothing: a fetch that failed, a parse that refused, a run that read no ASN record, and a write that stored nothing each answer an error and change no stored table | | `ze-show:system-profile` | `handleShowSystemProfile` in `profile.go` | `{"type": "...", "format": "pprof-base64", "data": "..."}` | | `ze-show:system-memory-map` | `handleShowSystemMemoryMap` in `memory_map_linux.go` | `{"vm-rss-kb": N, "vm-size-kb": N, ...}` (Linux only) | | `ze-show:system-update` | `handleShowSystemUpdate` in `internal/plugins/update-cmd/cmd/show.go` | `{"backend": "ze-self-update"\|"gokrazy-ab", "running-version": "...", "remote-version": "...", "update-available": bool, "status": "...", "download-status": "...", "staged-version": "...", "gokrazy-reachable": bool, "gokrazy-features": [...]}` | | `ze-show:system-update-history` | `handleShowSystemUpdateHistory` in `internal/plugins/update-cmd/cmd/show.go` | `{"history": [{"timestamp": "...", "from": "...", "to": "...", "result": "..."}], "count": N}` | | `ze-update:system-firmware-check` | `handleFirmwareCheck` in `firmware.go` | `{"running-version": "...", "update-available": bool, ...}` or on gokrazy `{"backend":"gokrazy-ab", "status":"unsupported", "message":"updates managed by gokrazy"}` | | `ze-update:system-firmware-download` | `handleFirmwareDownload` in `firmware.go` | `{"downloaded-version": "...", "status": "complete"}` | | `ze-update:system-firmware-apply` | `handleFirmwareApply` in `firmware.go` | `{"applied-version": "...", "status": "restarting"}` | | `ze-update:system-firmware-restart` | `handleFirmwareRestart` in `firmware.go` | `{"status": "restarting"}` | | `ze-update:system-firmware-rollback` | `handleFirmwareRollback` in `firmware.go` | `{"status": "rolling back"}` | | `ze-show:interface` (no args) | `handleShowInterface` in `internal/component/iface/cmd/show_interface.go` | JSON array of `InterfaceInfo`. A stray token is refused with the usage text: every subcommand below has its own wire method | | `ze-show:interface-brief` | `handleShowInterfaceBrief` in `internal/component/iface/cmd/show_interface.go` | `{"interfaces": [{name, state, mtu, address?}], "count": N}` | | `ze-show:interface-type` (args: `<type>`) | `handleShowInterfaceType` in `internal/component/iface/cmd/show_interface.go` | `{"interfaces": [InterfaceInfo]}` for that type. An unmatched type is refused, and the refusal lists the types the running set has | | `ze-show:interface-errors` | `handleShowInterfaceErrors` in `internal/component/iface/cmd/show_interface.go` | `{"interfaces": [{name, rx-errors, rx-dropped, tx-errors, tx-dropped}]}`, only the links with a non-zero counter | | `ze-show:interface-rate` (args: `[<name>]`) | `handleShowInterfaceRateCmd` in `internal/component/iface/cmd/show_interface.go` | JSON array of `InterfaceRate` (all) or single object (named); fields: `name`, `rx-bps`, `tx-bps`, `rx-pps`, `tx-pps`, `stats` | | `ze-monitor:interface-rate` | `streamInterfaceRate` in `internal/component/iface/cmd/interface_rate.go` | Streaming JSON lines (1/s); optional `<name>` filter | | `ze-show:storage-smart` | `handleShowStorageSmart` in `internal/component/storage/show.go` | JSON array of per-device objects: `name`, `transport`, `healthy`, `temp-celsius`, `power-on-hours`, `error-count`, `percent-used` (NVMe), `available-spare` (NVMe), `smart-enabled`, `last-checked`, `last-short-test`, `last-long-test`. Returns error if SMART management not configured. | | `ze-show:flow-export` (args: `[<collector>]`) | `handleShowFlowExport` in `internal/plugins/flowexport/cmd_show.go` | No arg: JSON array of per-collector objects. Named: single collector object, or error `collector not found: <name>`. Per-collector fields: `name`, `address`, `port`, `protocol`, `datagrams-sent`, `bytes-sent`, `errors`, `sequence`, `last-export-time` (Unix seconds, omitted before first poll). When unconfigured: `{"status": "not-configured"}`. Backed by `flowexport.Exporter.Status()`. | | `ze-show:traffic-stat` (args: `[name <interface>]`) | `handleShowTraffic` in `internal/component/trafficstat/cmd/traffic.go` | One-shot aggregated snapshot: `{"at": "RFC3339", "severity": "normal\|caution\|danger", "degraded": bool, "interfaces": [{name, rx-bps, tx-bps, rx-pps, tx-pps}], "top-source-ips": [{address, bps}], "top-dest-ips": [{address, bps}], "top-ports": [{port, service, proto, bps, amplification?}], "protocol-mix": [{proto, name, bps, percent}], "history": [float64]}`. Optional `name <interface>` filters the interfaces array. When no collector data: `"degraded": true` with interface rates only. | | `ze-monitor:traffic-stat` (args: `[name <interface>]`) | `streamTraffic` in `internal/component/trafficstat/cmd/traffic.go` | Streaming JSON lines (1/s) with the same shape as `ze-show:traffic-stat`. Attaches as a consumer on connect, detaches on disconnect (lazy lifecycle). Also registered as a `MonitorProvider` for full-screen TUI rendering via `createTrafficMonitorSession` in `cmd/render.go`. | | `ze-show:traffic-feature` (args: `[name <address>]`) | `handleShowTrafficFeature` in `internal/component/trafficfeature/cmd/traffic_feature.go` | Neutral per-source feature snapshot: `{"degraded": bool, "top-source-ips": [{address, fan-out, out-in-ratio, port-entropy, new-peer, rare-port, beaconing}]}`. `out-in-ratio` is the string `"inf"` when a source has no inbound bytes (else a float). Optional `name <address>` filters to one source. Facts only (no verdict); the anomaly detection family applies judgment. | | `ze-show:anomaly` (no args) | `handleShowAnomaly` in `internal/plugins/anomaly/detect/show.go` | Recent behavioral anomaly incidents (report-only): `{"enabled": bool, "incidents": [{entity, entity-kind, cohort, score, severity, at, fired-features: [{name, z}]}]}`. `entity-kind` is `source`, `dest` or `port`. A source or dest row names its subject in `entity` as a prefix; a port row carries `port` and `proto` as two more fields and renders `entity` as `proto/port`, because a port is not an address. Bounded recent-incident ring; empty until an entity's correlated deviation confirms. `enabled` is false when the detector is not running. The `anomaly/shape` responder consumes the underlying `anomaly-detect` events; this command is the read-only view. | | `ze-show:anomaly-observe` (no args) | `handleShowAnomalyObserve` in `internal/plugins/anomaly/observe/show.go` | Behavioral anomaly incident LIFECYCLE, newest first: `{"enabled": bool, "active-count": N, "incidents": [{id, interface, entity, cohort, fired-features: [{name, z}], score, severity, start-time, end-time, active}]}`. `end-time` is omitted while `active` is true. It is set when the incident clears, or when the stale timeout finalizes it. A finished incident's duration is readable here and nowhere else. `enabled` is false when the plugin is not running. | | `ze-show:anomaly-shape` (no args) | `handleShowAnomalyShape` in `internal/plugins/anomaly/shape/show.go` | Shadow-first responder status: `{"enabled": bool, "mode": "shadow"\|"armed", "action": "limit"\|"drop", "kill-switch": bool, "armed-count": N, "armed": [source, ...]}`. `enabled` is false before configuration. In shadow mode (default) nothing is installed; armed sources carry a live per-source firewall action with a timed auto-revert. | | `ze-show:traffic-usage` (args: `[name <interface>]`) | `handleShowTrafficUsage` in `internal/plugins/trafficusage/show.go` | No arg: JSON array of per-interface objects. `name <interface>`: single interface object. Per-interface fields: `ingress-ports`, `egress-ports`, `map-entries`, and (only when `track-ip` is enabled) `ingress-ips`, `egress-ips`. Bad args: error `usage: show traffic usage [name <interface>]`. When unconfigured: `{"status": "not-configured"}`. | | `ze-show:pki-certificates` | `handleShowPKICertificates` in `internal/component/pki/show.go` | `{"certificates": [CertSummary, ...], "count": N}` | | `ze-show:pki-certificate` (args: `<name> [pem \| bundle pem \| fingerprint [algo]]`) | `handleShowPKICertificate` in `internal/component/pki/show.go` | No sub-command: full detail map. `pem`: `{"pem": "..."}`. `bundle pem`: `{"pem": "cert+key"}`. `fingerprint`: `{"name": "...", "algorithm": "sha256", "fingerprint": "aa:bb:..."}`. | Both warnings/errors handlers accept optional `source <name>` filter and errors accepts `count <N>` limit. Return a non-nil empty slice when empty. <!-- source: internal/component/cmd/show/show.go -- handleShowWarnings, handleShowErrors --> <!-- source: internal/plugins/static/cmd_show.go -- ForwardToPlugin proxy --> <!-- source: internal/plugins/policyroute/cmd_show.go -- ForwardToPlugin proxy --> <!-- source: internal/component/bgp/plugins/bmp/cmd_show.go -- ForwardToPlugin proxy --> <!-- source: internal/component/bgp/plugins/rr/cmd_show.go -- ForwardToPlugin proxy --> <!-- source: internal/component/cmd/show/yang/ze-cli-show-cmd.yang -- top-level warnings / errors containers --> ### Issue JSON shape | Field | JSON type | Notes | |-------|-----------|-------| | `source` | string | Subsystem name (lowercase, short: `bgp`, `config`, `iface`, ...) | | `code` | string | Kebab-case identifier (`prefix-threshold`, `notification-sent`, ...) | | `severity` | string | `"warning"` or `"error"` (MarshalJSON converts the internal uint8 enum to the label) | | `subject` | string | What the issue is about (peer address, transaction id, file path) | | `message` | string | Human-readable one-liner | | `detail` | object | Optional. Omitted via `omitempty` when nil or empty. Keys and values are producer-defined. | | `raised` | RFC 3339 time | First appearance | | `updated` | RFC 3339 time | Most recent raise (warnings advance; errors equal raised) | <!-- source: internal/core/report/report.go -- Issue struct with json tags, Severity.MarshalJSON --> ### Day-one BGP producers | Severity | Code | Subject | Detail keys | Source function | |----------|------|---------|-------------|-----------------| | warning | `prefix-threshold` | `<peerAddr>/<afi>/<safi>` | `family`, `count`, `warning`, `maximum` | `raisePrefixThreshold` in `reactor/session_prefix.go`, called from `applyPrefixCheck` | | warning | `prefix-stale` | peer address | `updated` | `RaisePrefixStale` in `reactor/session_prefix.go`, called from `reactor_peers.go` at peer add (and clear at remove) | | error | `notification-sent` | peer address | `code`, `subcode`, `direction=sent` | `raiseNotificationError("sent", ...)` in `reactor/session_prefix.go`, called from `Peer.IncrNotificationSent` | | error | `notification-received` | peer address | `code`, `subcode`, `direction=received` | `raiseNotificationError("received", ...)` in `reactor/session_prefix.go`, called from `Peer.IncrNotificationReceived` | | error | `session-dropped` | peer address | `reason` | `raiseSessionDropped` in `reactor/session_prefix.go`, called from `peer_run.go` FSM Established->Idle branch when no notification was exchanged | The `session-dropped` producer is suppressed when a NOTIFICATION was sent or received during the same session lifecycle: the operator already sees that event in `show errors` so a duplicate session-dropped would be noise. Tracking is via `Peer.notificationExchanged atomic.Bool`, reset at the start of each `runOnce` iteration. <!-- source: internal/component/bgp/reactor/session_prefix.go -- reportCode* constants, raise* helpers --> <!-- source: internal/component/bgp/reactor/peer.go -- Peer.notificationExchanged --> <!-- source: internal/component/bgp/reactor/peer_run.go -- FSM Established->Idle branch + runOnce reset --> ### Login banner integration The Ze CLI login banner reads from the same report bus (filtered by source `bgp`). One active warning displays the detail line; multiple warnings collapse to a count line pointing at `show warnings`. This is the single source of truth for "what's wrong right now" across the connect path and the show command. <!-- source: internal/component/bgp/config/loader.go -- collectPrefixWarnings --> ### Capacity limits and env vars | Env var | Default | Maximum | Registered by | |---------|---------|---------|---------------| | `ze.report.warnings.max` | 1024 | 10000 | `env.MustRegister` in `internal/core/report/report.go` | | `ze.report.errors.max` | 256 | 10000 | same | Operator values exceeding the maximum are clamped and logged at warn level. Zero or negative values fall back to the default. This prevents an env-var typo from causing memory exhaustion. <!-- source: internal/core/report/report.go -- newStore, maxWarningCap, maxErrorCap --> --- ## Target-First Syntax ### Process Lifecycle Commands ``` request shutdown # Graceful shutdown request reboot # Graceful shutdown + OS reboot (requires root on Linux) request reload # Reload configuration request halt # Goroutine dump + immediate exit show status # Get process status ``` ### Session Commands ``` plugin session ready # Signal plugin init complete plugin session ping # Health check plugin session bye # Disconnect ``` <!-- source: internal/core/ipc/yang/ze-plugin-api.yang -- session RPCs --> ### BGP Plugin Configuration ``` bgp plugin encoding json # Set event encoding to JSON (default) bgp plugin encoding text # Set event encoding to human-readable text bgp plugin format hex # Wire bytes as hex string bgp plugin format base64 # Wire bytes as base64 bgp plugin format parsed # Decoded fields only (default) bgp plugin format full # Both parsed AND wire bytes bgp plugin ack sync # Wait for wire transmission bgp plugin ack async # Return immediately (default) ``` <!-- source: internal/component/bgp/yang/ze-bgp-api.yang -- plugin-encoding, plugin-format, plugin-ack RPCs --> ### Event Subscription Commands A plugin declares what it CAN handle. The peer's configuration decides what it GETS. A peer-scoped event is delivered to a process when both halves name it: the process subscribed to the event, and the peer's `attach process <name>` block grants that type in that direction. A peer that attaches no block for a process feeds it nothing. ``` request subscribe <namespace> event <type> [direction received|sent|both] request subscribe peer <selector> event <type> [direction ...] request subscribe plugin <name> <namespace> event <type> [direction ...] request unsubscribe <namespace> event <type> [direction received|sent|both] ``` **Precedence.** The configuration is durable receive authorization and is rebuilt on every config apply. A `request subscribe` typed at a running daemon is a live capability override: it can add a type the process did not declare at startup only where the peer's block grants that type. It cannot widen the configured grant, and the next config apply discards it. <!-- source: internal/component/plugin/server/delivery_graph.go -- (*Server).PeerScopedProcs, (*Server).DiscardRuntimeSubscriptions --> At plugin ready, and again after every apply, ze reports each peer, process and event type the two halves disagree about. Two lines an operator sees: ``` event delivery: the config grants an event the plugin never declared peer=... process=... granted=update-received event delivery: the plugin declared an event the peer does not grant it peer=... process=... declared=state ``` **Namespaces:** - `bgp` - BGP protocol events - `rib` - RIB events (cache, route changes) **BGP event types:** | Event | Has Direction | Description | |-------|---------------|-------------| | `update` | ✅ | UPDATE message | | `open` | ✅ | OPEN message | | `notification` | ✅ | NOTIFICATION message | | `keepalive` | ✅ | KEEPALIVE message | | `refresh` | ✅ | ROUTE-REFRESH message | | `state` | ❌ | Peer state change (up/down) | | `negotiated` | ❌ | Capability negotiation complete | | `rpki` | ❌ | RPKI validation result (from bgp-rpki plugin) | | `update-rpki` | ✅ | UPDATE merged with RPKI validation (from bgp-rpki-decorator) | Plugins may register additional event types via `Registration.EventTypes`. These are validated at runtime against the dynamic registry. <!-- source: internal/component/plugin/registry/registry.go -- Registration.EventTypes --> **RIB event types:** | Event | Description | |-------|-------------| | `cache` | Cache entry events | | `route` | Route change events | **Examples:** ``` request subscribe bgp event update # All peers, both directions request subscribe bgp event update direction received # Received only request subscribe peer upstream1 event update # Specific peer request subscribe peer * event state # All peers, state changes request subscribe peer !upstream1 event update direction sent # Exclude one peer request subscribe rib event route # RIB route events ``` ### Monitor Commands Stream live events or display live dashboards. All monitor commands follow verb-first syntax: `monitor <module> [args...]`. ``` monitor event # All events, all peers monitor event peer <addr> # Filter by peer address monitor event peer * # Explicit all peers monitor event include <type>,<type> # Only listed event types monitor event exclude <type>,<type> # All types except listed monitor event direction received # Received events only monitor event direction sent # Sent events only monitor event include update peer <addr> direction received # Combined filters monitor bgp # Live peer dashboard (TUI) ``` | Keyword | Values | Default | |---------|--------|---------| | `peer` | IP address, peer name, `!exclusion`, or `*` | `*` (all peers) | | `include` | Comma-separated event types | All types (mutually exclusive with `exclude`) | | `exclude` | Comma-separated event types | None (mutually exclusive with `include`) | | `direction` | `received`, `sent` | Both directions | Keywords may appear in any order. `include` and `exclude` are mutually exclusive. Event types span all namespaces: BGP (update, open, notification, keepalive, refresh, state, negotiated, eor, congested, resumed, rpki) and RIB (cache, route). Types are validated at parse time. <!-- source: internal/component/plugin/server/event_monitor.go -- ParseEventMonitorArgs --> Wire method: `ze-event:monitor`. Supports pipe operators: `| json`, `| table`, `| match`. <!-- source: internal/component/plugin/server/monitor.go -- MonitorManager --> **Note:** `monitor bgp` is the live peer dashboard (TUI only). `monitor event` streams live events (SSH exec or TUI). `monitor system netlink` streams kernel netlink events (SSH exec or TUI, Linux only). #### Netlink Monitor Stream kernel route, link, and address change events as one JSON line per event. Replaces `ip monitor` on gokrazy appliances. ``` monitor system netlink # All netlink groups (route + link + address) monitor system netlink route # Route changes only monitor system netlink link # Link state changes only monitor system netlink address # Address changes only monitor system netlink all # Explicit all (same as no argument) ``` Wire method: `ze-monitor:system-netlink`. Linux only; returns "not available on this platform" on other OSes. <!-- source: internal/component/iface/cmd/monitor_netlink_linux.go -- streamNetlinkMonitor --> <!-- source: internal/component/cli/model_dashboard.go -- isDashboardCommand --> ### System Commands ``` system help # Show help (uses dispatcher, includes plugin commands) system version software # Show Ze version system version api # Show IPC protocol version system subsystem list # List available subsystems system command list # List all commands (builtin + plugin) system command list verbose # List with source (builtin/process name) system command help "<name>" # Show command details system command complete "<partial>" # Complete command names system command complete "<cmd>" args [<completed>...] "<partial>" # Arg completion ``` <!-- source: internal/core/ipc/yang/ze-system-api.yang -- system RPCs --> ### Process Lifecycle Commands ``` request shutdown # Gracefully shutdown request reboot # Gracefully shutdown then reboot the system show status # Show process status request reload # Reload the configuration show reload-status # Show how many config reloads have been processed ``` #### show reload-status (the reload fence) Returns the reload generation counter as JSON: ```json {"generation": 3, "last-outcome": "applied", "last-reload-at": "2026-07-16T09:12:44Z"} ``` | Field | Meaning | |-------|---------| | `generation` | Reloads PROCESSED since daemon start. Starts at 0. | | `last-outcome` | `applied`, `failed`, or `none` before the first reload. | | `last-reload-at` | RFC3339 UTC completion time; empty string before the first reload. | `generation` advances on **every** processed reload, including one that rejected a change or changed nothing. That is what makes it a fence rather than a statistic: a reload that rejects a change (l2tp refusing a listener rebind, for example) leaves no other observable trace, so an observer wanting to assert "the reload ran and correctly left this alone" has nothing else to wait on. It reads `generation`, triggers the reload, polls until the value advances, then asserts. Waiting on a timer instead makes the assertion pass vacuously against a reload that had not started. A reload refused with `ErrReloadInProgress` does not advance the counter: it was queued, not processed, and the replay advances it. The counter is observational only; nothing reads it to make a decision, so it cannot change what a reload accepts or rejects. <!-- source: internal/component/plugin/server/reload_generation.go -- counter state, outcome vocabulary --> <!-- source: cmd/ze/hub/main_reload.go -- doReload, the increment site, after engine.Reload --> <!-- source: internal/component/cmd/show/reload_status.go -- ze-show:reload-status handler + JSON shape --> ### Peer Commands ``` show bgp peer list # List all peers show bgp peer <selector> detail # Show specific peer detail show bgp peer <selector> capabilities # Show specific peer capabilities show bgp peer <selector> statistics # Show specific peer statistics show bgp peer <selector> history # Show FSM transition history request peer <selector> teardown [<cease-subcode>] # Disconnect peer create bgp peer <address> asn <asn> [...] # Add a peer to the running daemon delete bgp peer <name> # Remove dynamic peer request peer <sel> flush # Wait for forward pool to drain (barrier) ``` <!-- source: internal/component/bgp/yang/ze-bgp-api.yang -- peer RPCs --> ### Cache Commands (Ze) > **Design history:** summary 148 was retired on 2026-08-01 and was not carried > into `plan/learned/DESIGN-HISTORY.md`. That file's header gives the > git-recovery route. What survives of the cache pattern is in its "BGP engine: > wire encoding and RIB" > Load-bearing invariants: cache `Ack` and `Retain` > are independent refcount axes, and engine cache ack is cumulative. ``` send bgp <sel> cached <id> # Forward cached UPDATE to peers request cache retain <id> # Prevent eviction request cache release <id> # Allow eviction (reset TTL) request cache expire <id> # Remove immediately show cache # List cached message IDs # Batch variants (comma-separated IDs, max 1000): send bgp <sel> cached <id1>,<id2>,...,<idN> # Batch forward request cache release <id1>,<id2>,...,<idN> # Batch release ``` The cache commands enable route reflection via API: 1. Received UPDATEs are assigned a unique msg-id (per-UPDATE, not per-NLRI) 2. API outputs UPDATE info with msg-id 3. External process decides routing 4. The `send bgp <sel> cached <id>` command references msg-id (zero-copy when contexts match) 5. Cache entries expire after configurable TTL (default 60s) unless retained <!-- source: internal/component/bgp/reactor/reactor.go -- cache forward --> #### Fast-path typed SDK (rs-fastpath-3) The text-RPC `send bgp <sel> cached <id>` path tokenises, parses, and walks the command registry on every call. Plugins that forward many cached UPDATEs per second (route server, future route reflector) use a typed SDK pair instead: ```go Plugin.ForwardCached(ctx, ids []uint64, destinations []netip.AddrPort) error Plugin.ReleaseCached(ctx, ids []uint64) error ``` Both methods round-trip via `DirectBridge` when the plugin is in-process (zero socket I/O, no tokenisation, no command-registry lookup) and fall back to the `ze-plugin-engine:forward-cached` / `:release-cached` methods over the newline-framed plugin RPC connection when the plugin is out-of-process. The engine handler is `reactorAPIAdapter.ForwardUpdatesDirect` / `ReleaseUpdates`. Destinations are peer addresses (`netip.AddrPort`). Port-0 entries match any peer instance with the same address. Cap: 4096 destinations per call (override via `ze.fwd.dest.cap`); exceeding the cap is an explicit error, not silent truncation. Empty destination list returns an error too, guarding against an accidental wildcard broadcast — use `ReleaseCached` to ack without forwarding. Per-source ordering, egress filter chains, AS-PATH prepend, next-hop policy, replay-on-new-peer, and every other forwarding invariant are preserved. Only the transport differs from the text-RPC path. <!-- source: pkg/plugin/sdk/sdk_engine.go -- Plugin.ForwardCached, Plugin.ReleaseCached --> <!-- source: pkg/plugin/rpc/bridge.go -- DirectBridge.ForwardCached, SetForwardCached --> <!-- source: internal/component/bgp/reactor/reactor_api_forward_batch.go -- ForwardUpdatesDirect, ReleaseUpdates, maxForwardDestinations --> ### Log Commands (Ze) ``` show log levels # Show all subsystem log levels (JSON map) request log level <logger> <level> # Change subsystem log level at runtime ``` Levels: `debug`, `info`, `warn`, `err`. Changes take effect immediately via `slog.LevelVar` atomic swap. Only loggers created via `slogutil.Logger()` or `slogutil.LazyLogger()` (non-disabled) are shown and modifiable. <!-- source: internal/plugins/log/yang/ze-log-cmd.yang -- log command YANG --> ### Metrics Commands (Ze) ``` show metrics values # Dump Prometheus text format output show metrics list # List metric names only (no values) show metrics pool # Per-attribute pool occupancy and dedup rates ``` Requires telemetry to be enabled in config (`telemetry { prometheus { ... } }`). Returns error if metrics registry is not available. `show metrics pool` returns 13 per-attribute pools (Origin, AS-Path, LocalPref, MED, NextHop, Communities, LargeCommunities, ExtCommunities, ClusterList, OriginatorID, AtomicAggregate, Aggregator, OtherAttrs) with live/dead slots, bytes, intern count, dedup hit rate. <!-- source: internal/component/cmd/metrics/yang/ -- ze-bgp-cmd-metrics-api.yang --> ### Peer Selectors ``` peer * # All peers peer upstream1 # Specific peer by name peer !upstream1 # All peers EXCEPT this one (for route reflection) ``` <!-- source: internal/core/selector/selector.go -- Selector --> The `!<ip>` negated selector is useful for route reflection: ``` # Forward update to all peers except the source send bgp !upstream1 cached 12345 ``` > **Note:** Filter selectors (`[local-as ...]`, `[peer-as ...]`) from ExaBGP multi-session > draft are not supported — the draft never became an RFC. ### Route Commands (update text) All route operations use unified `update text` syntax with flat attribute declarations (no `set` keyword) and keyword aliases (short forms accepted, see Keyword Aliases below): <!-- source: internal/component/bgp/plugins/cmd/update/update_text.go -- ParseUpdateText --> ```bash # Announce routes (flat attributes, no 'set') send bgp <selector> update text next <ip> [attributes...] nlri <family> add prefix <prefix>... # Withdraw routes send bgp <selector> update text nlri <family> del prefix <prefix>... # End-of-RIB marker (RFC 4724) send bgp <selector> update text nlri <family> eor # VPLS (L2VPN/VPLS) send bgp <selector> update text nlri l2vpn/vpls add rd <rd> ve-id <n> ve-block-offset <n> ve-block-size <n> label-base <n> # EVPN (L2VPN/EVPN) send bgp <selector> update text nlri l2vpn/evpn add mac-ip rd <rd> mac <mac> [ip <ip>] label <n> send bgp <selector> update text nlri l2vpn/evpn add ip-prefix rd <rd> prefix <prefix> label <n> send bgp <selector> update text nlri l2vpn/evpn add multicast rd <rd> ip <ip> ``` #### A withdrawal can be answered with the peers it was withheld from `send bgp <selector> update text ... nlri <family> del ...` names no route to a peer whose session has advertised nothing, because RFC 4271 Section 4.3 identifies a withdrawn route in the context of the connection it was previously advertised on. Such a peer is written the withdrawal's path attributes with no route in them, or nothing at all where the family's withdrawal carries no attributes of its own (`docs/architecture/update-building.md`, "A Withdrawal Names a Route This Connection Advertised"). The command still answers `done`, and the peers it withheld the routes from are named in the response's `warnings`: ``` withdraw ipv4/unicast: withdrawal withheld: this session has advertised no route to the peer: ipv4/unicast, peers 192.0.2.10 ``` Once the session has carried one UPDATE that makes any destination reachable, every later withdrawal is written, whether or not the peer holds the route named. <!-- source: internal/component/bgp/plugins/cmd/update/update_text.go -- handleUpdateText --> <!-- source: internal/component/bgp/route/route.go -- ErrWithdrawWithheld --> ### Route Commands (update cursor) Stateful cursor mode for efficient replay of stored routes on reconnect. The engine maintains attribute state per (plugin, peer) pair. Subsequent commands send only changed attributes (delta encoding), reducing per-call overhead. <!-- source: internal/component/bgp/plugins/cmd/update/cursor.go -- handleUpdateCursor --> ```bash # First command: establish full attribute state + announce NLRIs send bgp <selector> update cursor origin igp as-path [65001] med 100 \ next-hop 10.0.0.1 nlri ipv4/unicast add 10.0.0.0/24 10.0.1.0/24 # Delta: only changed attributes, rest inherited from cursor send bgp <selector> update cursor as-path [65001 65003] \ nlri ipv4/unicast add 10.1.0.0/24 # NLRIs only (all attributes inherited) send bgp <selector> update cursor nlri ipv4/unicast add 10.2.0.0/24 # Remove an attribute from cursor send bgp <selector> update cursor del med nlri ipv4/unicast add 10.3.0.0/24 # Clear cursor state (call after replay completes) send bgp <selector> update cursor done ``` Cursor mode supports announce-only (`nlri <family> add`). Withdrawals are not supported because replay re-sends stored routes, never withdrawals. ### Removed Commands (use update text instead) The following legacy commands have been removed: | Old Command | Replacement | |-------------|-------------| | `announce ipv4/unicast <p> next-hop <nh>` | `update text next <nh> nlri ipv4/unicast add prefix <p>` | | `announce ipv6/unicast <p> next-hop <nh>` | `update text next <nh> nlri ipv6/unicast add prefix <p>` | | `announce eor <afi> <safi>` | `update text nlri <family> eor` | | `announce vpls ...` | `update text nlri l2vpn/vpls add ...` | | `announce l2vpn ...` | `update text nlri l2vpn/evpn add ...` | | `withdraw ipv4/unicast <p>` | `update text nlri ipv4/unicast del prefix <p>` | | `withdraw ipv6/unicast <p>` | `update text nlri ipv6/unicast del prefix <p>` | | `withdraw vpls ...` | `update text nlri l2vpn/vpls del ...` | | `withdraw l2vpn ...` | `update text nlri l2vpn/evpn del ...` | ### Watchdog Commands ``` request bgp watchdog announce <name> [med <N>] [peer] # Send all routes in pool (optional MED override) request bgp watchdog withdraw <name> [peer] # Withdraw all routes in pool from peers ``` Routes are tagged with a pool when announced: ```bash update text nhop set 10.0.0.1 nlri ipv4/unicast add prefix 1.0.0.0/24 watchdog set mypool ``` > **Note:** `watchdog set` in wire-mode updates is parsed but not yet > implemented. The `request bgp watchdog announce`/`request bgp watchdog withdraw` > pool commands work independently of this tagging. ### RIB Commands ``` show bgp rib [filters...] [terminal] # Unified route display with pipeline source filters: received | advertised filters: peer <selector>, path <pattern>, prefix <pattern>, community <value>, family <afi/safi> terminals: count, histogram, graph (AS topology box-drawing) show bgp rib best [filters...] [terminal] # Best-path per prefix (RFC 4271 §9.1.2) show bgp rib status # RIB status (peer/route counts) clear bgp rib in <selector> # Clear Adj-RIB-In (* for all peers) clear bgp rib out <selector> [family] # Resend Adj-RIB-Out (* for all, optional family) request bgp rib inject <peer> <family> <prefix> [attrs] # Insert route into Adj-RIB-In (no session needed) request bgp rib withdraw <peer> <family> <prefix> # Remove route from Adj-RIB-In show bgp rib rpf <family> <source-addr> # RPF lookup (longest-prefix-match in Loc-RIB) ``` Generic pipes apply to the answer the command produced. The operator language has exactly one statement, `pipeCatalog`, and [`docs/features/pipe-operators.generated.md`](https://github.com/ze-software/ze/blob/main/docs/features/pipe-operators.generated.md) is that table published; naming the operators here again is the drift this catalog exists to end. What a given command owes is published per command by `ze help command --json`, derived from the shape it declares, and an operator the shape cannot support is refused by name before the command runs. For a command the daemon serves, the DAEMON runs the chain. `execMiddleware` splits it off an SSH exec command and applies it, and `ze cli -c` sends the chain intact and prints what comes back. Only the daemon holds the configuration, so only the daemon can honor `environment cli format default`. A command the client serves in its own process through `RegisterLocalData` is the exception: `ServeLocal` runs the same chain over the local payload, before any daemon is contacted. `| save` is refused on every chain the daemon expands, because the file would be written on the daemon's filesystem with the daemon's privileges. <!-- source: internal/component/command/pipe_catalog.go -- pipeCatalog --> <!-- source: internal/component/command/pipe.go -- validateDeclaredShape --> <!-- source: internal/component/command/pipe_save.go -- validateSaveOps --> <!-- source: internal/component/command/local_data.go -- ServeLocal --> <!-- source: cmd/ze/help_command.go -- operatorsFor --> <!-- source: internal/component/ssh/ssh.go -- execMiddleware --> <!-- source: internal/component/cli/client/main.go -- Execute, commandWithFormat --> <!-- source: internal/component/cli/client/answer.go -- daemonOutput, newDaemonOutput --> RIB route filters such as `received`, `advertised`, `peer`, `family`, `prefix`, `path`, and `community` are command-specific filters registered by the RIB command and folded into the RIB iterator request before route output is generated. Inject attributes: `origin <igp|egp|incomplete>`, `nhop|nexthop <ip>`, `aspath <asn,asn,...>`, `localpref <n>`, `med <n>`. Peer address is a label (valid IP, no session required). Only simple prefix families (IPv4/IPv6 unicast/multicast). IPv4-mapped IPv6 next-hops accepted. <!-- source: internal/component/bgp/plugins/rib/rib_commands.go -- injectRoute, withdrawRoute --> <!-- source: internal/component/bgp/plugins/rib/yang/ze-rib-api.yang -- RIB RPCs --> #### Inter-Plugin RIB Commands (GR/LLGR) These commands are dispatched between plugins (bgp-gr to bgp-rib) and are not intended for direct user invocation: ``` request bgp rib retain-routes <peer> # Retain routes for peer (GR activation) request bgp rib release-routes <peer> # Release retained routes request bgp rib mark-stale <peer> <restart-time> [level] # Mark routes stale (level: 1=GR, 2=LLGR) request bgp rib purge-stale <peer> [family] # Purge stale routes (optionally per-family) request bgp rib attach-community <peer> <family> <hex> # Attach community to stale routes in family request bgp rib delete-with-community <peer> <family> <hex> # Delete routes carrying community in family ``` ### Named Commits (Batching) A named commit holds withdrawals and flushes them to every peer the selector matches. There is no `group start` or `group end`: this page published that pair until 2026-09-05 and no handler ever answered either spelling. ``` request commit start <name> # Open a named commit request commit withdraw <name> route <prefix> # Queue one withdrawal request commit show <name> # What the commit holds request commit end <name> # Flush it request commit eor <name> # Flush it, then End-of-RIB request commit rollback <name> # Discard the queue ``` A named commit cannot carry an ANNOUNCEMENT. `(*Transaction).QueueAnnounce` has no non-test caller, so nothing queues one, and `send bgp <selector> update ...` announces immediately whether a commit is open or not (`plan/journal/unwired-feature.md`, 2026-09-05). `end` and `eor` answer what each peer took: | Key | What it states | |-----|----------------| | `routes-queued`, `withdrawals-queued` | What the commit HELD, offered to each matched peer | | `routes-announced`, `routes-withdrawn`, `updates-sent`, `eor-sent` | What LEFT, summed over the peer rows | | `eor-requested` | The operator asked for an End-of-RIB, which is a different fact from one being sent | | `peers` | One row per matched peer, keyed by address, carrying `name`, `state`, that peer's four counters, and `reasons` | A `reasons` entry appears exactly when that peer took less than the commit offered it, and the vocabulary is closed: `not-established`, `announce-refused`, `routes-dropped`, `withdraw-refused`, `send-failed`, `eor-refused`. Any such peer makes the command answer `error`, and the error sentence names each one, because an error answer's payload is dropped in transit and the sentence is all a plugin receives. This rail drops the work for a peer with no established session rather than queueing it, so undelivered means dropped and `done` would be untrue. <!-- source: internal/component/bgp/plugins/cmd/commit/commit.go -- handleNamedCommitEnd, peerRows, shortfallSentence --> <!-- source: internal/component/bgp/reactor/reactor_api_batch.go -- SendRoutes, commitToPeer --> <!-- source: internal/component/bgp/types/types.go -- TransactionResult, PeerCommitResult --> <!-- source: internal/component/bgp/transaction/commit_manager.go -- CommitManager --> --- ## Action-First Syntax (Legacy) ### Show Commands ``` show neighbor [summary|extensive|configuration] show adj-rib in [<afi> <safi>] show adj-rib out [<afi> <safi>] ``` ### Announce/Withdraw All route operations now use `update text` syntax: ```bash update text next <ip> [attributes...] nlri <family> add prefix <prefix> update text nlri <family> del prefix <prefix> update text nlri <family> eor ``` > **Note:** Legacy `announce`/`withdraw` commands have been removed. > See "Removed Commands" section above for migration table. ### Control ``` teardown <peer-ip> <code> [<reason>] shutdown reload restart reset enable-ack disable-ack silence-ack help version ``` --- ## API Content Configuration (Ze) ### Attribute Filtering `ContentConfig.Attributes` limits which path attributes are parsed for API output. It is set by the engine, not by a peer's attach block: the `content { encoding format attribute }` container inside `attach process` parses and reaches no field of `ProcessBinding`, so a peer cannot ask for one rendering while another peer asks for a different one. <!-- source: internal/component/bgp/reactor/peer_settings.go -- ProcessBinding --> Available attribute names: | Name | Code | Description | |------|------|-------------| | `origin` | 1 | ORIGIN | | `as-path` | 2 | AS_PATH | | `next-hop` | 3 | NEXT_HOP | | `med` | 4 | MULTI_EXIT_DISC | | `local-pref` | 5 | LOCAL_PREF | | `atomic-aggregate` | 6 | ATOMIC_AGGREGATE | | `aggregator` | 7 | AGGREGATOR | | `community` | 8 | COMMUNITIES | | `originator-id` | 9 | ORIGINATOR_ID | | `cluster-list` | 10 | CLUSTER_LIST | | `extended-community` | 16 | EXTENDED_COMMUNITIES | | `large-community` | 32 | LARGE_COMMUNITIES | | `all` | - | All attributes (default) | <!-- source: internal/component/bgp/types/contentconfig.go -- ContentConfig --> Benefits of partial parsing: - Reduced CPU (only parse what's needed for routing decision) - Reduced memory (don't store full parsed attributes) - Wire bytes preserved for zero-copy forwarding ### NLRI Family Filtering `ContentConfig.NLRI` limits which address families are included in API output. Like the attribute filter, it is engine-set: no config leaf reaches it. Available families: | Config Syntax | Canonical Name | |---------------|----------------| | `ipv4/unicast` | ipv4/unicast | | `ipv6/unicast` | ipv6/unicast | | `ipv4/multicast` | ipv4/multicast | | `ipv6/multicast` | ipv6/multicast | | `ipv4 mpls` | ipv4 mpls | | `ipv6 mpls` | ipv6 mpls | | `ipv4/mpls-vpn` | ipv4/mpls-vpn | | `ipv6/mpls-vpn` | ipv6/mpls-vpn | | `ipv4/flowspec` | ipv4/flowspec | | `ipv6/flowspec` | ipv6/flowspec | | `l2vpn/evpn` | l2vpn/evpn | | `l2vpn/vpls` | l2vpn/vpls | Special values: `all` (default), `none` --- ## Route Attributes Attributes are flat keyword-value pairs (no `set` keyword). Both short and long forms accepted. API text output uses short forms; config output uses long forms. ``` next <ip> # Next-hop IP (required) — long: next-hop origin igp|egp|incomplete # Origin attribute path <asn>,<asn>,... # AS path — long: as-path pref <int> # Local preference — long: local-preference med <int> # Multi-exit discriminator s-com <comm>,<comm>,... # Standard communities — long: community x-com <ext>,<ext>,... # Extended communities — long: extended-community l-com <lc>,<lc>,... # Large communities — long: large-community originator-id <ip> # Originator ID cluster-list <ip>,<ip>,... # Cluster list label <label> # MPLS label (per-NLRI-section modifier) rd <rd> # Route distinguisher (per-NLRI-section modifier) info <id> # ADD-PATH path ID (per-NLRI-section modifier) — long: path-information atomic-aggregate # Atomic aggregate flag aggregator <asn> <ip> # Aggregator aigp <value> # AIGP split /<len> # Ze: prefix expansion (see below) ``` <!-- source: internal/component/bgp/types/types.go -- RouteSpec, FlowSpecRoute --> <!-- source: internal/component/bgp/types/nexthop.go -- RouteNextHop --> ### Keyword Aliases | Long (config) | Short (API) | Also accepts | |----------------|-------------|--------------| | `next-hop` | `next` | `nhop` (legacy) | | `local-preference` | `pref` | — | | `as-path` | `path` | — | | `community` | `s-com` | `short-community` | | `large-community` | `l-com` | — | | `extended-community` | `x-com` | `e-com` | | `path-information` | `info` | — | | `route-distinguisher` | `rd` | — | Lists use commas (no spaces): `path 65001,65002`. Brackets accepted for transition: `as-path [65001 65002]`. <!-- source: internal/component/bgp/plugins/cmd/update/update_text.go -- keyword alias table --> --- ## Split Keyword (Ze Extension) The `split` keyword expands a prefix into smaller prefixes. All attributes apply to each generated prefix. ### Syntax ``` split /<target-length> ``` ### Example ``` # Announce 2 prefixes with one command update text next 1.2.3.4 nlri ipv4/unicast add prefix 10.0.0.0/23 split /24 # → 10.0.0.0/24 next-hop 1.2.3.4 # → 10.0.1.0/24 next-hop 1.2.3.4 # With MPLS label - label applies to each prefix update text next 1.2.3.4 nlri ipv4/nlri-mpls label 100 add prefix 10.0.0.0/22 split /24 # → 10.0.0.0/24 label 100 # → 10.0.1.0/24 label 100 # → 10.0.2.0/24 label 100 # → 10.0.3.0/24 label 100 # With L3VPN - RD and label apply to each prefix update text next 1.2.3.4 nlri ipv4/mpls-vpn rd 100:1 label 200 add prefix 10.0.0.0/23 split /24 # → 10.0.0.0/24 rd 100:1 label 200 # → 10.0.1.0/24 rd 100:1 label 200 ``` ### Supported Families | Family | Split Support | Notes | |--------|---------------|-------| | IPv4/IPv6 unicast | ✅ | Standard prefix expansion | | IPv4/IPv6 nlri-mpls | ✅ | Label copied to each prefix | | IPv4/IPv6 mpls-vpn | ✅ | RD + label copied to each prefix | | FlowSpec | ❌ | N/A - uses match rules, not prefixes | | VPLS/EVPN | ❌ | Different NLRI structure | ### Constraints - Target length must be longer than source prefix (e.g., /23 → /24, not /24 → /23) - Maximum expansion: implementation-dependent (avoid /8 → /32) --- ## FlowSpec Commands FlowSpec rules use the unified `update text` syntax. The match components are the FlowSpec NLRI (`nlri ipv4/flow add <components>`); the action is carried as a FlowSpec extended community declared with the `extended-community` attribute before the `nlri` section (`discard` is sugar for `traffic-rate 0`). ``` # Match TCP traffic to 10.0.0.0/8 port 80 and drop it update text extended-community discard \ nlri ipv4/flow add \ destination 10.0.0.0/8 \ protocol tcp \ destination-port =80 # Withdraw the same FlowSpec rule (match components identify it) update text nlri ipv4/flow del \ destination 10.0.0.0/8 \ protocol tcp \ destination-port =80 ``` ### Match Components | Keyword | Description | |---------|-------------| | destination | Destination prefix | | source | Source prefix | | destination-port | Destination port | | source-port | Source port | | port | Any port | | protocol | IP protocol | | next-header | IPv6 next header | | tcp-flags | TCP flags | | icmp-type | ICMP type | | icmp-code | ICMP code | | fragment | Fragment flags | | dscp | DSCP value | | packet-length | Packet length | | flow-label | IPv6 flow label | ### Actions (then) | Keyword | Description | |---------|-------------| | accept | Accept traffic | | discard | Drop traffic | | rate-limit <bps> | Rate limit | | redirect <rt> | Redirect to VRF | | redirect-next-hop | Redirect to next-hop | | mark <dscp> | Set DSCP | | community [...] | Add community | <!-- source: internal/component/bgp/plugins/cmd/update/update_text_flowspec.go -- FlowSpec NLRI parsing --> <!-- source: internal/component/bgp/route/route_community.go -- ParseExtendedCommunities (discard, traffic-rate, redirect actions) --> --- ## Filter Callbacks The engine sends `filter-update` callbacks to external plugin filters during UPDATE processing. This is a callback RPC (engine to plugin), not a user command. | Field | Type | Description | |-------|------|-------------| | `filter` | string | Filter name (declared at stage 1, dispatches to the right handler) | | `direction` | string | `import` or `export` | | `peer` | string | The peer that SENT the route on import, the DESTINATION peer on export | | `peer-as` | uint32 | That peer's ASN, with the same meaning as `peer` | | `update` | string | Text-format attributes and NLRI (only declared attributes) | Response: `{"action":"accept"}`, `{"action":"reject"}`, or `{"action":"modify","update":"<delta>"}` with only changed fields. **`peer` and `peer-as` change meaning with the direction, and the callback says so only through `direction`.** The import chain passes the sending peer (`runIngressPolicyChain`) and the export chain passes the destination (`runEgressPolicyChainASN4`), both through `PolicyFilterChain`. A filter that reads the ASN as "who sent me this" is wrong on export, where nothing in the callback names the sender at all. **A filter's declared `Direction` does not gate dispatch.** `FilterDecl.Direction` is carried to `plugin.FilterRegistration.Direction` and read by nothing in the engine, so a filter is called on whichever chain the operator's config names it in. Direction lives on the config attachment point, and a filter that must act on one direction only tests `direction` itself. <!-- source: internal/component/bgp/reactor/filter_ordered.go -- runIngressPolicyChain, runEgressPolicyChainASN4 --> <!-- source: internal/component/plugin/server/startup.go -- the one writer of FilterRegistration.Direction --> <!-- source: internal/component/plugin/server/server.go -- CallFilterUpdate builds the filter-update request --> <!-- source: pkg/plugin/sdk/sdk_callbacks.go -- OnFilterUpdate handles it plugin-side --> --- ## Response Format ### Success (with serial prefix) ```json {"type":"response","response":{"serial":"1","status":"done"}} ``` ### Error ```json {"type":"response","response":{"serial":"1","status":"error","data":"description"}} ``` <!-- source: internal/component/plugin/types.go -- Response struct --> ### Rejected rows: `errors` and `data` A handler can answer with a row generator rather than a built payload. It can reject a row while the walk continues. A consumer that reads the whole answer as one string sees the rejected rows under a SIBLING key. A partial result therefore renders, instead of collapsing into one error string. | Answer | Rendering | |--------|-----------| | rows, no rejected row | `{"peers":[...]}`, or a bare `[...]` when the handler names no envelope. Unchanged | | rows and rejected rows | `{"peers":[...],"errors":[...]}` | | rejected rows, no envelope key | `{"data":[...],"errors":[...]}` | | the handler names its envelope `errors` | refused, on this path and on the record path | <!-- source: internal/component/plugin/types.go -- Records.MarshalJSON --> <!-- source: pkg/plugin/rpc/collapse.go -- CollapseRecords, AnswerErrorsKey, AnswerDefaultKey, ErrReservedEnvelopeKey --> `errors` appears only when a row was rejected. An ordinary answer keeps the shape it had, and no consumer meets a key it has not seen. `data` is where the rows go when the handler names no envelope and a row was rejected. A bare array has nowhere to carry a sibling. `| count` counts the rows the command produced and never the rejected ones, so the two collections stay separately countable. A commit that applied 97 leaves and rejected 3 renders both, rather than the 97 being lost with the error. Two kinds of handler answer with a generator. `system command list` is the engine's, and a plugin command handler is the SDK's. Every other command returns a built payload, and none of these keys appears for it. <!-- source: internal/component/plugin/server/system.go -- commandRows --> <!-- source: pkg/plugin/records.go -- Records --> ### A plugin command answers with records `execute-command` is the callback the engine sends for a command a plugin registered. Every plugin answers it with a head, its records and a terminator, and the engine reads that sequence for every plugin. <!-- source: pkg/plugin/sdk/sdk_dispatch.go -- Plugin.answerExecuteCommand --> <!-- source: internal/component/plugin/ipc/rpc.go -- PluginConn.SendExecuteCommandAnswer --> The handler decides what it produces, and the wire decides how it travels. | The handler returns | On the wire | What `routeToProcess` builds | |---------------------|-------------|------------------------------| | a built value | the `doc` item type and one record carrying that value | `plugin.RawJSON`, unchanged | | a `plugin.Records` walk of 256 rows or fewer | the `doc` item type and one record carrying the collapsed document | `plugin.RawJSON` over that document | | a `plugin.Records` walk of more than 256 rows, declaring no columns | the `map` item type and one record for each row | `plugin.Records` over the arriving rows | | a `plugin.Records` walk of more than 256 rows, declaring its columns | the `tab` item type, the names on the head, and one positional record for each row | `plugin.Records` over the arriving rows, carrying the head's names | The dispatcher branches on the head's item type and never on what the handler returned. A bounded walk is therefore the document it has always been, and only a walk that streams reaches an operator as records. A `tab` answer reaches the operator as the same objects a `map` answer would have carried. The engine forwards the head's column names beside the rows, and the rendering zips each positional row against them. A command that declares a schema and one that does not therefore answer one document for the same data. <!-- source: internal/component/plugin/server/command.go -- streamedPluginResponse --> <!-- source: internal/component/command/render_records.go -- RenderRecords, answerDocument --> <!-- source: pkg/plugin/records.go -- Records.WriteAnswer --> <!-- source: pkg/plugin/rpc/answer_write.go -- WriteRecordAnswer, WriteDocumentAnswer --> <!-- source: internal/component/plugin/server/command.go -- routeToProcess, pluginAnswerRows --> The value a built payload carries is unchanged, byte for byte. Only the frame around it changed, and it changed for every peer. No declaration selects the frame: `CommandDecl.Shape` states what the ANSWER holds, for the pipe layer to publish and to refuse against, and the walk length alone decides which frame carries it. So there is one frame and every reader knows it before the first line. <!-- source: pkg/plugin/rpc/types.go -- CommandDecl.Shape --> The rows are pulled as the operator's rendering writes them, so the engine never holds the whole collection for a walk that streams. A row wider than one wire message is rejected and the walk continues. That row reaches the operator under `errors`, beside the rows that were applied. <!-- source: internal/component/plugin/server/system.go -- handleSystemCommandList, commandRows --> ### Show Neighbor ```json { "neighbor": { "address": "192.168.1.2", "local-address": "192.168.1.1", "local-as": 65001, "peer-as": 65002, "router-id": "1.1.1.1", "state": "established" } } ``` ### Show Adj-RIB ```json { "routes": [ { "nlri": "10.0.0.0/8", "next-hop": "192.168.1.2", "origin": "igp", "as-path": [65002] } ] } ``` --- ## Command Dispatch ### Command Tree Structure > **Note:** This tree shows the **internal noun-first dispatch structure**, not > the user-facing grammar. User-facing commands are verb-first > (`show`/`request`/`clear`/`update`/`monitor` roots, e.g. `show bgp peer <sel> detail`, > `send bgp <sel> cached <id>`, `clear bgp rib in`); the noun-first RPCs below remain > only for internal dispatch. Nodes such as `peer/<selector>/announce` and > `peer/<selector>/withdraw` reflect removed verbs (see "Removed Commands"). ``` daemon ├── shutdown ├── reload ├── restart └── status plugin └── session ├── ready ├── ping └── bye bgp ├── help ├── command │ ├── list │ ├── help │ └── complete ├── event │ └── list ├── log │ ├── levels # Show subsystem log levels │ └── set # Set subsystem log level at runtime ├── metrics │ ├── values # Show Prometheus metrics (text format) │ ├── list # List metric names │ └── pool # Per-attribute pool occupancy and dedup rates └── plugin ├── encoding ├── format └── ack peer ├── list ├── detail ├── capabilities ├── statistics └── <selector> ├── detail ├── capabilities ├── statistics ├── teardown ├── announce ├── withdraw └── group rib ├── routes [sent|received|sent-received] [filters...] [count|json] ├── best [filters...] [count|json] ├── status ├── clear [in|out] ├── inject <peer> <family> <prefix> [attrs...] └── withdraw <peer> <family> <prefix> group ├── start └── end monitor ├── bgp # Live peer dashboard (TUI) └── event # Stream live events (keeps session open) ``` --- ## Ze Implementation Notes ### Command Dispatcher ```go type Handler func(ctx *Context, peers []string, remaining string) error type DispatchTree map[string]interface{} // Handler or nested DispatchTree func Dispatch(tree DispatchTree, tokens *Tokenizer, reactor *Reactor) (Handler, []string) { // Walk tree consuming tokens // Return handler and matched peers } ``` <!-- source: internal/component/plugin/server/command.go -- Dispatcher, Handler --> ### YANG-Typed Command Arguments Operational commands declare their argument types as YANG leaves inside `ze:command` containers. The same leaf metadata drives two consumers: 1. **Completer** (`command/completer.go`): enum values become tab-completion suggestions; keyword leaf names appear as completable tokens. 2. **Dispatcher** (`plugin/server/command.go`): validates args against ArgDefs between tokenize and handler call (two-phase: keyword extraction, then positional matching). A command carries more grammar than its ArgDefs hold. A modifier group states a keyword and a value the HANDLER parses, so the dispatcher meets tokens that belong to no definition of its own, and it MUST NOT read one of them as a bad value. `validateCommandArgs` counts the tokens it could not place against the definitions still open. As many tokens as open definitions, or fewer: each token can be attributed to a definition, so the first one is refused by that definition's own message (`invalid value "not-an-ip", does not match expected pattern`). More tokens than open definitions: at least one token is a value for nothing, so a missing mandatory argument is reported instead (`show policy test peer test-peer update <hex>` answers `required argument missing: direction`, and not a complaint about the `update` keyword the handler reads). A leaf's own `description` reaches no surface. `argDefFor` (`config/yang/command.go`) reads the leaf's `type` and its `mandatory` statement, and `command.ArgDef` carries no description field. State what an argument means in the command's own `ze:help`. ```go type ArgDef struct { Name string // YANG leaf name (kebab-case) Kind ArgKind // ArgString, ArgEnum, ArgUint, ArgUnion EnumValues []string // Valid enum values UintBits int // 8, 16, 32, or 64 Ranges []UintRange // Valid ranges (disjoint segments supported) Pattern *regexp.Regexp // Compiled XSD pattern for ArgString UnionDefs []ArgDef // Member types for ArgUnion Mandatory bool // True if YANG leaf has mandatory true Anchor string // Path keyword this value follows; "" for a trailing value } ``` ArgDefs are extracted from YANG by `BuildCommandTree` (`config/yang/command.go`) and stored on `command.Node.ArgDefs`. The dispatcher receives them via `RegisterOptions.ArgDefs` populated by `PathToArgDefs`. A container that names an object declares the value the operator types after its keyword, once, and every command under it takes that value: `request interface <name> up`, `<name> down`, `<name> mtu <bytes>` share one `name` leaf on the `interface` container. `inheritArgDefs` (`config/yang/command.go`) carries such a leaf down to each command after every module is merged, with `Anchor` set to the container's name, and the renderer places the value right after that keyword. The command under such a container that acts on no single member of the set states `ze:inherit "none"`: `show bgp peer list` reads every peer, and `request interface migrate` names two interfaces of its own. Nothing binds a value by `Anchor`: a positional token still goes to the definition whose type constrains it most (`positionalDef`). Runtime-dynamic hints (e.g., address families from plugin registry) remain as `ValueHints` callbacks. Static hints (log levels, FD limit "max") are YANG-declared and served through ArgDefs. <!-- source: internal/component/command/node.go -- ArgDef, Node.ArgDefs --> <!-- source: internal/component/config/yang/command.go -- extractArgDefs --> ### Plugin Command Completion Plugin-registered commands (from the plugin `CommandRegistry`, not YANG) are absent from the YANG-derived command tree, so they are **injected into the interactive completion tree after the daemon starts**. `CommandRegistry.VisibleCommandEntries()` returns every non-`Hidden` command as a `command.CommandEntry{Name, Description}`; `command.MergeCommandPaths` inserts each path into the tree as completion-only nodes. The merge is non-destructive: an existing YANG node keeps its `WireMethod` and description, so a plugin command never shadows a builtin at the completion layer (mirroring dispatch precedence). - **SSH** rebuilds the tree per session and merges eagerly (`session_factory.go` `mergePluginCommands`), so each session reflects the current registry — a plugin that has exited is simply absent next session. - **Web** builds a throwaway overlay from the current registry for each `/cli/complete` request (`web_completer.go` `pluginAwareCommandCompleter`). The shared YANG tree stays immutable. The hub binds the web listener after plugin startup and the initial registry freeze. The per-request overlay reflects plugins that a later reload adds or removes. - **Shell completion** (`ze completion words`) runs in a standalone CLI process with no daemon, so it stays YANG-only; the daemon's `system command complete` RPC completes plugin commands directly from the registry (`Registry().Complete`). - **The attached console** of `ze start --cli` asks the daemon for `system command list` at attach time. It filters the compiled RPC list against that answer, then injects each non-hidden plugin command (`buildRuntimeTreeFromDispatch`, `injectPluginCommands`). Both help texts travel on that answer, so the tree carries the summary and the explanation. A plugin a later reload adds is absent until the operator attaches again. `Hidden` commands still dispatch when typed in full. They never appear in completion, in help, in the MCP `tools/list` result, or in the API command list. <!-- doc-links: ignore (JSON-RPC method name, not a path) --> `buildCommandMeta` is the one producer of the last two surfaces, and it skips every hidden plugin command. <!-- source: internal/component/plugin/server/command_registry.go -- VisibleCommandEntries, Complete, Hidden --> <!-- source: cmd/ze/hub/command_meta.go -- buildCommandMeta hidden plugin command skip --> <!-- source: internal/component/command/node.go -- MergeCommandPaths, CommandEntry --> <!-- source: cmd/ze/hub/session_factory.go -- mergePluginCommands (SSH per-session) --> <!-- source: internal/component/cli/client/main.go -- newAttachedModel, buildRuntimeTreeFromDispatch --> <!-- source: cmd/ze/hub/web_completer.go -- pluginAwareCommandCompleter (web live overlay) --> <!-- source: cmd/ze/hub/main.go -- runYANGConfig --> <!-- source: internal/component/plugin/server/startup.go -- signalStartupComplete, WaitForStartupComplete --> ### Quiesce Barrier (test synchronization) `request quiesce` (`ze-system:quiesce`) **blocks until every registered subsystem has drained its pending asynchronous work, then replies** — a barrier tests use in place of a fixed `time.sleep`. It is the general form of `ze-bgp:peer-flush`: the control plane is already synchronous (a command reply lands after its handler runs), but downstream effects (routes flushed to peer sockets, and later FIB/tc/listeners) complete after the reply, so a test does `send(change); request quiesce; assert on-wire` with no sleep. Subsystems register a `Quiescer` at runtime (they need a live reference such as the reactor), and the handler discovers them through the registry, with no per-subsystem switch. The BGP reactor auto-registers TWO quiescers when it attaches to the server (`registerReactorQuiescer`): `bgp-forward-pool` (the reactor's `FlushForwardPool`, draining post-establishment forwarded routes) and `bgp-peer-sync` (`DrainPeerSync`, draining each peer's initial-sync opQueue, which goes DIRECT to the session and bypasses the forward pool). Each drain is bounded by a per-subsystem timeout, so a wedged subsystem yields an error naming it instead of hanging the daemon. Invocation note: `request quiesce` and `request peer <sel> flush` are api-yang RPCs reached through **dispatch-command**, not as direct wire methods. A plugin calls `api.dispatch("request quiesce")` (the test SDK's `ze_api.quiesce()` and `ze_api.wait_for_ack()` both do this); a raw `_call_engine("ze-system:quiesce")` returns "unknown method" because `dispatchPluginRPC` routes only `ze-plugin-engine:*` engine ops plus codec RPCs. Extension point: Layer 2/3 subsystems (a kernel-FIB quiescer, a tc/qdisc quiescer) register into the same registry and `request quiesce` drains them with no change to the barrier. `wait_for_ack` is now a thin, sleepless wrapper over this barrier: the two BGP quiescers together cover the forward pool AND the per-peer initial-sync drain, so a route sent during establishment is on the wire (past its EOR) before the barrier returns. <!-- source: internal/component/plugin/server/quiesce.go -- Quiescer, QuiescerRegistry, quiesceAll, handleQuiesce, registerReactorQuiescer --> <!-- source: internal/component/bgp/reactor/reactor_api.go -- DrainPeerSync, peersSynced; peer.go PendingSync --> <!-- source: internal/core/ipc/yang/ze-system-cmd.yang -- request/quiesce -> ze-system:quiesce --> ### Peer Selector Parsing ```go type Selector struct { All bool IP netip.Addr Filters map[string]string // local-as, peer-as, local-ip, id, family } func ParseSelector(s string) (*Selector, error) { if s == "*" { return &Selector{All: true}, nil } if strings.HasPrefix(s, "[") { return parseFilteredSelector(s) } ip, err := netip.ParseAddr(s) return &Selector{IP: ip}, err } ``` ### Command Registry ```go var Commands = []CommandInfo{ {"daemon shutdown", false, nil}, {"send bgp * update text", true, []string{"next", "origin", ...}}, // ... } ``` <!-- source: internal/component/plugin/server/rpc_register.go -- registeredRPCs --> ### A command's two help texts A command node carries two help texts, and each is declared by its own YANG statement. An RPC, a plugin command and an offline local command each carry the same pair, in the declaration form their own registration uses. | Field | Declared by | Holds | |-------|-------------|-------| | `command.Node.Description` | the `description` statement | the one-line SUMMARY | | `command.Node.Help` | the `ze:help` extension | the LONG explanation of that one command | Neither is derived from the other, and no reader shortens either one to guess at the other. The summary is authored short because it is a summary. No reader cuts it: every surface prints the summary whole. `mergeYANGEntry` (`internal/component/config/yang/command.go`) writes both, and `mergeHelpText` decides each field on its own when several modules contribute one command path. A collision leaves the first value in place and logs `YANG command help text mismatch` naming the field that collided. An empty `Help` is a command nobody has written an explanation for, and the help page prints its summary alone. An empty `Description` is a defect: `validateNode` names each one by path. An RPC carries the same two texts, in the same two YANG statements. `ExtractRPCs` (`internal/component/config/yang/rpc.go`) writes them to `RPCMeta.Description` and `RPCMeta.Help`. `GetHelpExtension` is the ONE reader of the extension for both carriers. A command container reaches it through `Entry.Exts`, and an rpc through `gyang.RPC.Exts()`. `./le docvalid help-shape` holds the two corpora to one shape. An RPC's pair reaches an agent through the machine-readable reference. `SchemaRegistry.RegisterRPCs` copies both to `RegisteredRPC.Description` and `RegisteredRPC.LongHelp`. `aihelp.Build` then publishes them under `description` and `long-help`, the two keys `ze help command --json` uses for a command. `ze help ai --json` and the MCP `ze_reference` tool read that one projection. The `show schema methods` and `ze schema methods` tables print one line for each RPC. Both read the summary alone, as every other one-line surface does. A PLUGIN command carries the same two texts, declared in its Stage 1 message as `description` and `long-help`. `VisibleCommandEntries` reads both off the registry, and `MergeCommandPaths` fills each field of the tree on its own. A plugin that declares a summary and no explanation therefore fills the summary alone. The names cross at that call. The plugin server spells them `Description` and `LongHelp`, because `Help` already means the SUMMARY there, on `Completion` and on the dispatcher's builtin `Command`. The bound and the control-character refusal on a declared text are in `docs/architecture/api/process-protocol.md`. `command help "<name>"` answers with both, under the `description` and `long-help` keys, for a builtin and for a plugin command alike. `system command list` carries both texts on every row too. The `help` key holds the summary, and `long-help` holds the explanation. That answer is the only place the ATTACHED console of `ze start --cli` reads either text from. An explanation that does not travel here is one its `?` key cannot print. `commandRows` fills the pair for a builtin and for a registered plugin command alike. A command that declares no explanation yields a row with no `long-help` key. On the client, `applyCommandText` writes the pair onto the node the row names, in ONE walk. `injectPluginCommands` carries the pair into a node the tree does not yet hold. An OFFLINE LOCAL command carries the same two texts in a `registry.Meta`, declared beside its handler in Go rather than in a YANG module. `Description` is the summary and `LongHelp` is the explanation, and the same empty-is-unwritten rule holds for both. `collectCommands` (`cmd/ze/help_command.go`) merges these registrations into `ze help command --json` after the tree, and skips one whose path the tree already holds, so the catalog publishes the node's texts for such a path and the registration's for every other. `./le docvalid help-shape` holds this third corpus to the same seven rules, reading the registrations this binary links from the registry and the four `cmd/ze` declares in `package main` from its source, which is the only way to read a package Go forbids importing. #### Which surface renders which field Every surface that shows a command on ONE line reads the summary, and every one of them prints it whole. Only a surface that shows ONE command reads the long explanation: the help page in the terminal, and the two published detail surfaces. | Surface | Producer | Reads | |---------|----------|-------| | The per-command help page | `commandHelpPage`, rendered by `helpfmt.(*Page).WriteTo` | `Description` on the header line, then `Help` in the body block, then the child rows. A node states its own two texts whether or not it has children | | A help page's child rows | `command.HelpEntries` | `Description` | | A completion candidate | `command.TreeCompleter.matchChildren`, `choiceSuggestions` | `Description` | | The interactive completion pane | `internal/component/cli` `Model.renderDropdownBox` | nothing. A menu row is the command name alone, and a name wider than the box is clamped to the frame | | The interactive message line | `internal/component/cli` `Model.warningText`, `Model.handleKeyMsg` (the `?` key), `Model.updateCompletions` | `Description`, whole, for the candidate the menu has selected | | The interactive explanation box, which the `?` key opens | `internal/component/cli` `Model.renderExplanationBox`, answered by `command.TreeCompleter.Explain` | `Help`, whole. The attached console reads it from the `long-help` key of `system command list` | | A shell-completion record | `internal/plugins/completion` `writeCompletionRecord` | `Description` | | The `ze help command` table row | `printCommandTable` | `Description` | | `ze help command --verbose` | `printCommandVerbose` | `Description`, then `Help` | | `ze help command --json` | `commandEntry` | `description`, and `long-help` | | The web admin command form | `buildAdminFragmentData`, rendered by the `commandForm` template | `Description` as the lede, `Help` as the body | | The web completion dropdown | `HandleCLICompleteWithCommandCompleter` | `Description`, in the JSON `description` key | | An MCP tool's action enum | `buildToolDef` | `Description`, one line for each action | | An MCP tool's own description | `buildToolDef`, `commandText` | `Description`, then a blank line, then `LongHelp` | | The OpenAPI operation | `OpenAPISchema` | `Description` as `summary`, `LongHelp` as `description` | | The published wiki catalog | `wikicatalog.Render` | `Description` in the summary table column, `LongHelp` in the `###` detail block | | The published CLI reference row | `internal/le/site` `writeCommandRow`, `commandMirrorDescription` | `Description` | | The published per-command detail page | `internal/le/site` `equivalentZeCard`, `equivalentDetailMirror` | `Description` as the lede, `LongHelp` as the Description body | | The `llms.txt` command line | `internal/le/site` `writeLLMSCommands` | `Description`, whole and with no character budget | | An offline local command in any of the rows above | `registry.ListLocal`, merged by `collectCommands` and by `wikicatalog.Collect` | `Meta.Description` and `Meta.LongHelp`, in place of the node's two texts | The machine surfaces carry the same pair. `commandMeta` (`cmd/ze/hub/command_meta.go`) holds both halves for the API and MCP listers. Its merge decides each half on its own, so a command with a YANG summary and a plugin explanation keeps both. OpenAPI 3.1 already names the two roles, so the mapping is one to one: `summary` is short and `description` is long. A command that declares no explanation carries NO `description` key, never an empty one. The shell-completion record is `name`, tab, `description`, newline. A summary carrying a tab or a newline is FOLDED to single spaces there, never cut. Folding answers the format's one-line constraint and loses no word. NO surface cuts a summary. The TUI completion pane held the last cut in Ze, and it went with the description column that sized it. A menu row is the command name alone. The selected candidate's summary is on message line 2, whole (`docs/architecture/cli/error-surface.md`). <!-- source: internal/component/command/help.go -- HelpEntries, describeChildren --> <!-- source: internal/component/plugin/server/schema.go -- RegisteredRPC, RegisterRPCs --> <!-- source: internal/component/aihelp/aihelp.go -- RPC, Build --> <!-- source: internal/core/helpfmt/helpfmt.go -- Page, (*Page).WriteTo --> <!-- source: cmd/ze/help_command.go -- commandEntry, printCommandTable, printCommandVerbose --> <!-- source: internal/component/command/registry/registry.go -- Meta, ListLocal --> <!-- source: cmd/ze/command_help_page.go -- commandHelpPage --> <!-- source: internal/plugins/completion/words.go -- writeCompletionRecord --> <!-- source: internal/component/cli/model_render.go -- (Model).renderDropdownBox, (Model).warningText --> <!-- source: internal/component/web/handler_admin.go -- buildAdminFragmentData, CommandFormData --> <!-- source: internal/component/web/cli.go -- HandleCLICompleteWithCommandCompleter --> <!-- source: internal/component/mcp/tools.go -- CommandInfo, buildToolDef, commandText --> <!-- source: internal/component/api/schema.go -- OpenAPISchema --> <!-- source: cmd/ze/hub/command_meta.go -- commandMeta, buildCommandMeta --> <!-- source: internal/component/config/yang/command.go -- mergeYANGEntry, mergeHelpText, GetHelpExtension, PathToHelp --> <!-- source: internal/component/command/node.go -- Node, CommandEntry --> <!-- source: internal/component/plugin/server/command_registry.go -- RegisteredCommand, VisibleCommandEntries --> <!-- source: internal/plugins/meta/cmd/help.go -- commandHelp, commandHelpText --> ### Per-command declarations: what a command says about itself Five registries let a command say something about itself to the CLI. Each one is keyed by command path. Each resolves a command to the declaration registered on the longest path that is a prefix of it. So `show bgp rib best` picks up a declaration on `show bgp rib` unless it registers one of its own. A command that must NOT inherit registers an EMPTY declaration. Absent and empty are different answers, and that is the trap all five registries exist in. | Registry | Declares | Read by | |----------|----------|---------| | `RegisterShape` | whether the answer holds rows (`tab`, `map`) or one document (`doc`) | `validateDeclaredShape`, before the command runs, and `ze help command --json` | | `RegisterColumns` | the order the table and text renderers put this command's columns in | `tableStyle.orderKeys`, through the four `ProcessPipes*` wrappers, and the four catalog readers as `column-orders` | | `RegisterAddressFields` | that a field of the answer holds an IP address | `validateDeclaredShape`, which admits `\| resolve` and `\| origin` only where a field is declared | | `RegisterPipeFilters` | the pipe segments this command accepts as its own, which `foldFilters` rewrites into server-side arguments | `foldFilters`, the completer, the pipe validator | | `RegisterAliases` | a name an operator types in the operator slot, standing for a chain (see "Pipe aliases" below) | `lookupAlias` | One implementation resolves all five: `commandRegistry[T]` in `column_order.go`. <!-- source: internal/component/command/answer_shape.go -- RegisterShape, ShapeForCommand, RegisterAddressFields, AddressFieldsForCommand --> <!-- source: internal/component/command/column_order.go -- commandRegistry, lookup --> #### Two packages, one path: empty is a floor and a disagreement is a defect The first four registries are a `declarationRegistry[T]`, which adds one rule to that lookup. `declare` reads what the path already holds: | The value | The path holds | Result | |-----------|----------------|--------| | anything | nothing | the value is stored | | EMPTY | anything | what the path holds stays | | non-empty | an EMPTY declaration | the value replaces it | | non-empty | an equal value | no change | | non-empty | a DIFFERENT non-empty value | `panic("BUG:")`, naming the registry, the path and both values | An empty declaration is a FLOOR and never a claim. It stops a shorter path being inherited, and it says nothing about what the answer holds. `show bgp rib` is the case this rule was written for. The BGP peer command plugin blanks every direct child of `show bgp`, and the rib command plugin declares `tab` for `show bgp rib`. Under the earlier last-writer-wins rule, package initialization order decided which of the two the path answered. Every in-tree caller declares from `init()`, so two different non-empty values are a state only a Ze defect reaches. The panic reports it before the daemon serves anything (`docs/contributing/ze-go-style.md`). A plugin declares from a socket instead, and a bad declaration there is an operating error rather than a Ze defect. So the first three registries carry a second write, `declareFor`, which is the same four cases with the panic replaced by an error. `RegisterPluginShapes` calls it, and nothing a plugin sends reaches `declare`. The ALIAS registry is the fourth, and it keeps `register`. `RegisterPluginAliases` stores `mergedAliases(path, ...)`, which differs from what the path holds each time it runs, so the rule would fire on the ordinary case. <!-- source: internal/component/command/column_order.go -- declarationRegistry, newDeclarationRegistry, declare, declareFor --> <!-- source: internal/component/command/answer_shape.go -- RegisterPluginShapes --> <!-- source: internal/component/command/alias.go -- aliasRegistry, mergedAliases --> #### The declared shape refuses an operator before the command runs `validateDeclaredShape` reads the declared shape and refuses an operator that shape cannot support, by name, before dispatch. A command declaring `doc` answers `count cannot apply here: this command answers one document, and count acts on rows`. The answer's own shape refuses as well, at apply time, and that half covers every command including the ones that declare nothing. The declaration is what makes the published catalog true, because `ze help command --json` lists a declared command's operators from its shape. An answer HAS rows in two spellings, and the second is what makes an identity readable. A LIST is rows. A MAP whose values share one shape is rows keyed by identity, and the key names each row: `show bgp peer list` maps a peer address to that peer's record, and `show bgp adj-rib-in` maps a peer address to that peer's routes. A row operator keeps the spelling it was given, so `show bgp adj-rib-in | first 1` answers one peer's routes under that peer's address. A map that mixes an object with a list under different keys is one document, because its keys are field names rather than identities. <!-- source: internal/component/command/answer_shape.go -- rowSet, identityValuesShareOneShape, selectRows --> Both halves of that message are derived, because one operator needs more than rows. `| fill` brings back the columns a command declared. So it acts on `tab` alone, and it means nothing over a `map` answer whose rows carry their own keys. `shapeDescription` therefore calls `map` "rows that describe themselves". It calls `tab` "rows read against a declared column order", and `operatorNeeds` reads the operator's own shape set. Calling both "rows" gave a refusal that contradicted itself, in front of an operator whose answer HAS rows. A declared address-field list is an ADMISSION gate AND a selector. It decides whether `| resolve` and `| origin` run at all, and it decides what they decorate. `bindAddressFields` copies the declaration onto each address operator when the chain is parsed, so a later registry withdrawal cannot change an in-flight chain, and `resolveJSON` and `originJSON` decorate a key only when `addressFieldSelected` finds it in that list. Standalone stdin is the one path that decorates every key. `ProcessStandalonePipesChecked` sets `allAddressFields` on each address operator, because stdin has no command path and therefore no declaration to read. There `addressFieldSelected` returns true for every key whose value parses as an address. <!-- source: internal/component/command/pipe.go -- bindAddressFields, ProcessStandalonePipesChecked --> <!-- source: internal/component/command/pipe_resolve.go -- resolveJSON, addressFieldSelected --> <!-- source: internal/component/command/pipe_origin.go -- originJSON --> Every `show bgp` command declares a shape, and two channels write them. Go compiled into the daemon declares twenty paths. Nine of those name an address field. `show bgp rib` and `show bgp irr` are served by a plugin process, and an in-core shim declares for them. Scope is therefore the registration site rather than the process boundary. A plugin process declares the other eleven in its Stage 1 message. Six sit under `show bgp rpki`, two under `show bgp rs`, two under `show bgp adj-rib-in`, and `show bgp healthcheck` is the eleventh. Four of the eleven name an address field. See "A plugin declares its own answer shape" below. A SELECTOR spelling declares nothing of its own. The registry resolves the string the operator typed. `show bgp peer detail` is not a prefix of `show bgp peer 192.0.2.1 detail`, so that spelling resolves `show bgp peer`, one of the empty declarations. It therefore reaches no refusal before dispatch, and the answer's own shape refuses after it. <!-- source: internal/component/command/pipe.go -- validateDeclaredShape, shapeDescription, operatorNeeds --> <!-- source: internal/component/command/pipe_resolve.go -- resolveJSON --> <!-- source: internal/component/command/pipe_origin.go -- originJSON --> <!-- source: internal/component/bgp/plugins/cmd/peer/peer.go -- cmdBgpChildren, registerShapes --> <!-- source: internal/component/bgp/plugins/rpki/rpki.go -- commandDecls --> <!-- source: internal/component/bgp/plugins/rs/server.go -- commandDecls --> <!-- source: internal/component/bgp/plugins/adj_rib_in/rib.go -- commandDecls --> <!-- source: internal/component/bgp/plugins/healthcheck/healthcheck.go -- commandDecls --> A column order never enters the payload. It is captured when the formatter is built, from the command string the wrapper already holds, and it reaches `| table` and `| text` only. `| json`, `| ndjson` and `| yaml` keep their alphabetical keys, because a program reads those three and key order carries no meaning for a program. A command declares one order per record shape. `show bgp` renders an outer record and a list of peer rows. Both carry an `uptime` key in a different position. So the renderer applies the declaration that names the most of the keys in the record it has in hand. ### `| display` and `| fill`: the operator's own answer An operator overrides both halves of that with two generic pipe operators. `| display <field>...` names the fields the answer leads with. `| fill [alpha] [reverse]` says whether the fields it did not name come back at all, and in what sequence. Each takes ONE type of argument, so no token is a field name in one position and a keyword in another. `| fill` on its own orders the remaining fields by the command's own declaration. `alpha` orders them by field name instead. `reverse` flips whichever way is in force. Neither way measures a column: each decides the sequence from the key set and a declaration. A third way was removed on 2026-08-19. `| fill overall` ordered columns by the width they render at, and that width is known only after every cell of the whole answer has been rendered. It made the first row unwritable until the last row had been read, which is the one thing a streamed answer cannot do. `overall` is now refused by name, and `| fill` itself is untouched. The two halves of the request travel by different routes, and the split is what makes them work under every format: | Half | Where it is applied | Reaches | |------|--------------------|---------| | Selection: which fields | `applyDisplaySelect`, over the payload, at the operator's position in the chain | every format, `\| json`, `\| ndjson`, `\| yaml` and `\| raw` included | | Sequence: in what order | `columnRequest` carried on `tableStyle` | `\| table` and `\| text` only | Selection is a data question the operator asked out loud, so a program gets the answer. Sequence is presentation, so it stops at the two renderers. `selectFields` walks the same shapes `tableStyle.renderValue` walks, and applies the same rule `orderKeys` applies. A record that carries at least one displayed field is cut to the displayed ones. A record that carries none is left whole. Without that agreement a nested sub-table and the JSON behind it would answer with different fields. Without it a record naming nothing displayed would render as a box with no rows. **A kind the `foldFilters` switch does not name stays in the chain.** The switch names the five kinds a command can own as a filter it resolves itself. Its `default:` arm carries every other kind to the chain `ApplyPipes` runs over the answer. Both sides run in the daemon. That arm is load-bearing. Without it a kind named nowhere reached neither side, for every command that registers filters of its own, and nothing reported the loss. `TestColumnOpsSurviveFoldFiltersOnFilteredCommand`, `TestAliasSurvivesFoldFiltersOnFilteredCommand` and `test/ui/display-fill-filtered-command.ci` are what hold that. <!-- source: internal/component/command/column_order.go -- RegisterColumns, ColumnsForCommand, commandRegistry --> <!-- source: internal/component/command/pipe_filter.go -- RegisterPipeFilters, lookupPipeFilters --> <!-- source: internal/component/command/pipe_columns.go -- parseDisplay, parseFill, columnsInChain, applyDisplaySelect, selectFields --> <!-- source: internal/component/command/pipe_table.go -- tableStyle.orderKeys, fillKeys, bestColumnOrder --> ### Pipe aliases: a name for an operator chain An alias is a name an operator types in the operator slot, standing for a chain they would otherwise retype. `show bgp | peers` says what `show bgp | display peers` says. Two callers declare one, and both write the same registry. | Registration | Table | Resolved | |--------------|-------|----------| | `RegisterAliases([]string{"show bgp"}, ...)`, from Go compiled into the daemon | `aliasRegistry`, the same `commandRegistry[T]` the two registries above use | by the longest command path that is a prefix of the command | | `RegisterAliases(nil, ...)`, from the same Go | `globalAliases`, a table of its own | for every command, when the per-command lookup carries no alias of that name | | `RegisterPluginAliases(owner, commands, declared)`, from a plugin's Stage 1 message | `aliasRegistry`, on the command paths that plugin declared | the same longest-prefix rule. A plugin reaches no global table | The global table is separate rather than a registration on the empty command path. `commandRegistry.register` skips an empty path, and `commandMatchesPrefix` refuses an empty prefix against every non-empty command, so such a registration would match nothing and report nothing. `expandAliases` runs between `ParsePipe` and `foldFilters`, so classification only ever sees operators the parser already knows. It is ONE pass, and four properties are what make one pass enough. `checkAlias` is the one reading of all four, so the two callers can never disagree about which declarations are sound: - An alias MUST NOT name another alias. Its expansion parses to pipe operators alone. - An alias MUST NOT carry the name of a pipe operator, which `ParsePipe` would read first. - An alias MUST NOT carry the name of a pipe filter of an overlapping command path. A command's own filter resolves before anything generic, so the filter would win at use time and nothing would say why. `RegisterPipeFilters` refuses the same pair from its side, because package init order decides which of the two registrations runs second. - An alias takes no argument. A word after the name is refused when the chain runs, rather than dropped. The two callers differ in the ANSWER, not in the checks. `checkedAlias` turns a refusal into `panic("BUG:")`. Only Go in this repository reaches `RegisterAliases`, so a bad registration there is a Ze defect the compiler cannot see. `RegisterPluginAliases` returns the refusal as an error, because the strings it reads arrived over a socket. An alias never enters the payload. `expandAliases` replaces the name with the operators before the chain runs, so a command handler cannot tell an alias from the chain it stands for. The chain is expanded in the process that PARSES it, and for a plugin's alias that process MUST be the daemon. Read "Where an alias resolves, and where it does not" below. `show bgp` registers the two in-tree aliases that exist. Both are a selection among sibling keys, because that answer carries its aggregates and its `peers` array at the same level: | Alias | Expands to | |-------|-----------| | `summary` | `display router-id local-as uptime peers-configured peers-established` | | `peers` | `display peers` | The BGP RPKI plugin declares a third, `summary` on `show bgp rpki`, over the Stage 1 channel the next section describes. <!-- source: internal/component/command/alias.go -- RegisterAliases, RegisterPluginAliases, checkAlias, checkedAlias, lookupAlias, AliasesForCommand --> <!-- source: internal/component/command/pipe.go -- parsePipeChain, expandAliases, parsePipeOps --> <!-- source: internal/component/bgp/plugins/cmd/peer/peer.go -- registerAliases --> ### A plugin declares a pipe alias in its Stage 1 message `DeclareRegistrationInput.Pipes` is a list of `PipeDecl`, beside the lists that carry families, commands, filters, doctor checks and enrichers. The SDK re-exports the type, so an external author imports one package. Each entry carries four strings, and the engine parses the expansion once, at registration. | Field | Meaning | |-------|---------| | `command` | The command path the alias sits on. It MUST be one of the commands this plugin declares in the same message | | `name` | The word an operator types after the pipe character. Lowercase kebab-case, one word | | `description` | The line completion and `command help` show beside the name | | `expansion` | The operator chain the name stands for, written the way an operator would type it | `validatePipeDecls` reads the shape and the ownership, in the position where Stage 1 already validates doctor checks and enrichers, before it converts anything. `registerPluginPipes` then writes the accepted set under `startupRegistrationMu`, between the registry row and the runtime families. Each later failure unwinds what the steps above it wrote. A plugin names only a path it declared itself, and it reaches no global table. A path another PLUGIN declared is refused a step earlier: `PluginRegistry.Register` runs before the alias write and rejects a command name another plugin holds, so the second plugin fails on the COMMAND and never reaches its alias. What the check confirms is that the plugin DECLARED the path, not that the daemon routes that path to it. A plugin that declares a name the daemon serves itself, `show bgp` for one, passes here. The dispatcher's own registry rejects that command entry later as a builtin conflict, and the plugin keeps running, so its alias sits on a command path the daemon answers. It can only ADD a name there, never take one: the exact-path check refuses a name the path already carries, `mergedAliases` keeps what the path held, and the name leaves with the plugin. Declaring a name a builtin already serves is a plugin defect, and the daemon logs it as `command registration rejected ... conflicts with builtin`. <!-- source: pkg/plugin/rpc/types.go -- PipeDecl, DeclareRegistrationInput --> <!-- source: internal/component/plugin/registration.go -- PluginRegistry.Register --> <!-- source: internal/component/plugin/server/command_registry.go -- CommandRegistry.Register, AddBuiltin --> <!-- source: pkg/plugin/sdk/sdk_types.go -- PipeDecl --> <!-- source: internal/component/plugin/server/startup.go -- validatePipeDecls, registerPluginPipes, commandPathKey --> #### Collision has two populations, because there are two resolution rules Reading collision as "any overlapping path" refuses the case the channel exists for. `show bgp` carries an alias named `summary`, and every `show bgp *` path overlaps `show bgp`, so that reading refuses `show bgp rpki | summary` before it is written. | Pair | Collides when | Why | |------|---------------|-----| | Alias against alias | the two sit on the SAME normalized command path | `lookupAlias` reads the set on the longest registered prefix and never falls back to a shorter one. A longer path SHADOWS a shorter one, and that is how `show bgp rpki` answers `summary` while `show bgp` answers one of its own | | Alias against pipe filter | their command paths OVERLAP | `foldFilters` resolves a command's own filter for the whole subtree the filter covers, so an overlapping filter makes the alias unreachable | | Alias against a built-in operator name | always | `ParsePipe` reads the built-in name first, so the alias is never reached | `aliasOnPath` is the exact-path reading and `filterShadowing` is the overlapping one. A declaration is also refused when one name appears twice on one path in one message. A declaration naming a command path the plugin did not declare is refused too. A refusal fails the whole Stage 1 registration, and the plugin does not start. Nothing is registered when any one declaration is refused, so a plugin never has to undo a partial registration. The message names the plugin, the command path and the alias name. The daemon log is where an operator reads it, because the engine stops a refused plugin before it can report anything itself. <!-- source: internal/component/command/alias.go -- aliasOnPath, filterShadowing, RegisterPluginAliases --> #### A declaration ADDS to a path. It never replaces what the path holds `commandRegistry.register` stores one value for each path, so writing a declared set straight into the registry drops every alias that path already answered to. `show bgp rpki` already carries the empty declaration the in-tree BGP command plugin puts on every child of `show bgp`. A plugin therefore declares onto an occupied path in the ordinary case. `mergedAliases` adds the declared names to what the path holds. Every declared name is checked against that same set first, so nothing merged this way replaces anything. Removal is by ENTRY for the same reason. `UnregisterPluginAliases` takes back the names one owner registered, and leaves the in-tree names and other owners' names in place. A path the owner created from nothing goes once its last entry is gone. Without removal a plugin that stops cannot start again, because the exact-path check then refuses it its own name. Two call sites remove it, and they are the two that remove the registry row and the runtime families. `rollbackStartupProcess` is both the failed-startup path and the config-reload stop path. The other is the family-conflict unwind inside `onRegistration`. <!-- source: internal/component/command/alias.go -- mergedAliases, UnregisterPluginAliases, pluginAliasPath --> <!-- source: internal/component/plugin/server/startup.go -- rollbackStartupProcess, engineStartupSink --> #### The engine derives the barrier that stops an alias below its command An alias on `show bgp rpki` is inherited by `show bgp rpki roa`, because `roa` registers nothing of its own and the lookup resolves the longest registered prefix. That offers the name on a leaf whose answer cannot carry it. `aliasBarriers` derives the answer from the plugin's own command list. It reads every command the plugin declared that sits strictly below a path carrying one of its aliases. Each such command that declares no alias itself gets an empty declaration. The in-tree form of the same barrier is written by hand in `peer.go`, over `cmdBgpChildren`. A plugin author writes nothing and does not have to know the resolution rule. <!-- source: internal/component/command/alias.go -- aliasBarriers --> <!-- source: internal/component/bgp/plugins/cmd/peer/peer.go -- cmdBgpChildren --> #### The pipe layer selects and re-sequences. It cannot compute This is the obligation every command that wants an alias owes, and it is a property of the PAYLOAD rather than of the pipe layer. `display` keeps the named keys and drops the rest. `fill` re-sequences what `display` did not name. `count` replaces the answer with a number. `first` and `last` cut the item list. `match` keeps the rendered lines that match a pattern. The format operators render one payload a different way. None of them renames a key, adds two numbers, counts the rows whose field holds a given value, or asks the handler for anything. So a command whose second view is a pipe alias MUST EMIT the aggregate fields beside the detail rows, as siblings at one level. `show bgp` has always done this, and `show bgp rpki` now does it too: `overviewCommand` writes `appendSummaryFields` and `appendCacheServers` into one record, and `| summary` selects the first half. Four of the seven RPKI aggregate fields are computed. `vrp-count` sums the two family counts, `sessions-established` counts the sessions in one state, `sessions-total` renames what `show bgp rpki status` calls `sessions`, and `validation-enabled` is a constant. The command computes them, and the alias only selects them. The expansion is a second copy of the field names, and `display` names keys and reports no miss. A field added to the payload and not to the expansion is therefore dropped from the alias in silence. A conversion owes three things: - one authored list of the field names. - an expansion built from that list rather than repeating it. - a test holding the list against the bytes the writer produces. RPKI does this with `summaryFieldNames` and `buildSummaryAliasExpansion`. Two questions send a candidate back to being a subcommand. Would the operator have to supply a value? An alias takes no argument. Does the answer need data the parent's payload does not carry? An alias reshapes what was returned. `show bgp rpki roa 192.0.2.0/24` fails both and stays a subcommand. `show bgp rpki cache` fails the second one: it reports `preference`, `session-id`, `serial` and three intervals that the bare answer does not carry. <!-- source: internal/component/bgp/plugins/rpki/rpki.go -- overviewCommand, appendSummaryFields, appendCacheServers, summaryFieldNames, buildSummaryAliasExpansion --> #### Where an alias resolves, and where it does not The chain is resolved in the process that parses it, and a plugin's alias lives in the daemon's registry alone. | Surface | Parses the chain | A plugin's alias works | |---------|------------------|------------------------| | `ze cli -c "<command>"`, and any SSH exec channel | the daemon, in `execMiddleware` | Yes | | The TUI a plain ssh client with a pty reaches | the daemon, which hosts the Bubble Tea model | Yes | | `ze cli` with no command argument | the CLIENT process, in `executeOperationalCommand` | No | | `cliClient.StreamMonitor`, for a streaming monitor command | the CLIENT process | No | On the two client-side rows the operator reads `pipe error: unknown pipe operator: <name>`, and Tab offers the name nowhere. The aliases compiled into the client resolve there, which is what hid the gap. Tab after the pipe character on `show bgp` offers `summary` and `peers` in the same client. The repair is a wire surface that carries the daemon's alias table to the client at session start, and it is NOT built. <!-- source: internal/component/ssh/ssh.go -- execMiddleware --> <!-- source: internal/component/cli/model_mode.go -- executeOperationalCommand --> #### Discovery: what each catalog reader can see A declaration a plugin makes travels TWO channels, and both carry the same slice. The Stage 1 registration message reaches a running daemon. The plugin's `registry.Registration` reaches anything that links the composition root, which is how a catalog generator reads a declaration without starting an engine. `Commands` carries the answer shape, the column order and the address fields; `Pipes` carries the aliases the plugin puts on its own commands. Until 2026-09-07 the alias had no second channel, so no reader outside a daemon could report one. | Surface | Reads | Alias | Shape, column order, address fields | |---------|-------|-------|-------------------------------------| | `show command help "<name>"` | the running daemon's registries | Yes, as a `pipe-aliases` list beside `pipe-filters` | Yes, as `answer-shape`, `column-orders` and `address-fields` | | Tab completion in the daemon-hosted TUI | the running daemon's registries | Yes | Yes | | `./le command list` | the compiled tree and `registry.All()` | Yes | Yes | | `ze help command --json` | the compiled tree and `registry.All()` | Yes | Yes | | the wiki catalog (`wikicatalog.Collect`) | the compiled tree and `registry.All()` | Yes | Yes | An EXTERNAL plugin is the one case the last three rows still cannot answer for. It registers nothing in the composition root, so its declaration exists on the Stage 1 message alone and a running daemon is the only reader of it. `show command help` lists an in-tree alias and a declared one the same way, and it listed neither before 2026-08. It reports the expansion beside the description. An alias takes no argument and names no other alias, so the chain it stands for is the whole of what the name does. `command.DeclaredForCommand` is the ONE reader of both channels, and the last three rows call it. So the three catalogs agree by derivation, not because a check reconciles them. It weighs the two channels by PATH LENGTH together, and not one after the other. Inside a daemon there is only one channel: `registerPluginShapes` (`internal/component/plugin/server/startup.go`) writes each Stage 1 declaration into the three registries at the plugin's own command path, and every later read resolves to the longest declared path that is a prefix of the command. A reader that asked the registries first and the plugin second gave an ancestor's declaration to a command that declares its own, and `show bgp rib help` published the eleven route columns of `show bgp rib` and offered `| resolve` on an answer holding no address. Two rules follow from reproducing what a daemon holds. A plugin declaration that names no column and no address field is a BARRIER and not an absence, so a command whose answer has no columns says so and inherits nothing. And a declaration that states no answer shape is passed over entirely, because `registerPluginShapes` writes nothing for one. `AliasesForCommand` is deliberately NOT changed to read the registration. It is what a running daemon reads, and a daemon has already written each STARTED plugin's aliases into the registry, so adding a registration's aliases there would offer an operator a name no running command answers to. The alias channel is weighed by PATH LENGTH beside the registry, as the three above are. `lookupAlias` reads the set on the longest registered prefix and never falls back to a shorter one, so a plugin's alias on a longer path SHADOWS an in-tree ancestor's rather than joining it, and the two merge only where they sit on ONE path. The barrier holds on the read side for the same reason: `show bgp rpki` answers to `summary`, and `show bgp rpki roa` answers to nothing at all, because the daemon writes an empty declaration on a command the same plugin declares below its alias path, and no path is longer than the command itself. The global aliases sit under both, as they sit under every registered set. `./le plugin declarations check` gates BOTH channels. It compares what a plugin's runner passes to `p.Run` against what its `registry.Registration` carries, identifying a command by its name and a pipe alias by the pair its registry keys it on, the command path and the name. A field both literals write as a call to one parameterless function is compared by that function's identity rather than by reading its body, because one function answers one slice. Anything else is compared in BOTH directions and over every field an entry states. The published catalog is generated from the registration, so a command the registration carries and the runner never declares is a phantom on the website, and a `Shape`, a `Columns` or a `Hidden` the two spell differently is a catalog describing an answer the daemon does not give. The gate pairs ONE runner literal to ONE registration literal in a package, and it refuses a package that builds two of either rather than pooling both sides. Pooled, two plugins wired to each other's declaration functions agree as a package and disagree one by one: dropping either plugin takes the surviving one's catalog entries with it while the daemon still serves them. The wiki catalog is NOT built from `ze help command --json`. Both join the registries in their own process, because an `internal` package cannot import `cmd/ze`'s main package. `compareWikiCatalogProducer` (`internal/le/docvalid/command_surfaces.go`) holds what is left of that split to one answer: the four main-package commands `wikicatalog.Collect` carries as literal entries, and the `le ` paths it drops. All three readers name a purely plugin-provided command. Each walks `registry.All()` after its own registry, because a plugin's command is dispatched through the plugin and reaches neither the YANG command tree nor the local command registry. Until 2026-09-07 the two published catalogs named none of them: `ze help command --json` answered 270 commands at `ze_core,ze_bgp` and `show bgp rpki roa` was not one, although it declares a shape, a column order and an address field. It answers 313 now. Two rules govern what such an entry carries. - A HIDDEN declaration is skipped by the two published catalogs, because the daemon already keeps one out of `VisibleCommandEntries` and out of completion. `./le command list` keeps it, because that inventory answers what ze REGISTERS. `request bgp adj-rib-in claim-replay` is the one such command. - The YANG node WINS wherever one exists. A plugin command can be modeled and still carry no wire method, `show vrrp interface` among them, so it arrives with an authored summary, long help and grammar already written. For a command no node models, `usage` carries the invocation form the plugin declares and `grammar` stays empty: a plugin declares its arguments as text, and a token list built from that text would state kinds nobody declared. A declared argument is spelled in ANGLE BRACKETS, because both catalogs publish the tokens verbatim and a bare identifier reads as a keyword an operator types. <!-- source: internal/component/command/declared.go -- DeclaredForCommand --> <!-- source: internal/component/plugin/registry/registry.go -- Registration.Commands --> <!-- source: internal/le/plugin/declarations/plugindeclarations.go -- Check --> <!-- source: internal/plugins/meta/cmd/help.go -- commandHelp, pipeAliasHelp, handleBgpCommandHelp --> <!-- source: internal/le/command/list/commandlist.go -- Collect, Answer --> <!-- source: internal/le/wikicatalog/catalog.go -- Collect, operatorsFor --> <!-- source: cmd/ze/help_command.go -- collectCommands, extractPipes --> #### A plugin declares its own answer shape Each `CommandDecl` in the Stage 1 message carries three optional fields: `shape`, `columns` and `address-fields`. An absent field is an undeclared field, so a plugin written before this channel existed keeps its old behavior. | Field | Holds | |-------|-------| | `shape` | `doc`, `map` or `tab`, the same three words the answer head uses on the wire | | `columns` | the answer's keys, in the order a person reads them. It needs a shape with rows | | `address-fields` | the keys whose value holds an address. It needs a shape | `validateShapeDecls` reads the three fields where `validatePipeDecls` reads the aliases. It refuses four declarations by name: - a spelling that is not one of the three words. - a column or address-field list with no shape. - a declaration on a blank command path. - a list or a name past its bound. The bounds are 64 columns and 16 address fields for one command, with each name 1 to 64 bytes. A refused declaration fails the plugin's startup and writes nothing. `registerPluginShapes` then writes under `startupRegistrationMu`, between the pipe aliases and the runtime families, and joins the same unwind. `UnregisterPluginShapes` takes the whole declaration back when the plugin stops. **A plugin can never panic the daemon with a declaration.** The three registries keep the panic on `declare`, which only Go compiled into the daemon reaches. The plugin route is `declareFor`, which is the same four cases with the panic replaced by an error. Two in-tree packages that disagree are still a Ze defect and still panic. Every declaration writes all THREE registries. A command that declares a shape and no column writes an EMPTY column declaration. That empty declaration is the barrier that stops the command inheriting its parent's order. Removal restores the empty declaration a shim left behind, rather than deleting the path. The declaration lives in the daemon's registry. So `| display <partial>` offers a plugin command's field names in the daemon-hosted session, and offers none in `ze cli` with no command argument. That is the same client-side gap the alias table above records, for the same reason. <!-- source: pkg/plugin/rpc/types.go -- CommandDecl --> <!-- source: internal/component/plugin/server/startup.go -- validateShapeDecls, validateDeclaredFieldName, registerPluginShapes --> <!-- source: internal/component/command/answer_shape.go -- RegisterPluginShapes, UnregisterPluginShapes --> <!-- source: internal/component/command/column_order.go -- declarationRegistry, declare, declareFor, withdraw --> <!-- source: internal/component/command/completer.go -- completeDisplayFields, completePipeForCommand --> <!-- source: internal/component/bgp/plugins/cmd/rib/rib.go -- registerPipeFilters, registerRibAnswerShapes --> <!-- source: internal/component/bgp/plugins/filter_irr/cmd_irr.go -- registerIRRShapes --> ### The chain over a row generator A handler that answers with a row generator runs the same chain, one record at a time. `applyPipesRecords` is the record half of `ApplyPipes`. `| match`, `| count`, `| first`, `| last`, `| display`, `| resolve` and `| origin` each act per record, so `| count` holds nothing and `| last 8` holds eight records. <!-- source: internal/component/command/pipe_records.go -- applyPipesRecords, applyRecordOp, recordsCounted, recordsLast --> A format operator changes no record. `RenderRecords` renders what the chain produced. It writes per record for one chain alone: `| ndjson`, over an answer that declares no column schema, whose chain folded no display metadata. Every other format needs a document. A column width needs every row, and metadata rides in the envelope. <!-- source: internal/component/command/render_records.go -- RenderRecords, streamsPerRecord, writeDocument --> `| table` and `| text` therefore collect. That cost is paid once, in the renderer, and the record stage forwards the records untouched. A chain that answers a document of its own is not filed under the command's envelope. `| count` is the one operator that does: it answers `{"count":N}` whatever it counted, and the whole-payload path replaces the payload for the same reason. `system command list | count` therefore answers `{"count":N}`, the same document every other command's `| count` answers, and not `{"commands":[{"count":N}]}`. <!-- source: internal/component/command/render_records.go -- chainAnswersItsOwnDocument, answerDocument --> <!-- source: internal/component/command/pipe.go -- applyCount --> <!-- rfc: none -- this is Ze's own command surface --> <!-- since: 2026-08-20 --> Authorization is decided once, at dispatch, and the rows are produced after that decision. That is what a built payload has always done, and a generator changes only how long the gap is. There is no per-row authorization, so a handler MUST NOT yield a row the caller was not already authorized to receive when the command was accepted. <!-- source: internal/component/plugin/server/dispatch.go -- dispatchCommandArgsResponse --> <!-- source: internal/component/plugin/server/system.go -- commandRows --> A chain the validator refuses answers one rejected row and pulls nothing, so an unreadable chain never reads as an empty answer. <!-- source: internal/component/command/pipe_records.go -- faultRecords --> <!-- source: internal/component/command/pipe.go -- ValidatePipes --> --- ## Managed Config RPCs RPCs for hub-client managed configuration. These operate over MuxConn after auth, separate from the plugin 5-stage protocol. | Verb | Direction | Payload | Response | |------|-----------|---------|----------| | `config-fetch` | Client to hub | `{"version":"<hash-or-empty>"}` | `{"version":"<hash>","config":"<base64>"}` or `{"status":"current"}` | | `config-changed` | Hub to client | `{"version":"<hash>"}` | `{}` | | `config-ack` | Client to hub | `{"version":"<hash>","ok":true}` or `{"version":"<hash>","ok":false,"error":"..."}` | `{}` | | `ping` | Either direction | `{}` | `{}` | Version hash is truncated SHA-256 (16 hex characters) of config bytes. <!-- source: pkg/fleet/envelope.go -- RPC payload types --> <!-- source: internal/component/plugin/server/managed.go -- hub-side handlers --> <!-- source: internal/component/managed/client.go -- client-side handlers --> --- ## MCP Methods The MCP Streamable HTTP transport (revision `2026-07-28`) is stateless and strictly client-to-server. Every message is its own HTTP POST, and the server never sends an independent JSON-RPC request on any stream. These methods are distinct from ze's own dispatcher commands (above). They are part of the MCP protocol contract, and each request carries the version and capabilities it speaks in its own `params._meta`. | Method | Direction | Purpose | |--------|-----------|---------| | `server/discover` | Client -> server | Advertise `supportedVersions`, `capabilities` (including `extensions["io.modelcontextprotocol/ui"]` for MCP Apps and `extensions["io.modelcontextprotocol/tasks"]` for background tasks), and `instructions`. Mandatory for a server to implement, and optional for a client to call | | `tools/list` | Client -> server | The tool inventory, derived from the command registry at call time, in a deterministic order. A descriptor carries `_meta.ui` only when the request declared the `io.modelcontextprotocol/ui` extension compatibly | <!-- doc-links: ignore (JSON-RPC method name, not a path) --> | `tools/call` | Client -> server | Run a tool. Answers `resultType: "task"` when the command's `ze:task-support` annotation is `required` and the request declared the tasks extension. Answers `resultType: "input_required"` when the tool needs a value the call did not supply | <!-- doc-links: ignore (JSON-RPC method name, not a path) --> <!-- source: internal/component/mcp/streamable_tools.go -- runMethod dispatch switch --> <!-- source: internal/component/mcp/discover.go -- serverDiscover --> <!-- source: internal/component/mcp/mrtr.go -- newInputRequiredResult, permitsInputRequired --> There are no server-initiated methods. `notifications/tasks/status` existed under the earlier revision and was removed with the session and the GET stream it required. `elicitation/create` survives, but no longer as a method. `elicitation/create` is now a **value** inside the `inputRequests` map of an `InputRequiredResult`. A server RETURNS that result from `tools/call` (or `resources/read`), and the <!-- doc-links: ignore (JSON-RPC method name, not a path) --> server does not send it. The client then retries the original request with `inputResponses`. See [MCP Elicitation](../../../guides/mcp/elicitation/index.md) for the full round trip. See [MCP Architecture Overview](https://github.com/ze-software/ze/blob/main/docs/architecture/mcp/overview.md#capability-negotiation) for how capabilities are declared per request. ## MCP Task Methods These are the `io.modelcontextprotocol/tasks` extension, not core protocol. A `tools/call` on a command annotated `ze:task-support required` creates a <!-- doc-links: ignore (JSON-RPC method name, not a path) --> background worker and returns a `CreateTaskResult` immediately. The client then polls for status, and the server pushes nothing. | Method | Direction | Purpose | |--------|-----------|---------| | `tasks/get` | Client -> server | Current state of a task by `taskId`. A terminal task carries its outcome here: `result` when completed, `error` when failed | | `tasks/update` | Client -> server | Answer a task's outstanding input requests. Ze raises none, so it verifies ownership and acknowledges with an empty result, ignoring unknown `inputResponses` keys | | `tasks/cancel` | Client -> server | Request cancellation of a working task | `tasks/list` and `tasks/result` were REMOVED this revision and are now unknown methods, answered HTTP 404 with `-32601`. A poll of `tasks/get` replaced the blocking `tasks/result`. That change is why a terminal state carries its payload. `tasks/list` was dropped outright. No method enumerates tasks now, so a client tracks the ids it was given. All three surviving methods require the request's `_meta["io.modelcontextprotocol/clientCapabilities"]` to declare `io.modelcontextprotocol/tasks` under `extensions`. The bare `tasks` member that the earlier revision used is no longer accepted. A request without the declaration is refused with `-32021` (`MissingRequiredClientCapability`) and HTTP 400, carrying `data.requiredCapabilities` in the extension shape. <!-- source: internal/component/mcp/tasks.go -- task registry --> <!-- source: internal/component/mcp/streamable_tools.go -- tasksGet, tasksUpdate, tasksCancel, failMissingTasksCapability --> <!-- source: internal/component/mcp/meta.go -- parseClientCapabilities --> Task creation is server-directed. There is no `task` member on `tools/call` <!-- doc-links: ignore (JSON-RPC method name, not a path) --> params. The server decides per tool from the YANG `ze:task-support` annotation: `required` always, `forbidden` never, and `optional` synchronous. A client that did not declare the extension gets the ordinary synchronous result, not an error. <!-- source: internal/component/mcp/streamable_tools.go -- callTool, createTask --> <!-- source: internal/component/mcp/tools.go -- groupTaskSupport --> ## MCP Resource Methods <!-- source: internal/component/mcp/resources.go -- resources/list, resources/read --> | Method | Direction | Description | |--------|-----------|-------------| | `resources/list` | Client -> server | List all available UI resources. The response comes from an embedded FS walk | | `resources/read` | Client -> server | Read a single resource by `ui://` URI. Returns content as text or base64 blob | Both results carry cache hints (see MCP Result and Error Envelope below). Neither method is gated on a client capability. `resources` is a member of `ServerCapabilities`, not of `ClientCapabilities` (whose members are `experimental`, `roots`, `sampling`, `elicitation` and `extensions`). No conformant client can therefore declare `resources`. A server that advertises the capability in `server/discover` serves it. <!-- source: internal/component/mcp/resources.go -- resourcesList, resourcesRead --> <!-- source: internal/component/mcp/meta.go -- clientCapabilities --> ## MCP Result and Error Envelope Every successful MCP result carries a `resultType` and `_meta["io.modelcontextprotocol/serverInfo"]`, stamped from one shared helper so no method can omit them. `resultType` is `complete` for a finished result. `resultType` is `input_required` for the MRTR interim result a handler produces when it needs a value the request did not supply. The shared helper preserves `input_required` and does not overwrite it. And a guard on the single path out of dispatch refuses to emit `input_required` on any method other than `prompts/get`, `resources/read` and `tools/call`. <!-- doc-links: ignore (JSON-RPC method name, not a path) --> <!-- source: internal/component/mcp/streamable_tools.go -- ok, resultMeta, runMethod --> <!-- source: internal/component/mcp/mrtr.go -- guardInputRequired, permitsInputRequired --> Four methods additionally carry the `CacheableResult` fields, `ttlMs` and `cacheScope`. Both are non-optional on those results. | Method | `ttlMs` | `cacheScope` | |--------|---------|--------------| | `server/discover` | `60000` | `private` | | `tools/list` | `60000` | `private` | <!-- doc-links: ignore (JSON-RPC method name, not a path) --> | `resources/list` | `3600000` | `private` | | `resources/read` | `3600000` | `private` | `tools/call` and every `tasks/*` method carry neither field, in either result <!-- doc-links: ignore (JSON-RPC method name, not a path) --> shape. Three reasons make that correct. `tools/call` is absent from the <!-- doc-links: ignore (JSON-RPC method name, not a path) --> specification's cacheable-operation list. Interim `input_required` results are explicitly not cacheable. And a result produced by an MRTR retry must not be cached at all. Ze therefore applies the hints from a per-method table on the way out of dispatch. The shared `ok()` responder does not apply them, because `tools/call` <!-- doc-links: ignore (JSON-RPC method name, not a path) --> also uses it. `cacheScope` is `private` on every cacheable result, which forbids a shared gateway or caching proxy from serving one authorization context's response to another. It is defence in depth, not the access control: per-request authentication remains the gate. <!-- source: internal/component/mcp/caching.go -- cacheTTLByMethod, stampCacheHints --> | Code | HTTP | Meaning | |------|------|---------| | `-32020` | 400 | `HeaderMismatch`: a required standard header is missing or disagrees with the body | | `-32021` | 400 | `MissingRequiredClientCapability`: `data.requiredCapabilities` names what the client must declare | | `-32022` | 400 | `UnsupportedProtocolVersion`: `data.supported` lists the server's versions, `data.requested` echoes the client's | | `-32602` | 400 for a malformed `params._meta`, 200 otherwise | Invalid params | | `-32601` | 404 | Unknown method, including `initialize` | | — | 405 | GET or DELETE to the MCP endpoint | <!-- source: internal/component/mcp/streamable.go -- handlePOST, httpStatusForDispatch --> <!-- source: internal/component/mcp/headers.go -- validateStandardHeaders --> <!-- source: internal/component/mcp/streamable_tools.go -- failUnsupportedVersion --> Three HTTP request headers are mandatory on every POST, and each one must agree with the body: - `MCP-Protocol-Version` mirrors the `_meta` protocol version. - `Mcp-Method` mirrors `method`. - `Mcp-Name` mirrors `params.name` for `tools/call` and `prompts/get`, and it <!-- doc-links: ignore (JSON-RPC method name, not a path) --> mirrors `params.uri` for `resources/read`. Ze decodes the `=?base64?...?=` sentinel in `Mcp-Name` first, then compares the decoded value with the body. <!-- source: internal/component/mcp/headers.go — mcpNameSource --> Tool descriptors in `tools/list` carry `_meta.ui.resourceUri` when the command <!-- doc-links: ignore (JSON-RPC method name, not a path) --> group has a `ze:ui-resource` YANG extension. The `_meta.ui` block is emitted unconditionally, and the `ui://` asset it points at is readable by every caller. --- **Last Updated:** 2026-07-29 --- ### Page: Command Ownership and Plugin Structure https://ze-software.net/architecture/command-ownership/ # Command Ownership and Plugin Structure <!-- source: ai/rules/plugins.md -- full rule --> <!-- source: internal/component/plugin/all/all.go -- blank imports that wire init() --> <!-- source: internal/le/plugin/imports/actions.go -- Answer --> ## The Folder Test Every feature in Ze passes two folder tests: **Copy test:** copy a plugin folder into the project, run codegen (`./le repository generate`), and the plugin's commands, YANG schema, and handlers are live. No manual wiring. **Delete test:** delete a plugin folder, run codegen, and every one of its features disappears. Every other plugin and the core keep working. No dangling references, no broken builds. These two tests are the load-bearing invariant of the architecture. Everything below exists to make them hold. ## Three Directories, Three Roles ``` internal/ core/ # shared primitives, no subsystem knowledge component/ # shared subsystem implementations and services plugins/ # self-contained command-only and full-subsystem owners ``` ### `core/` -- Infrastructure Primitives Leaf packages that provide reusable services. They depend on nothing else internal (or only on other `core/` packages). No subsystem-specific knowledge. Examples: `slogutil` (logging), `crashlog` (panic capture), `env` (environment variables), `family` (address families), `health` (health registry), `metrics` (prometheus), `events` (event bus), `diagnostic` (doctor codes), `textbuf` (string building), `paths` (file locations). ### `component/` -- Shared Subsystem Implementations Subsystem logic that multiple owners use can compose `core/` and other `component/` packages. A component owns configuration YANG when its data model belongs to the shared subsystem rather than one removable plugin. Examples include `bgp` (BGP engine), `config` (config system), `cli` (SSH CLI editor), `iface` (interface management), `host` (hardware detection), `firewall`, `doctor` (readiness checks), `web`, and `l2tp`. ### `plugins/` -- Self-Contained Feature Owners Each folder under `internal/plugins/` owns a removable feature surface. Two plugin shapes use this directory: - A **command-only plugin** owns command YANG, RPC handlers, and CLI registration. It delegates the operation to a `component/` or `core/` package. - A **full-subsystem plugin** also owns its protocol or service runtime, configuration, lifecycle, and state in the same folder. Both shapes can own user commands. Removing either folder removes all surfaces that belong to that owner. A full-subsystem plugin declares the commands it owns in one `commandDecls()` function in its own package. Two readers call it and neither copies it: the `registry.Registration` its `init()` builds, which a catalog generator reads from the linked composition root, and the `sdk.Registration` its runner passes to `p.Run`, which a running daemon reads over Stage 1. `./le plugin declarations check` fails with the package and the command when the two disagree. <!-- source: internal/plugins/host-cmd/cmd/register.go -- init --> <!-- source: internal/plugins/ospf/register.go -- runOSPFEngine, commandDecls --> <!-- source: internal/le/plugin/declarations/plugindeclarations.go -- Check --> ## Plugin Directory Layout ``` internal/plugins/<name>/ register.go # lifecycle or offline CLI registration, when needed *.go # runtime, config, and state for a full-subsystem plugin yang/ ze-<name>-*.yang # hand-written command or config definitions embed.go # GENERATED: //go:embed vars register.go # GENERATED: yang.RegisterModule() calls cmd/ register.go # pluginserver.RegisterRPCs() in init() handler.go # RPC handler functions ``` Command-only plugins omit subsystem runtime files. Full-subsystem plugins keep their runtime with the command and configuration surfaces that they own. ### YANG as Data, Not Code YANG files are declarative definitions, not Go code. They live in a `yang/` subfolder (not `schema/`) to make this clear. The Go glue files in `yang/` (`embed.go`, `register.go`) are generated by codegen from the `.yang` files present in the directory. The only hand-written file is the `.yang` itself. This means adding a YANG command schema to a plugin is: 1. Write `ze-<name>-cmd.yang` in the plugin's `yang/` folder 2. Run `./le repository generate` 3. Done: the codegen produces `embed.go`, `register.go`, and updates `all.go` ### How Codegen Enables the Folder Test The codegen (`internal/le/plugin/imports.Write`) scans the directory tree for: - Packages containing `yang.RegisterModule` calls -> adds to `all.go` schema imports - Packages containing `pluginserver.RegisterRPCs` calls -> adds to `all.go` RPC imports - `yang/` directories containing `.yang` files -> generates `embed.go` + `register.go` Because discovery is directory-based, copying a plugin folder in (or deleting it) and re-running codegen is all that's needed to wire (or unwire) the plugin. ## YANG Container Merge Owners declare their commands by re-stating the path from the verb root: ```yang module ze-host-cmd { namespace "urn:ze:host:cmd"; prefix hostcmd; import ze-extensions { prefix ze; } container show { container host { container cpu { ze:command "ze-show:host-cpu"; } } } } ``` The YANG loader unions same-named top-level containers across all registered modules. The owner module needs no `import` or `augment` of the central verb schema. Give each module a unique `namespace` and `prefix`. ## Verb-Root Anchors Multi-owner verbs (`show`, `clear`, `monitor`, `delete`, `set`, `update`) keep a bare anchor in the central package `internal/component/cmd/<verb>/`. The anchor declares only truly generic commands (cross-plugin aggregations, process-global introspection). Each owner container-merges its subtree onto the verb root. A central self-containment test bans every migrated token to prevent drift back. ### What Stays Central A command stays in the central verb package only when it has no single removable owner: - Aggregates a cross-plugin registry (`show policy list`, `show metrics name`) - Reads the generic core system (`show version`, `show health`, `show uptime`) - Is process-global (`show system memory`, `show system goroutines`) ## Self-Containment Invariant Two tests enforce the invariant per verb: **Central guard** (e.g. `cmd/show/yang/self_containment_test.go`): a banned-token map listing every owner-migrated command. If any banned token appears in the central YANG, the test fails. **Owner presence** (e.g. `plugins/host/yang/self_containment_test.go`): asserts the owner's YANG declares its commands. If the owner is deleted, the presence test vanishes with it (correct). If someone moves the command back to central, the presence test fails (incorrect, caught). When carving a new command out of a central schema: 1. Add the banned token to the central guard test 2. Add the presence assertion to the owner's YANG test 3. Both halves must exist before the carve is complete ## Handler Registration Handlers register via `pluginserver.RegisterRPCs()` in an `init()` function inside the plugin's `cmd/` package: ```go func init() { pluginserver.RegisterRPCs( pluginserver.RPCRegistration{ WireMethod: "ze-show:host-cpu", Handler: handleShowHostCPU, }, ) } ``` The package must be blank-imported (directly or transitively) from `internal/component/plugin/all/all.go`. The codegen scans for packages containing `pluginserver.RegisterRPCs` or `yang.RegisterModule` calls and generates the import list. ## What the Removal Test Forbids | Anti-pattern | Why it fails the removal test | |--------------|-------------------------------| | A plugin's command spelling in generic dispatch (`internal/component/plugin/server`) | Deleting the plugin leaves dead BGP or iface knowledge in shared code | | A plugin's subtree in a central verb schema, such as `show bgp ...` in `internal/component/cmd/show/yang/ze-cli-show-cmd.yang` | Deleting the plugin leaves a `show bgp` branch with no handler | | Plugin handlers registered from a central verb package (`internal/component/cmd/show`, `internal/component/cmd/delete`) | Deleting the plugin leaves the central package referencing gone symbols | | Help, usage or inventory strings that hardcode a plugin's commands in a generic package | Deleting the plugin leaves help advertising commands that no longer exist | | The CLI helper (`cmd/ze/internal/cmdutil`) special-casing a plugin's selectors | Selector handling is generic, and per-plugin knowledge belongs to the owner | ### What shared code may carry Generic command plumbing carries selector scope, not command spelling. The dispatcher extracts a typed selector value because a YANG `ArgDef` declares it, and it contains no plugin grammar: not the word `peer`, `bgp` or `bfd`. The classification rule is ownership before grammar. <!-- source: internal/component/plugin/server/command.go -- ArgDef selector extraction --> ## Finding the Owner: follow the code, not the wire method The `ze-<ns>:` prefix on a `WireMethod` is a label rather than an ownership claim, and it is often a legacy misnomer. The owner is what the handler actually calls. | Command (WireMethod) | What the handler calls | Owner | |----------------------|------------------------|-------| | `ze-show:ip-route`, `ze-show:neighbors`, `ze-show:kernel-routes` | `iface.ListKernelRoutes`, `iface.ListNeighbors` (kernel tables through the iface backend) | `internal/component/iface`, not central `show` and not the BGP RIB | | `ze-bgp:pool-stats` | `bgp/plugins/rib/pool` attribute-pool metrics | The BGP RIB plugin | | `ze-bgp:metrics-values`, `ze-bgp:metrics-list` | The generic core Prometheus registry (`internal/core/metrics`) | Generic, stays central | | `ze-bgp:subscribe`, `ze-bgp:unsubscribe` | The generic `pluginserver` subscription manager | Generic, stays central | | `ze-show:policy-list` | The cross-plugin filter-type registry (`registry.FilterTypesMap`) | Generic, stays central | A command is generic, and stays central, only when it has no single removable owner: it aggregates a cross-plugin registry, reads a generic core system, or is process-global (`show warnings`, `show health`, `subscribe`). Everything that reads one plugin's or one component's state belongs to that owner, whatever the `ze-<ns>:` label on its WireMethod says. ## Carving a Command Into Its Owner 1. **Handler.** Add `func init() { pluginserver.RegisterRPCs(...) }` and the handler in the owner package. When the owner package is already blank-imported, because it has a `register.go` the generator's `pluginDirs` finds or it sits in `rpcDirs`, the registration links with no generator and no manual-island change. The handler imports `plugin` and `pluginserver` plus the owner's own API, so it creates no import cycle. 2. **Schema, by container merge rather than `augment`.** Add `<owner>/yang/ze-<x>-cmd.yang`, a standalone module that re-declares the path from the root. The YANG loader unions same-named top-level containers across every registered module, so the owner module needs no `import` or `augment` of the central schema and has no base-module coupling. Give it a unique `namespace` and `prefix`, `import ze-extensions`, and add the embed var and the `yang.RegisterModule` call. A new `<owner>/yang/` package whose `register.go` imports `config/yang` is auto-discovered, so re-run the plugin import generator to refresh `internal/component/plugin/all/all.go`. 3. **Schema location.** The command YANG lives in `<owner>/yang/`, a sibling of `cli` and `cmd`, and never under `<owner>/cmd/yang/`. 4. **Both halves of the invariant.** The owner `yang/` gets a presence test asserting its command tokens are declared, and the central verb schema test bans the moved tokens. ### Unowned verb roots A verb whose subcommands belong to several owners, such as `monitor bgp`, `monitor vpn ipsec` and `monitor ping`, does not declare its root container inside any one plugin. Declaring it there means deleting that plugin deletes the whole verb. The root lives in a central, plugin-free package `internal/component/cmd/<verb>`: a `doc.go` that blank-imports its `yang/` subpackage. Each owner container-merges only its own subtree onto that root, and the central package holds no handlers. The root anchor stays even when it declares zero commands. Once every subcommand of a verb has carved out, the central verb schema is a bare `container <verb>` with no `ze:command` leaf of its own. `internal/component/cmd/clear` is the precedent: `clear interface counters` (iface), `clear dns cache` (resolve), `clear vpn ipsec sa` (ike), `clear l2tp ...` (l2tp) and `clear bgp rib ...` (bgp) are all owner-owned, so `ze-cli-clear-cmd.yang` declares only the bare anchor. Owners attach to it two ways, and the second has a hard dependency on it: - **Container merge.** The owner declares its own `container <verb> { container <noun> ... }` and the YANG loader unions same-named roots. iface, resolve and ike use this for `clear`. New carves use this shape, because it creates no base-module coupling. - **Augment.** The owner declares `augment "/<prefix>:<verb>"` against the anchor module. l2tp and bgp use this for `clear`, through `augment "/cliclearcmd:clear"`. An augment names its target module, so deleting the anchor breaks every augmenting owner's build. That is the concrete reason the bare anchor remains. ### Dedicated feature modules When one feature spreads across several verbs, a one-shot root command, a `show` view, a `monitor` stream and a `resolve` variant, the feature gets its own module `internal/component/<feature>` that owns every one of those commands, rather than scattering them across the verb packages. When two such modules would share low-level primitives, ping and traceroute both build ICMP echo packets and resolve targets, those primitives are extracted to an `internal/core/<x>` package such as `internal/core/probe`, so neither feature module depends on the other or on a central verb package. ## Registration Over Hardcoding: the CLI client too The registration discipline covers the CLI client model, not only the daemon's command and schema tree. The daemon registers streaming views generically with `pluginserver.RegisterMonitorProvider(MonitorProvider{Prefix, CreateFn})` and `RegisterStreamingHandler(prefix, handler)`, resolved by longest-prefix `matchesPrefix`. The Bubble Tea client mirrors this with its own view registry. The anti-pattern is each rich live view (dashboard, traceroute, ping, traffic) adding its own field, factory, state and dispatch to the core `cli.Model`, wired one at a time in `cmd/ze/hub/session_factory.go` and `internal/component/cli/client/main.go`. Every new view then edits the core struct in four or five places, which is the opposite of a core that discovers features through a registry. The shape in the tree is the client-side view registry in `internal/component/cli/view_registry.go`: `RegisterView(viewSpec{key, prefix, matches, start})`, `RegisteredViews()`, and a longest-prefix `resolveView` copied from `matchesPrefix`. Each view registers from its own `register_view_*.go` `init()` and hangs its session state off the single `Model.activeView` handle plus the generic `Model.viewFactories` store, with no per-feature field. Consumers iterate `cli.RegisteredViews()` and inject each factory by key through `SetViewFactory` rather than through typed setters. The `TestModelHasNoPerFeatureViewField` reflection guard fails when a per-feature field returns. <!-- source: internal/component/cli/view_registry.go -- RegisterView, RegisteredViews, resolveView --> <!-- source: internal/component/plugin/server/handler.go -- matchesPrefix --> <!-- source: internal/component/cli/model_test.go -- TestModelHasNoPerFeatureViewField --> ## The Removal-Compliance Guards In The Tree The first instance is `TestShowSchemaHasNoBGPPluginCommands` in `internal/component/cmd/show/yang/self_containment_test.go`. It asserts the central `show` verb schema declares no part of the `show bgp ...` subtree (`ze-rib-api:`, `ze-bgp:peer-`, `ze-show:bgp-decode`, `ze-show:bgp-encode`), because `show bgp rib ...` and `show bgp peer ...` are owned by `internal/component/bgp/plugins/cmd/{rib,peer}/yang`, and the offline `show bgp decode` and `show bgp encode` diagnostics are owned by `internal/component/bgp/cli/yang`. The owner half is `TestBGPToolsSchemaOwnsDecodeEncode`, which asserts the surface moved rather than vanished. Non-BGP owners share one general central guard, `TestShowSchemaHasNoMigratedOwnerCommands` in the same file. Its banned-token map grows by one entry per carved owner: flow export, RSVP-TE, LDP, policy routes, static, VPN IPsec, VPP, the iface kernel reads. Each owner's `yang/` package holds the matching presence test, such as `TestRSVPTECmdSchemaOwnsShowRSVPTE`. For `clear`, the pair is `TestClearSchemaHasNoMigratedOwnerCommands` and, for example, `TestResolveCmdSchemaOwnsClearDNSCache`. ## Summary: Where Things Go | Artifact | Location | Hand-written? | |----------|----------|---------------| | Implementation library | Shared code: `component/<name>/` or `core/<name>/`<br>Full-subsystem plugin: `plugins/<name>/` | Yes | | Config YANG (data model) | Shared subsystem: `component/<name>/yang/`<br>Full-subsystem plugin: `plugins/<name>/yang/` | Yes | | Command YANG (CLI tree) | `plugins/<name>/yang/`, never `<owner>/cmd/yang/` | Yes (`.yang` only) | | YANG embed + register | `plugins/<name>/yang/` | Generated | | RPC handlers | `plugins/<name>/cmd/`, or the owner package | Yes | | Offline CLI registration | `plugins/<name>/register.go` | Yes | | Help, usage and completion | Derived from the owner's registry and schema | Derived | | Doctor check and its unit test | The owner package | Yes | | Blank imports | `all.go` | Generated | --- ### Page: Route Selection https://ze-software.net/architecture/route-selection/ # Route Selection ## Overview Every route received by ze goes through two phases before it can become the best path for a prefix. Phase 1 determines whether the route is valid and eligible. Phase 2 determines which eligible route wins. A route that fails at any step gets a reason explaining why it was not selected. ## Design: Unified Rejection Reason Each non-best route carries a single reason (`uint8`) recording why it was not selected. The reason is set once: either during validation (the route is ineligible) or during best-path comparison (the route lost to a better candidate). The winning route has reason `none` (0). This is a single mechanism, not two separate ones. Whether a route was disqualified before the race or lost at step N of the race, the answer is the same type of value on the same field. ## Phase 1: Validation Routes that fail validation never enter best-path selection. | # | Reason | Check | RFC | Location | |---|--------|-------|-----|----------| | 1 | `nlri-syntax-invalid` | NLRI prefix length exceeds remaining bytes | 7606 | `message/rfc7606.go` | | 2 | `attr-structure-malformed` | Path attribute header/length out of bounds | 7606 | `message/rfc7606.go` | | 3 | `duplicate-mp-reach` | Multiple MP_REACH_NLRI or MP_UNREACH_NLRI | 7606 | `message/rfc7606.go` | | 4 | `attr-flags-invalid` | Well-known attribute missing Transitive or has Optional | 7606 | `message/rfc7606.go` | | 5 | `attr-value-invalid` | Per-attribute validation (ORIGIN range, AS_PATH structure, NEXT_HOP format, length checks for MED/LOCAL_PREF/AGGREGATOR/COMMUNITY/ORIGINATOR_ID/CLUSTER_LIST/EXT_COMMUNITY/LARGE_COMMUNITY, MP_REACH/MP_UNREACH structure) | 7606 | `message/rfc7606.go` | | 6 | `mandatory-attr-missing` | ORIGIN, AS_PATH, or NEXT_HOP absent (when required) | 4271 | `message/rfc7606.go` | | 7 | `family-not-negotiated` | MP_REACH/MP_UNREACH AFI/SAFI not in OPEN capabilities | 4271 | `reactor/session_validation.go` | | 8 | `as-loop` | Local ASN found in AS_PATH (AS_SEQUENCE or AS_SET) | 4271 S9 | Not yet implemented | | 9 | `originator-id-loop` | ORIGINATOR_ID matches local Router ID (iBGP only) | 4456 S8 | Not yet implemented | | 10 | `cluster-list-loop` | Local Router ID found in CLUSTER_LIST (iBGP only) | 4456 S8 | Not yet implemented | | 11 | `rpki-invalid` | Origin AS does not match any covering VRP | 6811 | `plugins/adj_rib_in/rib_validation.go` | <!-- source: internal/component/bgp/message/rfc7606.go -- RFC 7606 validation checks --> <!-- source: internal/component/bgp/reactor/session_validation.go -- family negotiation check (validateUpdateFamilies) --> ### RFC 7606 Error Escalation Validation collects all errors and applies the strongest action: | Action | Strength | Effect | |--------|----------|--------| | `none` | 0 | Route accepted | | `attribute-discard` | 1 | Malformed attribute removed, route continues | | `treat-as-withdraw` | 2 | Entire UPDATE treated as withdrawal | | `session-reset` | 3 | NOTIFICATION sent, session closed | Multiple errors in one UPDATE do not produce multiple reasons. The strongest action determines the outcome. Attribute-discard marks the specific attribute in-place (draft-mangin-idr-attr-tombstone-00) but the route itself continues. ### RPKI Validation RPKI validation is asynchronous with a 30-second fail-open timeout. Routes pending validation are held in a separate map. On timeout, the route is promoted with state `not-validated` and enters best-path selection normally. | State | Value | Meaning | |-------|-------|---------| | `not-validated` | 0 | Default or timeout (fail-open) | | `valid` | 1 | Origin AS matches a covering VRP | | `not-found` | 2 | No covering VRP exists | | `invalid` | 3 | Covering VRP exists but no AS match | | `pending` | 4 | Awaiting validation (internal only) | Only state 3 (`invalid`) produces a rejection reason. States 0, 1, and 2 allow the route to proceed to best-path selection. ## Phase 2: Best-Path Selection (RFC 4271 Section 9.1.2) Eligible routes compete pairwise. The loser at each step gets tagged with the step that eliminated it. Steps are evaluated in strict order; the first difference decides. | # | Reason | Rule | RFC | Notes | |---|--------|------|-----|-------| | 9 | `stale-deprioritized` | Route at or above depreference threshold loses to fresh route | 9494 | GR/LLGR stale-level; threshold = 2 | | 10 | `lost-local-pref` | Highest LOCAL_PREF wins | 4271 | Default 100 if absent | | 11 | `lost-as-path-length` | Shortest AS_PATH wins | 4271 | AS_SET counts as 1 | | 12 | `lost-origin` | Lowest ORIGIN wins (IGP=0 < EGP=1 < INCOMPLETE=2) | 4271 | | | 13 | `lost-med` | Lowest MED wins (same neighbor AS only) | 4271 | Compared only when first AS matches. Section 9.1.2.2 (c) gives a route that carries no MULTI_EXIT_DISC the lowest possible value, 0, so an absent attribute wins this step | | 14 | `lost-ebgp-over-ibgp` | eBGP preferred over iBGP | 4271 | eBGP = PeerASN != LocalASN | | 15 | `lost-igp-cost` | Lowest IGP cost to next-hop | 4271 | Not yet implemented | | 16 | `lost-router-id` | Lowest Router ID / ORIGINATOR_ID wins | 4271/4456 | Numeric IP comparison | | 17 | `lost-cluster-list-length` | Shortest CLUSTER_LIST wins | 4456 | Section 9 inserts this between RFC 4271 steps f) and g). Counted in CLUSTER_IDs; an absent attribute counts zero. Unconditional | | 18 | `lost-peer-address` | Lowest peer IP address wins (final tiebreak) | 4271 | Numeric IP comparison | <!-- source: internal/component/bgp/plugins/rib/ -- best-path selection implementation --> ### Candidate Extraction Before comparison, each route's attributes are extracted from pool handles into a flat `Candidate` struct: LocalPref, ASPathLen, FirstAS, Origin, MED, PeerASN, LocalASN, OriginatorIP, ClusterListEntries, PeerIP, PeerAddr, StaleLevel. Router ID and peer address comparisons use typed `netip.Addr` fields for zero-allocation numeric ordering. `ClusterListEntries` counts CLUSTER_IDs rather than octets and is `uint16`, because a CLUSTER_LIST can carry 16383 of them. ### Enforced Before Selection (Not Best-Path Reasons) These checks are implemented, but as ingress filters that reject the UPDATE before it reaches best-path selection, so they never appear as a reason in the enum above: - **AS loop detection** (own ASN in AS_PATH): rejected on ingress by the reactor loop filter on all sessions (RFC 4271 Section 9). - **Cluster-list loop detection** (RFC 4456, own Router ID in CLUSTER_LIST): rejected on ingress by the same loop filter (iBGP sessions). A route that survives this filter still has its CLUSTER_LIST length compared at step 17. - **Originator-ID loop detection** (RFC 4456, own Router ID as ORIGINATOR_ID): rejected on ingress by the same loop filter (iBGP sessions). - **OTC mismatch** (RFC 9234, Only-To-Customer attribute validation): enforced by the bgp-role plugin. <!-- source: internal/component/bgp/reactor/filter/loop.go -- ingress AS/cluster-list/originator-id loop filter --> <!-- source: internal/component/bgp/plugins/role/otc.go -- RFC 9234 OTC validation --> ### Not Yet Implemented - **IGP cost to next-hop** (step 15): requires IGP integration. This would be an additional best-path reason when implemented. ## Complete Reason Table All reasons in evaluation order. A route gets exactly one reason: the first check it fails. | Value | Reason | Phase | RFC | |-------|--------|-------|-----| | 0 | `none` | - | - | | 1 | `nlri-syntax-invalid` | Validation | 7606 | | 2 | `attr-structure-malformed` | Validation | 7606 | | 3 | `duplicate-mp-reach` | Validation | 7606 | | 4 | `attr-flags-invalid` | Validation | 7606 | | 5 | `attr-value-invalid` | Validation | 7606 | | 6 | `mandatory-attr-missing` | Validation | 4271 | | 7 | `family-not-negotiated` | Validation | 4271 | | 8 | `rpki-invalid` | Validation | 6811 | | 9 | `stale-deprioritized` | Selection | 9494 | | 10 | `lost-local-pref` | Selection | 4271 | | 11 | `lost-as-path-length` | Selection | 4271 | | 12 | `lost-origin` | Selection | 4271 | | 13 | `lost-med` | Selection | 4271 | | 14 | `lost-ebgp-over-ibgp` | Selection | 4271 | | 15 | `lost-igp-cost` | Selection | 4271 | | 16 | `lost-router-id` | Selection | 4271/4456 | | 17 | `lost-cluster-list-length` | Selection | 4456 | | 18 | `lost-peer-address` | Selection | 4271 | ## Implementation Notes - **Type:** `uint8` -- 19 values (0-18), extensible up to 255. - **One field, not two:** Biorouting splits this into `HiddenReason` (validation) and implicit sort order (selection). Ze uses one field because both give the same reason: why the route is not the best. - **String conversion:** Only on JSON output. Internal representation is always `uint8`. - **Cost:** One byte per route entry. Set once during validation or selection, never updated after. ## Related Documentation - `docs/architecture/route-types.md` -- route struct inventory and data flow - `docs/architecture/core-design.md` -- reactor, FSM, wire layer - `docs/architecture/wire/messages.md` -- BGP message parsing - `docs/architecture/plugin/rib-storage-design.md` -- RIB storage internals - `rfc/short/rfc4271.md` -- BGP-4 specification - `rfc/short/rfc7606.md` -- revised error handling for UPDATE messages - `rfc/short/rfc6811.md` -- RPKI-based origin validation --- ### Page: Ze System Architecture https://ze-software.net/architecture/system-architecture/ # Ze System Architecture **Status:** Superseded / legacy design note (kept for background only) **Last Updated:** 2026-01-30 **Purpose:** Describes Ze's hub/orchestrator mode with separate plugin processes --- > **Legacy document.** The hub/orchestrator concepts below are still broadly > accurate, but several concrete details have changed and its examples no longer > match the shipping system: > > - The daemon is started with `ze start <config-file>`. The bare `ze <config-file>` > launch form was removed from the CLI; the examples below have been corrected to > the surviving form. > - The `local-as` / `peer-as` / `peer-group` config grammar shown here has been > removed; local AS is now `session { asn { local ... } }` and peer groups are > `group` blocks (see [configuration syntax changes](../../reference/deprecations/index.md) > and [config syntax](https://github.com/ze-software/ze/blob/main/docs/architecture/config/syntax.md)). > - Shipped plugins (bgp, rib, gr) run in-process today rather than being forked > as separate `external ... { run "ze bgp" }` processes; the `external` block > is for third-party out-of-process plugins. > - The source tree is organised under `internal/core`, `internal/component`, and > `internal/plugins`; there is no `internal/bgp/...` layout as sketched below. > > For the current architecture see [Core Design](https://github.com/ze-software/ze/blob/main/docs/architecture/core-design.md) (canonical) and > [Hub Architecture](https://github.com/ze-software/ze/blob/main/docs/architecture/hub-architecture.md). --- ## Build personalities The repository builds `ze` and `le` from the one `cmd/ze` codebase. The root `./ze` and `./le` launchers execute the cached `bin/ze` and `bin/le` personalities and build only when that cache is absent. `./le --name <name>` takes one session out of that cache. The launcher consumes the option, builds `bin/le-<name>/le` with the same tags and toolchain pin as the shared build, and rebuilds it on every call. The file keeps the name `le` because `defaultDispatch` selects the personality with `registry.LookupRoot(binaryName())`, so the session name goes on the directory. The launcher carries the name into the process, and `refuseWrongBuildName` refuses to answer when the running binary is a different build. `./le --update` moves the cache forward instead of stepping around it. It builds the working tree beside `bin/le` and renames the result into place, so a peer session executing the old binary keeps its inode. The launcher never rebuilds on its own, because one peer's unfinished source would then fail every call in every session. About one call in sixteen compares the binary with the build inputs, after the command has answered. It prints one stderr line when a file git holds unmodified is newer. <!-- source: le -- the --name option; cmd/ze/le_build_name.go -- refuseWrongBuildName --> Both personalities use the command registry and pipe engine. Their composition roots remain separate: a normal `ze` build imports no `internal/le` package, while the non-default `ze_le` build companion imports `internal/le/register.go` and exposes its inventory under `ze le`. Shipped builds do not enable `ze_le`. <!-- source: cmd/ze/ze_le_register.go --> <!-- source: internal/le/leroot/dispatch.go -- Dispatch --> --- ## Overview Ze supports two operating modes: | Mode | Trigger | Description | |------|---------|-------------| | **In-process** | `bgp { }` block in config | BGP daemon with in-process plugins (simpler, default) | | **Hub mode** | `plugin { external ... }` block | Hub orchestrates separate plugin processes (this doc) | **This document describes Hub mode.** In hub mode, Ze runs as a **hub process** (`ze`) that orchestrates separate **plugin processes** communicating via pipes. This architecture enables: <!-- source: internal/component/plugin/server/ -- plugin server orchestration --> - Language freedom (plugins can be Go, Python, Rust, etc.) - Crash isolation (BGP crash doesn't kill RIB) - Independent development and testing - Third-party extensibility ### Request Metadata Ownership Request-scoped metadata is owned by the transport wiring, not by command payloads. REST, gRPC, SSH exec, and SSH interactive sessions extract trusted caller information (`username`, `remoteAddr`, request cancellation context) at the edge, then pass it through the shared API engine or dispatcher. The hub composition root converts that metadata into `pluginserver.CommandContext`; builtin handlers, subsystem dispatch, and plugin RPC routing derive child contexts from it. Plugin JSON payloads and command strings do not carry or override identity metadata. ``` ┌─────────────────────┐ │ ze start config.conf│ │ (hub) │ └──────────┬──────────┘ │ ┌──────────────────────────┼──────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ ze bgp │ │ ze rib │ │ ze gr │ │ (process) │ │ (process) │ │ (process) │ └─────────────┘ └─────────────┘ └─────────────┘ ``` --- ## Running Ze ### Basic Usage ```bash # Start Ze with a config file ze start config.conf # The hub process: # 1. Parses the config file # 2. Forks plugin processes (ze bgp, ze rib, etc.) # 3. Routes config to each plugin # 4. Coordinates startup via 5-stage protocol # 5. Routes commands and events between plugins ``` ### Process Hierarchy When running, you'll see these processes: ``` $ ps aux | grep ze user 1234 ze start config.conf # Hub process user 1235 ze bgp # BGP protocol handler user 1236 ze rib # Adj-RIB tracking user 1237 ze gr # Graceful Restart user 1238 /opt/acme/plugin # Third-party plugin ``` --- ## Configuration File The hub parses the entire config file (like VyOS): 1. Parse config syntax 2. Validate against combined YANG schema (all plugins) 3. Convert to internal map-of-maps structure 4. Route JSON subtrees to plugins based on handler registration The config file has three sections, parsed in order: ### Section 1: Environment Global settings applied before forking processes. The block is `environment`, modeled in `ze-hub-conf.yang` like every other top-level block. ``` environment { log { level debug } daemon { pid /var/run/ze.pid } } ``` An older `env { }` block existed for a second runtime that parsed the config itself. That runtime is deleted, and `env { }` is not a top-level keyword: it fails `ze config validate` and it fails at boot, with the same message. ### Section 2: Plugin Declarations Which processes to fork. Uses YANG schema from `ze-plugin-conf.yang`. ``` plugin { # Built-in plugins (shipped with ze) external bgp { run "ze bgp"; } external rib { run "ze rib"; } external gr { run "ze gr"; } # Third-party plugins external acme { run "/opt/acme/monitor-plugin"; respawn true; # Restart if it exits, if the plugin permits it timeout 60; # Startup timeout } } ``` A plugin declares in its Stage-1 registration what its own failure means: `restart`, `ignore` or `fatal`. That declaration decides what ze does. The `respawn` leaf states what the operator expects, and it can only ask for less than the declaration permits: `respawn false` leaves a plugin stopped that would have been started again, and `respawn true` against a plugin that declares it must not be restarted stops ze at startup with an error naming both sides. ### Section 3: Plugin Configuration Configuration for each plugin. Routed by the hub to the appropriate process. ``` # Routed to ze bgp (handles "bgp" container per ze-bgp-conf.yang) bgp { local-as 65001; router-id 1.1.1.1; peer transit-a { remote { ip 192.0.2.1; as 65002; } passive; capability { # GR plugin handles this path (augments BGP schema) graceful-restart { enabled true; restart-time 120; } } } peer-group upstream { peer-as 65000; } } # Routed to ze rib (handles "rib" container per ze-rib.yang) rib { # RIB-specific settings } # Routed to /opt/acme/plugin (handles "acme" container per acme.yang) acme { endpoint "https://monitor.example.com"; interval 30; } ``` --- ## Plugin Types ### Built-in Plugins Shipped with Ze, same binary: | Plugin | Binary | Purpose | |--------|--------|---------| | `bgp` | `ze bgp` | BGP protocol, sessions, FSM, peer-to-peer routing | | `rib` | `ze rib` | Adj-RIB-Out tracking, sent-route replay on reconnect | | `adj-rib-in` | `ze adj-rib-in` | Adj-RIB-In storage (raw hex), received-route replay | | `gr` | `ze gr` | Graceful Restart capability injection | ### Third-Party Plugins Any executable that speaks the plugin protocol: ``` plugin { external my-plugin { run "/path/to/plugin"; respawn true; # Only if the plugin declares failure-policy restart } } my-plugin { # Config routed here based on plugin's YANG schema } ``` ### Augmenting Plugins Some plugins (like GR) don't have their own root config block. They **augment** another plugin's schema: ```yang // ze-gr.yang augment "/bgp:bgp/bgp:peer/bgp:capability" { container graceful-restart { leaf enabled { type boolean; } leaf restart-time { type uint16; } } } ``` Config for augmenting plugins appears nested within the augmented schema: ``` bgp { peer transit-a { remote { ip 192.0.2.1; as 65002; } capability { graceful-restart { # Handled by ze gr, not ze bgp enabled true; } } } } ``` **How augmenting plugins work:** GR registers handler for `bgp.peer.capability.graceful-restart`. Hub sends just that JSON subtree to GR: ```json {"enabled": true, "restart-time": 120} ``` GR then uses the capability API to inject the capability: ``` # GR sends command: capability hex 64 0078 peer 192.168.1.1 │ │ └─ target peer │ └─ restart-time (120) in 12-bit format └─ capability code 64 (graceful restart, RFC 4724) ``` BGP stores registered capabilities and includes them in OPEN messages to peers. ### GR Plugin Coordination GR plugin coordinates with BGP and RIB via commands and events: ``` ze gr ze (hub) ze bgp │ │ │ │◄── config (JSON subtree) ───│ │ │ │ │ │── capability hex 64 ... ───►│─── (routes to BGP) ────────►│ │ peer 192.168.1.1 │ (BGP stores for OPEN) │ │ │ │ │── request subscribe bgp.peer.*►│ │ │ │ │ │ │◄── event bgp.peer.restart ──│ │◄── event bgp.peer.restart ──│ │ │ │ │ │── rib defer peer X ────────►│─────────────────────────────│──► ze rib │ │ │ ``` **Key points:** - GR uses `capability hex <code> <value> peer <addr>` format - Hub routes capability commands to BGP - GR subscribes to peer events (restart, up, down) - GR coordinates with RIB for route deferral during restart --- ## CLI Commands See [Command Ownership](../command-ownership/index.md) for the plugin self-containment pattern: how each component owns its CLI commands, YANG schema, and tests. ### Command Routing CLI commands are routed to plugins by prefix: ```bash # Routed to ze bgp ze bgp peer list ze bgp peer upstream1 show ze send bgp upstream1 update ... # Routed to ze rib ze rib show ze rib replay upstream1 # Routed to hub (system commands) ze system schema list ze system process list ze config reload ``` ### How CLI Works ``` ┌─────────────────────────────────────────────────────────────────────┐ │ $ ze bgp peer list │ │ │ │ 1. CLI connects to daemon via SSH (127.0.0.1:2222) │ │ 2. CLI sends: bgp peer list │ │ 3. Daemon looks up "bgp" in handler map → ze bgp process │ │ 4. Daemon forwards command via stdin to ze bgp │ │ 5. ze bgp executes, sends response via stdout │ │ 6. Daemon returns response to CLI via SSH session │ │ 7. CLI displays result │ └─────────────────────────────────────────────────────────────────────┘ ``` **Same binary, two modes:** - `ze start config.conf` → starts as daemon, listens on SSH port - `ze bgp peer list` → connects to running daemon as SSH client SSH target configurable via env vars `ze_ssh_host` and `ze_ssh_port` ### System Commands Commands handled directly by the hub: ```bash # List registered schemas ze system schema list ze-bgp: bgp, bgp.peer, bgp.peer-group ze-rib: rib ze-gr: bgp.peer.capability.graceful-restart # Show a schema ze system schema show ze-bgp # List running processes ze system process list bgp: pid=1235 state=ready rib: pid=1236 state=ready gr: pid=1237 state=ready # Reload configuration ze config reload ``` --- ## Event Flow Plugins communicate via events routed through the hub. ### Event Subscription Plugins subscribe to events during startup: ``` # ze rib subscribes to BGP events request subscribe bgp.event.* ``` ### Event Publishing When something happens, plugins publish events: ``` # ze bgp publishes peer state change event bgp.peer.up peer=192.0.2.1 asn=65002 ``` ### Event Routing Hub routes events to subscribers: ``` ze bgp ze (hub) ze rib │ │ │ │ (peer establishes) │ │ │── event bgp.peer.up ───────>│ │ │ peer=192.0.2.1 │ │ │ │── event bgp.peer.up ───────>│ │ │ peer=192.0.2.1 │ │ │ │ │ (UPDATE received) │ │ │── event bgp.update ────────>│ │ │ {...} │── event bgp.update ────────>│ │ │ {...} │ ``` --- ## Startup Sequence ### 10-Step Protocol ``` 1. Hub parses environment { } → Set global settings (api-socket, log level, etc.) 2. Hub parses plugin { } block → Build process list from ze-plugin-conf.yang 3. Hub forks each plugin → ze bgp, ze rib, ze gr, third-party, ... 4. Each plugin: Stage 1 → Declare YANG module + handlers 5. Hub registers schemas → Build handler routing table (SchemaRegistry) 6. Hub parses remaining config → Full parse, validate against combined YANG 7. Hub converts to JSON → Map-of-maps structure 8. Hub routes JSON to plugins → Each plugin gets its subtree (Stage 2) 9. Plugins: Stage 3-4 → Capability declarations, registry sharing 10. Plugins: Stage 5 → Ready, start operating ``` After startup, GR and other plugins use commands to configure BGP: - `capability hex <code> <value> peer <addr>` → inject capabilities - Plugins subscribe to events for runtime coordination ### 5-Stage Protocol (Per Plugin) Each plugin follows this protocol with the hub: | Stage | Direction | Content | |-------|-----------|---------| | 1 | Plugin → Hub | `declare schema yang <module>`, `declare schema handler <path>`, `declare priority <num>`, `declare cmd <name>`, `declare done` | | 2 | Hub → Plugin | Initial commit: `config verify` → plugin queries live/edit → `config apply` → `config done` | | 3 | Plugin → Hub | `capability hex ...`, `capability done` | | 4 | Hub → Plugin | `registry cmd ...`, `registry done` | | 5 | Plugin → Hub | `ready` <!-- source: internal/component/plugin/registration.go -- 5-stage protocol parsing --> <!-- source: internal/component/plugin/startup_coordinator.go -- startup coordination --> | **Priority:** Determines verify/apply order. Lower = first. Example: BGP=100, RIB=200, GR=300. --- ## Config Notification to Plugins ### Pull Model (Hub Never Pushes) **Hub notifies plugins of config changes, plugins query for config data.** On commit (startup, SIGHUP, or `ze config commit`), hub sends `config verify` / `config apply` notifications. Plugins query hub for config. Based on handler registration, plugins query for their JSON config: | Handler | Plugin receives | |---------|-----------------| | `bgp` (root) | Entire `bgp { }` block as JSON | | `bgp.peer.capability.graceful-restart` (sub-root) | Just that subtree as JSON | ### On-Demand Query Plugins query hub for specific config paths using text protocol: ``` # Query live (running) config: #1 query config live path "bgp.peer[address=192.0.2.1]" @1 done data '{"address": "192.0.2.1", "peer-as": 65002, "timers": {...}}' # Query edit (candidate) config: #2 query config edit path "bgp.peer[address=192.0.2.1]" @2 done data '{"address": "192.0.2.1", "peer-as": 65003, "timers": {...}}' ``` Hub stores config as map-of-maps internally, provides JSON in `data` field. --- ## Live/Edit Configuration (VyOS-style) Hub maintains two configuration states: | State | Purpose | |-------|---------| | **Live** | Running configuration (what plugins are currently using) | | **Edit** | Candidate configuration (being modified, not yet applied) | ### Commit Workflow ``` 1. User modifies edit config (via CLI or file) 2. User requests commit 3. For each plugin (by priority, lower first): a. Hub sends: config verify b. Plugin queries hub for live and edit config (its section) c. Plugin computes diff using shared library d. Plugin validates changes are acceptable e. Plugin responds: done or error 4. If all plugins verify ok: a. For each plugin (by priority): - Hub sends: config apply - Plugin applies changes - Plugin responds: done b. Edit becomes new live 5. If any verify fails: a. Hub aborts commit b. Edit unchanged, live unchanged ``` **Priority examples:** BGP=100 (first), RIB=200, GR=300 (last). ### Plugin Diff Responsibility **Hub provides:** Raw config states (live and edit) on request. **Plugin is responsible for:** 1. Query the config sections it needs 2. Compute diff using shared library code 3. Validate and apply changes ### Shared Diff Library Plugins use shared library code (not reimplemented per plugin) to compute differences: ``` Location: internal/component/config/diff/ Usage (Go): // Send query, receive JSON in data field live := sendQuery("query config live path \"bgp.peer\"") edit := sendQuery("query config edit path \"bgp.peer\"") changes := diff.Compare(live, edit) // changes = []Change{{Action: "create", Path: "bgp.peer[addr=X]", Data: {...}}, ...} ``` This library is part of the Ze codebase, available to all plugins. --- ## YANG Schema See [Command Ownership](../command-ownership/index.md) for how command YANG schemas use container merge to live in their owning component rather than a central verb package. ### What YANG Provides - Type validation (ranges, patterns, enums) - Cross-reference validation (leafref) - Schema-driven config routing ### Schema Registration During Stage 1, plugins declare their YANG schema: ``` declare schema yang ze-bgp declare schema handler bgp declare schema handler bgp.peer declare done ``` ### Existing YANG Modules | Module | Location | Defines | |--------|----------|---------| | `ze-types` | `yang/ze-types.yang` | Common types (asn, ip-address, etc.) | | `ze-bgp-conf` | `internal/component/bgp/yang/ze-bgp-conf.yang` | `container bgp` with peers, families | | `ze-plugin-conf` | `internal/component/plugin/yang/` | `container plugin` for process declarations <!-- source: internal/component/bgp/yang/ -- BGP YANG schemas --> <!-- source: internal/component/plugin/yang/ -- plugin YANG schemas --> | | `ze-rib` | `internal/component/bgp/plugins/rib/yang/ze-rib.yang` | Augments `ze-bgp-conf` with `container rib` | | `ze-graceful-restart` | `internal/component/bgp/plugins/gr/yang/ze-graceful-restart.yang` | Augments `ze-bgp-conf` for graceful-restart | | `ze-hostname` | `internal/component/bgp/plugins/hostname/yang/ze-hostname.yang` | Augments `ze-bgp-conf` for FQDN capability | **Note:** Plugin YANG schemas augment `ze-bgp-conf` to extend the configuration tree. Each plugin owns its YANG in a `schema/` subdirectory. ### YANG Augment Merging Plugins can augment other plugins' YANG schemas. Hub merges all YANG modules into a single consistent view: 1. Each plugin declares its YANG module in Stage 1 2. Hub collects all modules 3. Hub merges augments into base modules 4. Hub validates combined schema is consistent **Conflict handling:** If two plugins define conflicting augments (same path, different definitions), hub refuses to start. The plugins are incompatible. --- ## Package Structure **Planned (aspirational).** This is the target package structure, not the current layout. See `docs/architecture/overview.md` for the actual directory structure. ``` internal/ ├── hub/ # Hub/orchestrator (protocol-agnostic) │ ├── hub.go # Core hub │ ├── process.go # Fork and manage child processes │ ├── router.go # Route commands/events │ └── config.go # Parse env and plugin blocks │ ├── plugin/ │ ├── bgp/ # BGP plugin (moved from internal/bgp/) │ │ ├── message/ # BGP wire format │ │ ├── attribute/ # Path attributes │ │ ├── nlri/ # NLRI types │ │ ├── capability/ # BGP capabilities │ │ ├── fsm/ # State machine │ │ ├── rib/ # Peer-to-peer routing (moved from internal/rib/) │ │ └── reactor/ # BGP-specific reactor (moved from internal/reactor/) │ │ │ ├── rib/ # Adj-RIB tracking plugin │ │ ├── rib.go │ │ └── storage/ │ │ │ ├── gr/ # Graceful Restart plugin │ │ │ ├── server.go # Plugin server (reused by hub) │ ├── handler.go # Command/event dispatch │ ├── schema.go # SchemaRegistry │ └── subsystem.go # 5-stage protocol │ ├── yang/ # YANG loader and validator │ ├── loader.go │ └── validator.go │ └── config/ # Config parsing (shared) ``` --- ## Signals | Signal | Handler | Action | |--------|---------|--------| | `SIGHUP` | Hub | Reload configuration | | `SIGTERM` | Hub | Graceful shutdown (notify all plugins) | | `SIGINT` | Hub | Graceful shutdown | | `SIGUSR1` | Hub | Dump state/metrics | ### Config Reload (SIGHUP) ``` 1. Hub receives SIGHUP 2. Hub re-parses config file 3. Hub diffs current vs new config 4. Hub sends verify/apply for changes to affected plugins 5. Plugins apply changes (add/remove peers, etc.) ``` --- ## Benefits of This Architecture | Benefit | Description | |---------|-------------| | **Crash Isolation** | BGP crash doesn't affect RIB; processes restart independently | | **Language Freedom** | Plugins can be written in any language | | **Independent Development** | Test and develop plugins separately | | **Third-Party Extensibility** | Anyone can write plugins | | **Resource Limits** | Each process can have memory/CPU limits | | **Debugging** | Attach debugger to single process | | **Hot Reload** | Replace plugin binary without full restart (future) | --- ## Security Model ### Privilege Dropping (ExaBGP Pattern) Ze follows the standard Unix daemon privilege separation model: 1. Start as root (or with `CAP_NET_BIND_SERVICE`) to bind port 179 2. Bind the BGP listening socket 3. Drop privileges to the configured user/group 4. All subsequent work -- including plugin spawning -- runs as the unprivileged user The target user/group is configured via environment variables: | Variable | Underscore form | Purpose | |----------|-----------------|---------| | `ze.user` | `ze_user` | User to switch to after port binding | | `ze.group` | `ze_group` | Group to switch to (default: primary group of user) | When `ze.user` is not set, no privilege dropping occurs. Implementation: `internal/core/privilege/` -- calls `setgid` then `setuid` after `reactor.Start()` binds port 179. <!-- source: internal/core/privilege/ -- privilege dropping --> ### Plugin TLS Transport External plugins connect back to the engine via TLS. The engine binds TLS listeners (configured via `plugin { hub { server <name> { ip ...; port ...; secret ...; } } }`), forks child processes with `ZE_PLUGIN_HUB_HOST`/`ZE_PLUGIN_HUB_PORT`/`ZE_PLUGIN_HUB_TOKEN`/`ZE_PLUGIN_CA_PEM` env vars, and waits for authenticated connect-back. `ZE_PLUGIN_CA_PEM` carries the certificate authority root that issued the listener's certificate, and the SDK validates the chain against it and nothing else; the full contract is in [Process Protocol](https://github.com/ze-software/ze/blob/main/docs/architecture/api/process-protocol.md). Each plugin uses a single bidirectional TLS connection with MuxConn for concurrent RPCs. <!-- source: internal/component/plugin/acceptor.go -- hub TLS listener --> <!-- source: pkg/plugin/rpc/ -- MuxConn for concurrent RPCs --> ### Plugin Process Isolation Each external plugin runs in its own process group (`Setpgid`) for clean signal handling and inherits the daemon's (already-dropped) uid/gid. All plugins run as the same unprivileged user. <!-- source: internal/component/plugin/process/ -- process isolation --> --- ## Example Session ```bash # Start Ze $ ze start /etc/ze/config.conf [hub] Starting with config /etc/ze/config.conf [hub] Forking ze bgp (pid 1235) [hub] Forking ze rib (pid 1236) [hub] Forking ze gr (pid 1237) [hub] All plugins ready # Check status $ ze system process list bgp: pid=1235 state=ready uptime=5m rib: pid=1236 state=ready uptime=5m gr: pid=1237 state=ready uptime=5m # List BGP peers $ ze bgp peer list 192.0.2.1 AS65002 Established 5m 192.0.2.2 AS65003 Active - # Show routes $ ze rib show Prefix Next-Hop AS-Path Peer 10.0.0.0/24 192.0.2.1 65002 192.0.2.1 10.0.1.0/24 192.0.2.1 65002 65004 192.0.2.1 # Reload config $ ze config reload [hub] Reloading configuration [hub] Added peer 192.0.2.3 [hub] Reload complete # Graceful shutdown $ kill -TERM $(pgrep -f "ze start config.conf") [hub] Received SIGTERM, shutting down [hub] Notifying plugins... [bgp] Sending NOTIFICATION to peers [hub] All plugins stopped ``` --- ## Related Documents - [Core Design](https://github.com/ze-software/ze/blob/main/docs/architecture/core-design.md) - Canonical architecture (describes in-process mode) - [Hub Architecture](https://github.com/ze-software/ze/blob/main/docs/architecture/hub-architecture.md) - Hub mode internal design details - [Process Protocol](https://github.com/ze-software/ze/blob/main/docs/architecture/api/process-protocol.md) - 5-stage protocol specification - [YANG Config Design](https://github.com/ze-software/ze/blob/main/docs/architecture/config/yang-config-design.md) - Schema design - Config Dispatch - mode selection by config content (completed). This was summary 189, retired on 2026-08-01 and not carried into [Design History](https://github.com/ze-software/ze/blob/main/docs/../plan/learned/DESIGN-HISTORY.md), whose header gives the git-recovery route --- **Last Updated:** 2026-01-30 --- ### Page: .ci Test File Format https://ze-software.net/architecture/testing/ci-format/ # .ci Test File Format The `.ci` format is used by Ze's test runner to define functional tests. It supports embedded files (Tmpfs), test options, expectations, and commands. > For the execution architecture (how tests are scheduled and run concurrently) and the web `.wb` format, see [`runner-architecture.md`](https://github.com/ze-software/ze/blob/main/docs/architecture/testing/runner-architecture.md). ## Syntax Overview All lines use key=value format with `:` separators: ``` action=type:key=value:key=value:... ``` | Action | Purpose | |--------|---------| | `stdin=` | Embed stdin content for processes | | `tmpfs=` | Embed file content inline | | `option=` | Test configuration | | `cmd=` | Commands (API, shell, foreground/background) | | `expect=` | Expectations to validate | | `await=` | Block until the daemon's stderr carries a line, then tear down (deterministic fence) | | `reject=` | Negative expectations (fail if matched) | | `action=` | Actions (send notification, raw bytes) | | `http=` | HTTP endpoint checks and readiness polls | <!-- source: internal/test/runner/record_parse.go -- parseAndAdd, CI file parsing --> <!-- source: internal/test/tmpfs/tmpfs.go -- Tmpfs, File, Parse --> ## Key Concepts ### An unparseable test file fails; it never hides or vanishes A file that does not parse is recorded as a **permanent failure** and discovery continues. It is never dropped, and it never aborts the rest of the directory. All three discoverers behave identically. | Discoverer | Format | Marker | |------------|--------|--------| | `EncodingTests.Discover` | `.ci`, suites rooted by `registerCIRoot` (encode, plugin, ui, ...) | `Record.ParseFailed` + `State=StateFail` + `FailureType=parse_error` | | `ParsingTests.Discover` | `.ci` (parse suite) | `parsingTest.ParseError` | | `ParsingTests.Discover` | legacy `valid/*.conf` + `invalid/*.conf` with a companion `.expect` | `parsingTest.ParseError` | | `DecodingTests.Discover` | `.ci` and `.test` (decode suite) | `decodingTest.ParseError` | The legacy `.conf` layout has no instances in the tree today, and it is held to the same contract anyway: a missing, empty, or bad-regex `.expect` file records that one fixture as a failure rather than abandoning the directory. An unreachable abort becomes reachable the moment someone adds the directory, and the shape is the bug. Both alternatives are silent-coverage-loss bugs this project has already paid for, which is why neither is permitted: | Anti-pattern | What it costs | |--------------|---------------| | Return the parse error out of `Discover` | The whole directory is abandoned. One bad `.ci` made the `test/ui` suite discover and run ZERO tests, and a suite that runs nothing reads as green. | | `continue` past the bad file | The file leaves the suite with no warning and no failure record. Its coverage disappears and nothing says so. | A guard that neither denies nor speaks does not exist (`ai/rules/evidence.md`). The runner short-circuits a parse-failed test before executing anything, so the reported error is the parse error rather than a confusing downstream symptom. **Unparseable outranks skipped.** A file that both fails to parse and carries `option=needs-linux` / `option=skip-os` is reported FAIL, not SKIP. Its skip marker was parsed from the same broken file, so it is not trustworthy evidence that the file need not run: a contradicting directive may sit past the break, or the marker itself may be what was mis-parsed. Honoring it would mean trusting a broken file's own claim that it can be ignored. The consequences are asymmetric, which is what settles the ordering. 158 `.ci` files carry one of those markers (12 in `test/ui`), and on a non-Linux host they never run. A wrongly-SKIPPED malformed file is invisible indefinitely, which is exactly how `test/ui` rotted. A wrongly-FAILED one is loud and costs a single commit. The check therefore lives in `parallel.go`'s per-test goroutine, ahead of the skip short-circuit: that is the real entry point, and both `Runner.runTest` and `parsingRunner.runTest` are reached through it. <!-- source: internal/test/runner/parallel.go -- per-test goroutine, ParseFailed ahead of SkipReason --> <!-- source: internal/test/runner/parsing.go -- parsingRunner.Run, ParseError suppresses the SkipReason copy --> <!-- test: internal/test/runner/discover_malformed_test.go TestSkipMarkedMalformedCIStillFailsThroughParallelRunner, TestSkipMarkedMalformedParseTestFailsThroughParallelRunner --> <!-- source: internal/test/runner/record_parse.go -- EncodingTests.Discover, the reference shape --> <!-- source: internal/test/runner/parsing.go -- ParsingTests.Discover, parsingRunner.runTest --> <!-- source: internal/test/runner/decoding.go -- DecodingTests.Discover, decodingRunner.runTest --> <!-- test: internal/test/runner/discover_malformed_test.go -- parse and decode discoverers --> <!-- test: internal/test/runner/record_parse_test.go TestDiscoverSkipsUnparseableFile --> ### Suite label, test id, and failure identity The verify debugging protocol identifies a functional failure with: | Field | Source | Purpose | |-------|--------|---------| | Suite label | `ze-test` runner label such as `plugin`, `ui`, or `managed` | First routing boundary inside `./le functional` | | Test id | One-based decimal id printed by `--list` and per-test result lines | Exact single-test rerun scope | | Run number | `N/TOTAL` printed by `--list` and per-test result lines | Human progress marker for long suites | | CI file path | Parsed `.ci` source path | Full test definition and embedded fixtures | | Failure kind | Runner failure type, timeout state, or mismatch class | Conservative grouping key | | Expected / received evidence | `TEST FAILURE` block detail | Full debugging evidence in the stage log | In `ZE_VERIFY_MODE=1`, failed suites emit native failure-group metadata before the full failure blocks. The compact verify index uses that metadata for group routing and keeps the full `TEST FAILURE` blocks in the stage log. <!-- source: internal/test/runner/failure_group.go -- suite-local failure groups --> <!-- source: internal/test/runner/report.go -- TEST FAILURE blocks --> <!-- source: internal/test/runner/display.go -- per-test result lines --> ### Per-step trace output All three runner families (`.ci`, `.wb`, `.et`) record per-step outcomes during execution and emit dual-format trace output: - **Human:** colored checkmark/cross glyph per step with kind, assert, and detail. - **Machine:** `VERIFY STEP: {"file":"...","step":N,"kind":"...","status":"pass|fail",...}` -- one JSON line per step, matching the `VERIFY FAILURE GROUP:` prefix convention. Trace is emitted automatically for failed tests. Under `-v`, passing tests also show their trace. The `.ci` runner includes the trace in its `TEST FAILURE` report block when `StepTrace` is non-empty. <!-- source: internal/test/trace/trace.go -- StepResult, PrintTrace, writeHuman, writeMachine --> <!-- source: internal/test/runner/report.go -- step trace in failure reports --> ### conn and seq Most directives use `conn=N` and `seq=N` to identify message ordering: - **conn** (connection): 1-based TCP connection index. Each `ze-peer` instance manages one TCP connection. Multi-peer tests use `conn=1` for the first peer, `conn=2` for the second, etc. The maximum is set by `option=tcp_connections:value=N`. - **seq** (sequence): 1-based message sequence within a connection. `seq=1` is the first BGP message after OPEN/KEEPALIVE, `seq=2` is the second, etc. A test with two peers and one UPDATE each uses `conn=1:seq=1` and `conn=2:seq=1`, not `conn=1:seq=1` and `conn=1:seq=2`. ### Port Substitution Each test owns a pair of ports, exposed as variables in commands, `tmpfs=` content, `option=env` values, and URLs: | Variable | Meaning | |----------|---------| | `$PORT` | BGP peer port (leased by the runner, used by `ze-peer --port $PORT`) | | `$PORT2` | Secondary port, `$PORT`+1 (web UI, looking glass, plugin acceptor) | Never hardcode port numbers. Use `$PORT` in `cmd=` exec values and `$PORT2` in `http=` URLs. The pair is LEASED when the test starts, not when the suite discovers it (`runner.LeaseTestPorts`, `internal/test/runner/ports.go`). Discovery numbers the Nth test of every suite from the same base, so the preference alone collides whenever two ze-test processes run at once; the lease takes a machine-wide advisory lock in `$TMPDIR/ze-test-port-locks` and probes the pair, and a test whose preferred pair is locked or occupied gets one from 25000-32759 instead. A hardcoded number in a `.ci` file takes part in neither step, which is why the rule above is a rule. <!-- source: internal/test/runner/ports.go -- LeaseTestPorts --> <!-- source: internal/test/runner/runner_exec.go -- runTest leases before any $PORT expansion --> ## Stdin Blocks A stdin block embeds content the runner pipes to a process's standard input. Two commands take it as a FILE instead, and the table under "Where the block goes" says which, why, and what the `.ci` writes to select each route. ### Syntax **Multi-line (with terminator):** ``` stdin=<name>:terminator=<TERM> <content> <TERM> ``` **Single-line hex:** ``` stdin=<name>:hex=<hex-value> ``` **Single-line text:** ``` stdin=<name>:text=<text-value> ``` ### Parameters | Parameter | Description | |-----------|-------------| | `name` | Identifier referenced by `cmd=...:stdin=<name>` | | `terminator` | End marker for multi-line content | | `hex` | Hex-encoded content (single-line) | | `text` | Plain text content (single-line, newline appended) | <!-- source: internal/test/tmpfs/tmpfs.go -- StdinBlocks map, parseStdinBlock --> ### Examples **Multi-line (config):** ``` stdin=ze-bgp:terminator=EOF_CONF peer test-peer { remote { ip 127.0.0.1; as 65533; } local-as 65533; } EOF_CONF cmd=foreground:seq=1:exec=ze -:stdin=ze-bgp ``` The block goes to a file and the daemon runs as `ze start <file>`. The example said `exec=ze bgp server -:stdin=ze` until 2026-09-07, naming a command the CLI does not have and a block the file does not declare. **Single-line hex (decode test):** ``` stdin=payload:hex=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003C020000001C... cmd=foreground:seq=1:exec=ze bgp decode --json --family ipv4/unicast -:stdin=payload expect=json:json={ "type": "update", ... } ``` The block is PIPED: the `-` belongs to `decode`, not to the daemon. **Single-line text:** ``` stdin=cmd:text=update text nhop set 10.0.0.1 nlri ipv4/unicast add 10.0.0.0/24 ``` ### Where the block goes The runner pipes the block, with two exceptions. Each one exists because the child needs a path it can open, and each is selected by what the `exec=` line already writes: no new directive chooses between them. | The `exec=` line | Where the block goes | Why | |------------------|----------------------|-----| | `ze -`, and its flagged forms `ze -d -`, `ze --plugin <p> -`, `ze --mcp <port> -`, `ze --web <port> --insecure-web -` | a FILE in the work directory, and argv becomes `ze [flags] start <file>` | the daemon re-reads its config on SIGHUP, a restart reuses it, `action=rewrite:dest=ze-bgp.conf` addresses it by bare name, and a rollback assertion reads the directory beside it | | `ze-peer ...` with NO `-` in argv | a temporary FILE appended to argv | `ze-test peer` takes its expect script as a path argument | | `ze-peer ... -` | PIPED | `LoadExpectFile` opens its argument through `cliio`, so `-` is standard input there | | every other line, `ze bgp decode -`, `ze config validate -`, `ze-test replay -`, `sh -c ...` included | PIPED | `-` is the `cliio` stdin token (`ai/rules/cli.md`), and the command reads standard input | The daemon `-` is recognized by POSITION, not by a list of verbs: the runner asks `zeDaemonConfigArgIndex` which argument is the config, and substitutes only when that argument is the `-`. A `-` belonging to a verb is left in argv and the block is piped, so `ze config validate -` and `ze bgp decode pcap -` test the form the operator types. <!-- source: internal/test/runner/runner_exec_util.go -- routeStdinBlock --> Until 2026-09-07 the runner took the FIRST `-` in argv whatever it meant, so every verb form ran against a file path the author never wrote. A test that named a stdin block on a verb was testing the path form under a `-` that said otherwise, and `test/ui/bgp-decode-stdin-hex.ci` failed with `invalid hex: encoding/hex: invalid byte: U+002F '/'` for that reason. ### What a ze-peer block may carry A block handed to `ze-peer` (named on a `cmd=...:exec=ze-peer ...:stdin=<name>` line) is read first by ze-peer and then by the test runner. The block named `peer` is validated by the same rules even with no such line, because a `.ci` with no `cmd=` at all feeds its `expect=` lines to ze-peer by another route. **A line neither of them acts on fails the file at parse time**, naming the block, the line number and the directive. | Line | Read by | |------|---------| | `expect=bgp`, `reject=bgp`, `action=*`, and the peer's own `option=` (`asn`, `bind`, `tcp_connections`, `conn_map`, `open`, `update`, `linger`, `silent`, `await_eor`) | ze-peer | | `expect=json`, `expect=stderr`, `expect=syslog`, `reject=stderr`, `reject=stdout`, `reject=syslog` | the test runner, where the line stands | | `cmd=api` | nobody. It documents the command that produced the expected bytes | | `option=timeout` | the test runner parses it, and **adopts it only when the file declares none.** Its scope is the whole test, not this peer, so a file-level value always wins. 450 tracked peer blocks carry one. Write the one that governs outside the block | | `option=env` | **refused.** It sets the environment of every process the test starts, not of this peer, so it must be written outside the block | | a line neither parser accepts | **refused** | Any OTHER directive the runner parses is accepted where it stands and applies to the whole test. `option=file`, `expect=exit`, `await=` and `http=` all work inside a peer block, and none of them is peer-scoped. Only `option=env` and `option=timeout` are singled out. Those two read as peer scope and are not. A value the named option does not have is refused as well, not just an unknown option name. `option=update:value=inspect-update-message` and `option=asn:value=abc` each used to parse into nothing, which is the same silent drop one level down. The accepted set is derived from the two parsers rather than written out a third time (`peer.ClaimLine` reports what ze-peer did with the line, and the runner's own line parser answers for the rest), so a directive added to either one cannot start being dropped here. Before the guard existed the block forwarded only `expect=` and `action=` lines and discarded everything else in silence: eleven `reject=bgp` directives across nine RFC-behaviour tests, and the `reject=stderr` of a tenth, asserted nothing while reading as the negative half of the proof. <!-- source: internal/test/runner/peer_contract.go -- validatePeerBlockDirectives, the guard --> <!-- source: internal/test/peer/expect.go -- ClaimLine, ze-peer's own answer --> ## Tmpfs (Virtual File System) Tmpfs allows embedding multiple files within a single `.ci` file. Files are extracted to a temp directory at runtime. ### Syntax ``` tmpfs=<path>[:mode=<octal>][:encoding=<type>]:terminator=<TERM> <content> <TERM> ``` ### Parameters | Parameter | Required | Default | Description | |-----------|----------|---------|-------------| | `path` | Yes | - | Relative path (no `..`, no absolute) | | `mode` | No | Auto | File permissions (octal: 644, 755) | | `encoding` | No | `text` | Content encoding: `text` or `base64` | | `terminator` | Yes | - | End marker (alone on line) | <!-- source: internal/test/tmpfs/tmpfs.go -- File struct, Tmpfs.AddFile --> ### Mode Defaults Files default to `0644`. Use an explicit `mode=` only when the test needs a different permission. ### Terminator Rules - Must be non-empty - Must be unique within file (no two Tmpfs blocks can share terminator) - Alphanumeric and underscore only: `[A-Za-z0-9_]+` - Matched exactly (no whitespace trimming) - Recommended: `EOF_<PURPOSE>` (for example, `EOF_CONF`) ### Example ``` tmpfs=peer.conf:terminator=EOF_CONF peer test-peer { remote { ip 127.0.0.1; as 65533; } local-as 65533; } EOF_CONF option=file:path=peer.conf option=asn:value=65533 expect=bgp:conn=1:seq=1:hex=FFFF... ``` ### Security Constraints 1. **No absolute paths** - must be relative 2. **No parent traversal** - no `..` components 3. **No hidden files** - no `.` prefix in path components 4. **Path length limit** - max 256 characters 5. **Path depth limit** - max 10 levels <!-- source: internal/test/tmpfs/security.go -- validatePath, Validate --> ### Limits Configurable via environment variables: | Limit | Default | Environment Variable | |-------|---------|---------------------| | Max file size | 1 MB | `ze.bgp.ci.max_file_size` | | Max total size | 1 MB | `ze.bgp.ci.max_total_size` | | Max files | 100 | `ze.bgp.ci.max_files` | | Max path length | 256 | `ze.bgp.ci.max_path_length` | | Max path depth | 10 | `ze.bgp.ci.max_path_depth` | ## Options ``` option=<type>:key=value[:key=value...] ``` | Type | Keys | Description | |------|------|-------------| | `file` | `path=<name>` | Config file to use | | `asn` | `value=<N>[:peer=<ip>]` | The AS ze-peer opens with. It reaches BOTH carriers RFC 6793 defines: the two-octet My Autonomous System field, narrowed to AS_TRANS (23456) above 65535, and the Capability Value of capability 65. The range is 1 to 4294967295 and a value outside it fails the file when it is read, naming the option and the value. `peer=<ip>` binds the declaration to one of ze's endpoint addresses, for a peer process that serves several of ze's peers at once (`option=conn_map`). With no `option=asn` line at all the runner derives one from the `session { asn { remote N } }` leaf of the ze configuration the `.ci` names. It reads a `tmpfs=` block, a `stdin=` block and the file an `option=file:path=` points at, and it follows the inheritance chain: the router's own `bgp { session { asn { ... } } }`, then a `group` or `template`, then the peer, each level overriding the one above. Three things fail the file at read time rather than being guessed: two peers ze dials at ONE address expecting different ASNs, a declared AS the reader cannot read, and an eBGP peer the derivation reached with nothing. **That last refusal knows only what the reader knows.** A peer is judged eBGP by comparing the local and remote AS, so a peer for which NO local AS is declared at any level of the chain is not judged eBGP and is not refused; it inherits ze's own AS from the mirror, which is right for an iBGP session and wrong for an eBGP one. The derivation reaches no peer at all when a compiled fixture under `internal/test/fixture` writes the configuration and launches `ze-test peer` itself, because no `.ci` block is involved; such a fixture passes `--asn` (`cliWirePeerAS`, `internal/test/fixture/ui_fixture_send_bgp.go`). | | `bind` | `value=ipv6` | Bind to IPv6 | | `timeout` | `value=<duration>` | Test timeout (e.g., `30s`). Overrides auto-timeout. | | `tcp_connections` | `value=<N>` | The number of TCP connections the peer serves. It is a BOUND, never a witness: the count says how many connections happened and not who caused them. A peer that closes when its expectations are met makes ze dial again on its retry timer, so `value=2` is reached by a daemon that did nothing. To assert that the DAEMON dropped and restarted a session, add `option=linger`, which makes the peer incapable of causing the second connection. | | `linger` | `value=true` | Peer-block only: the check peer never closes a connection itself. After ALL expectations complete it prints its success token and holds the session open (answering KEEPALIVEs) until test teardown. BETWEEN connections, where one connection's expectations are met and a later connection is still owed, it holds that connection until the REMOTE closes it, and fails the test if teardown comes first. Without it a completed peer closes, which ze correctly treats as session-down; that withdraws the peer's routes and races any forwarding still in flight toward other peers, and it also makes the daemon's OWN close unobservable, because ze dials again on its retry timer whoever closed (`test/reload/config-apply-ordering-address-swap.ci` asserts a stop and a restart, and needs the peer to be incapable of causing one). A `conn_map` peer is excluded from the between-connections hold: it serves every connection of one batch in turn and then waits for the daemon to close them all, so holding the first would starve the batch. A `reject=bgp` that fires during either hold RETRACTS the success token already printed, so the peer still fails the test. | | `silent` | `value=true` | Peer-block only, check mode only: the peer stops sending the automatic KEEPALIVE reply it otherwise writes for every message it receives. It holds the TCP connection open and keeps reading and matching expectations. Needed to reach ze's receive hold timer: ze sends its own KEEPALIVE every hold/3 seconds, each automatic reply resets ze's hold timer, and "the peer went quiet" is otherwise unexpressible. A closed connection is a different event on a different code path, so `action=close` does not substitute. **Explicit writes still happen**: `action=send`, `action=notification`, the OPEN handshake itself, and `option=linger`'s post-completion KEEPALIVE loop are unaffected, so `silent` with `linger` is not silent. Sink and echo modes ignore it. See `test/plugin/deadpeer-holddown.ci`. | | `open` | `value=<behavior>` | OPEN message behavior | | `update` | `value=<behavior>` | UPDATE message behavior | | `env` | `var=<KEY>:value=<V>` | Set environment variable | | `skip-os` | `value=<os>[,<os>]` | Skip test on listed GOOS values (e.g., `darwin`, `linux`) | | `needs-linux` | `[caps=<tok>[,<tok>]]` | Linux-only test. It skips on non-Linux hosts and runs in the QEMU guest through `./le qemu all-tests`. `caps=` declares required capabilities such as `net-admin`, `net-raw`, and `bpf`; an unavailable capability produces a visible skip. | | `needs-path` | `value=<repo-rel-path>[:hint=<cmd>]` | Declares an optional heavyweight artifact. The runner resolves the path against the repository root and prints the native `hint` when the artifact is absent. A malformed or escaping path is a parse error. | | `netns-link` | `name=<if>[:address=<cidr>]` | Provisions a dummy interface inside the per-test namespace. The test skips outside the `./le qemu netns-test` path because the named link must never be created on the host. | | `exclusive` | `group=<name>` | Never run concurrently with another test carrying the same group name. Tests outside the group are unaffected and keep running alongside, so this costs far less wall-clock than dropping a whole suite to `-p 1`. Use it when tests contend for a kernel-global observation surface that unique names or addresses cannot partition: the ddos tests (`group=ddos-flood`) all flood the same loopback interface, and each daemon's detector picks its victim by top-destination-bytes over that interface's counters, so a sibling's concurrent flood is indistinguishable from the test's own. Applies on every platform and in every runner mode, because the contention is a property of the tests rather than of the host. | <!-- source: internal/test/runner/record_parse.go -- parseAndAdd, option parsing --> <!-- source: internal/test/runner/caps.go -- capsRequired, the caps= token table --> <!-- source: internal/test/runner/needs_path.go -- repoRootFrom, the needs-path lookup --> <!-- source: internal/test/runner/parallel.go -- per-group lock, taken before the concurrency semaphore --> #### Choosing between `needs-linux`, `caps=`, and `skip-os` | The `.ci` test ... | Use | |--------------------|-----| | Only validates config (`ze config validate -`), parses, or runs an offline `ze show` / `ze env` | Nothing. It runs natively on every OS | | Boots a daemon that APPLIES Linux-only config (interface, VLAN, firewall, L2TP kernel) | `option=needs-linux` | | The same, and needs privileged network configuration (creates interfaces, brings links up, programs netlink) | `option=needs-linux:caps=net-admin` | | The same, and opens a raw or packet socket (`resolve ping`, traceroute) | `option=needs-linux:caps=net-raw` | | The same, and loads eBPF | `option=needs-linux:caps=bpf` | | Skips on one non-Linux OS for a reason unrelated to the kernel | `option=skip-os:value=darwin` | | Needs an optional heavyweight artifact the checkout does not carry | `option=needs-path:value=<repo-rel>:hint=<cmd>` | `caps=` takes a comma-separated list, so a test that programs netlink and loads eBPF declares `caps=net-admin,bpf` and is gated on both. An unknown token is a parse error on every host, macOS included, so a typo cannot silently disable the gate. `caps=net-admin` exists because Linux alone is not the requirement. On an unprivileged Linux host, a CI runner or a rootless container, a test that applies interface config does not fail cleanly: the interface plugin fails its configure handshake with `operation not permitted` and the DAEMON exits 1, then the TEST hangs because its check peer waits for a session the exited daemon will never open. The gate reads `CapEff` from `/proc/self/status`, not uid 0: a setcap'd binary holds the capability without being root, and a restricted container can be root without it. A `caps=` test does not run in the merge gate. `./le verify worktree` runs unprivileged, so the marker turns an opaque hang into an honest skip there, and the coverage relocates to `.github/workflows/qemu-nightly.yml`. `TestCapabilityGatedTestsHaveANativeVMHome` fails when that link is broken: marking a test with a capability nobody's CI has would be a coverage deletion wearing a skip's clothing. <!-- source: internal/test/runner/caps.go -- capsRequired, capsAccepted --> <!-- source: internal/test/runner/caps_linux.go -- probeCaps, the CapEff read --> <!-- source: internal/le/workflowcheck/workflowcheck_test.go -- TestCapabilityGatedTestsHaveANativeVMHome --> ### OPEN Behaviors | Value | Description | |-------|-------------| | `send-unknown-capability` | Add unknown capability (code 66) to OPEN | | `inspect-open-message` | Validate received OPEN against expectations | | `send-unknown-message` | Send unknown message type (255) after OPEN | | `drop-capability` | Remove a capability from ze-peer's OPEN response | | `add-capability` | Add a capability to ze-peer's OPEN response | | `router-id` | Send an explicit BGP Identifier instead of the mirrored one | | `hold-time` | State the Hold Time of the OPEN body (`seconds=<N>`) | | `graceful-restart` | State ze-peer's own Graceful Restart capability (`restart-time=<N>`, `family=`, `forward-state=`) | | `llgr` | State ze-peer's own Long-Lived Graceful Restart capability (`stale-time=<N>`, `family=`, `forward-state=`) | | `paths-limit` | State ze-peer's own PATHS-LIMIT entries (`family=`, `limit=<N>`) | <!-- source: internal/test/peer/expect.go -- parseOptionConfig; internal/test/peer/open.go -- buildOpen --> ### Sender Facts (hold-time, graceful-restart, llgr, paths-limit) Each of these four values states a fact about ZE-PEER that a receiver acts on. They exist because a mirror asserts sameness: mirrored, each one made ze read its own configuration back out of the peer's OPEN and believe the peer had said it. ``` option=open:value=hold-time:seconds=<N> option=open:value=graceful-restart:restart-time=<N>[:family=<f>[,<f>]][:forward-state=true|false] option=open:value=llgr:stale-time=<N>[:family=<f>[,<f>]][:forward-state=true|false] option=open:value=paths-limit:family=<f>[,<f>]:limit=<N> ``` | Key | Range | What reads it | |-----|-------|---------------| | `seconds` | 0, or 3 to 65535 | `session_negotiate` takes the smaller of this and ze's `receive-hold-time`, then calls `timers.SetHoldTime`. RFC 4271 Section 4.2. 1 and 2 fail the file | | `restart-time` | 0 to 4095 | `grStateManager.onSessionDown` arms the restart timer on it, `runPeer` gives it to `startEORTimer`, and `show bgp peer` prints it. RFC 4724 Section 3 gives the field 12 bits | | `stale-time` | 0 to 16777215 | `enterLLGRLocked` arms one timer per family on it. RFC 9494 Section 3 gives the field 24 bits | | `limit` | 0 to 65535 | The Max Paths field of the code 76 entry this peer advertises. `Session.filterPathsLimit` drops paths past it, so ze sends this peer no more than `limit` paths for one prefix | | `family` | a family name, or a comma-separated list | The `<AFI, SAFI, Flags>` tuples of code 64, the 7-octet tuples of code 71, and the entries of code 76 | | `forward-state` | `true` (default) or `false` | The F bit of every tuple. `onSessionReestablished` purges the stale routes of a family whose F bit is clear | **The families are what make graceful restart act at all.** RFC 4724 Section 3 pairs the Restart Time with a tuple list, and `onSessionDown` builds its stale family set from that list and returns without dispatching anything when the set is empty. Ze's own code 64 carries the time and no tuples (`parseGRCapValue`), so until ze-peer owned the value, `retain-routes`, `mark-stale` and `purge-stale` were never dispatched in any test. **A file that states none of them gets the harness's own defaults, never ze's.** | Fact | Default | |------|---------| | Hold Time | 65535, the largest the two-octet field states. RFC 4271 Section 4.2 takes the smaller of the two, so the negotiated value stays ze's own and no existing test changes cadence. Only a `.ci` stating a smaller one opts in | | Restart Time | 300 seconds, longer than any functional test runs | | Long-Lived Stale Time | 600 seconds | | Max Paths | 65535, which bounds nothing | | Software version (code 75) | `ze-peer`. It has no option, because no session decision turns on it: `capability.Parse` holds no code-75 arm and the only reader is the offline `ze bgp decode` | | Families, for all three capabilities | the families ze-peer's own OPEN advertises, read from its Multiprotocol capabilities. An OPEN carrying none is an `ipv4/unicast` speaker (RFC 4760 Section 8) | Two refusals fail the file rather than dropping a line in silence: - Stating a fact AND `add-capability` or `drop-capability` for the same code. The stated octets would win, and the typed line would be read, validated and then lost. - Stating a fact for a capability **ze does not offer**. The capability SET ze-peer sends mirrors ze's, so the value would have no place to go. Configure ze to offer the capability, or drop the option. <!-- source: internal/test/peer/expect.go -- parseOpenHoldTime, parseGracefulRestartDecl, parseLLGRDecl, parsePathsLimitDecl --> <!-- source: internal/test/peer/open_capability.go -- ownedCapabilities, refuseUnofferedDeclarations --> ### BGP Identifier Control (router-id) ``` option=open:value=router-id:id=<a.b.c.d> ``` Ze-peer's default OPEN carries ze's own BGP Identifier with the last octet incremented, which is always a distinct, valid identifier. This option replaces it outright, so a test can present an identifier the default can never produce: `0.0.0.0`, or ze's own router-id (RFC 6286 Section 2.2 rejects both, the second only from an internal peer). A malformed or IPv6 value fails the file when it is read. It was ignored until 2026-09-08, which sent the DEFAULT identifier from a test that asked for an invalid one: the file then tested the valid identifier and passed. <!-- source: internal/test/peer/expect.go -- parseOptionConfig "router-id"; internal/test/peer/open.go -- openIdentity --> ### Capability Control (drop-capability / add-capability) Ze-peer mirrors the capability SET of ze's OPEN, so a `.ci` that says nothing about capabilities still negotiates whatever ze offers. It does NOT mirror the values that describe the SENDER. The AS, the BGP Identifier, the Hold Time, the Role (code 9), the Graceful Restart time, families and flags (code 64), the ADD-PATH directions (code 69), the Long-Lived Graceful Restart stale time and flags (code 71), the FQDN (code 73), the software version (code 75) and the PATHS-LIMIT entries (code 76) are each resolved from the test's own configuration and written into ze-peer's OPEN, because a mirror asserts sameness and every one of those facts is about the speaker rather than about the session. `ownedCapabilities` (`internal/test/peer/open_capability.go`) is the list: a code absent from it is mirrored by construction. The `drop-capability` and `add-capability` options act on that reconciled OPEN at wire level, allowing tests to control exactly which capabilities ze-peer advertises. **A capability the `.ci` states REPLACES the one ze-peer would have resolved.** An `add-capability` naming any resolved code -- 9, 64, 65, 69, 71, 73, 75 or 76 -- is sent as written and ze-peer adds no second capability of that code, so a file that drops a code and adds it back gets exactly what it asked for. Dropping code 65 removes the four-octet AS capability, which leaves the two-octet My Autonomous System field as the only carrier of the peer's AS: above 65535 that field can carry AS_TRANS alone, which is what RFC 6793 Section 3 defines for a speaker with no two-octet AS. **An `add-capability:code=65` carrying four octets IS the AS declaration, and the My Autonomous System field follows it.** It outranks `option=asn` and the derivation, because it names the octets that reach the wire and RFC 6793 Section 4.1 makes those the octets a receiver reads. Letting the header field come from anywhere else would put two ASNs in one OPEN, which is the disagreement ze answers with NOTIFICATION 2/2 Bad Peer AS. A stated code 65 of any OTHER length declares no AS: it is a malformed capability the test is driving on purpose, so it is sent as written and the header field keeps the AS the rest of the configuration resolved. That is the one case where the two carriers differ, and they differ because the `.ci` asked. **Drop a capability:** ``` option=open:value=drop-capability:code=<N> ``` Removes the capability with the given code from ze-peer's OPEN response. The peer will not see this capability in the mirrored OPEN. **Add a capability:** ``` option=open:value=add-capability:code=<N>:hex=<value-bytes> ``` Adds a capability with the given code and hex-encoded value bytes to ze-peer's OPEN response. | Key | Description | |-----|-------------| | `code` | Capability code (1-255), e.g., 65 for ASN4, 2 for route-refresh | | `hex` | Hex-encoded capability value bytes (only for add-capability) | **Use case: testing capability mode enforcement:** When Ze is configured with `require` mode for a capability, it sends a NOTIFICATION if the peer lacks that capability. To test this, use `drop-capability` to make ze-peer omit the capability from its response: ``` # Test: Ze requires ASN4, ze-peer drops it → Ze should send NOTIFICATION option=open:value=drop-capability:code=65 ``` When Ze is configured with `refuse` mode, it sends a NOTIFICATION if the peer has a capability. To test this, the default mirror behavior already includes the capability, but `add-capability` can add capabilities not in the original OPEN: ``` # Test: Add a custom capability for refuse testing option=open:value=add-capability:code=73:hex=067A652D626770 ``` **Multiple overrides** can be combined: ``` option=open:value=drop-capability:code=65 option=open:value=drop-capability:code=2 option=open:value=add-capability:code=73:hex=067A652D626770 ``` ### UPDATE Behaviors Routes ze-peer sends after the OPEN handshake, before it starts matching expectations. | Value | Keys | Description | |-------|------|-------------| | `send-default-route` | none | Send one UPDATE for `0.0.0.0/0` | | `send-route` | `prefix`, `origin-as`, `next-hop`, and optionally `as-path`, `as-set`, `originator-id`, `cluster-list`, `label` | Send one UPDATE for one prefix. Repeat the line for more | | `send-bulk` | `prefix`, `count`, `next-hop`, `origin-as`, and optionally `max-msg`, `eor` | Generate `count` sequential prefixes from `prefix` and send them as whole BGP messages | <!-- source: internal/test/peer/expect.go -- parseOptionConfig "update"; parseBulkSpec --> **Generating one oversize UPDATE (`max-msg`):** ``` option=update:value=send-bulk:prefix=10.0.0.0/24:count=16373:next-hop=10.0.0.1:origin-as=65001:max-msg=65535 ``` `max-msg` caps one generated message, header included. It defaults to the RFC 4271 limit of 4096, and accepts 23 to 65535. Raise it to 65535 when the session negotiates the Extended Message capability (RFC 8654), which ze advertises when the peer carries `capability { extended-message enable }`. Prefixes that do not fit one message spill into the next, so the example above is exactly one 65535 byte message: a 65516 byte body, the largest BGP permits. Use it whenever a test needs an UPDATE too large to write as a hex literal. A max-size message is 131070 hex characters, which is past the 64 KiB line limit of both `.ci` scanners and past what a reviewer can check. It is also the only way to hand the daemon a single oversize body: splitting the same prefixes across several standard messages is a different input, because the daemon decides per message. A malformed key fails the test load rather than sending nothing. That is deliberate: a spec that silently degraded to `count=0` would let a test asserting a route was NOT forwarded pass because no route was ever offered. <!-- source: internal/test/peer/inject.go -- InjectSpec.MaxMsgLen, buildV4Unicast --> ## Commands ``` cmd=<type>:key=value[:key=value...] ``` ### API Commands ``` cmd=api:conn=<N>:seq=<N>:text=<command> ``` | Key | Description | |-----|-------------| | `conn` | Connection number (1-4) | | `seq` | Sequence number within connection | | `text` | API command text | ### Example ``` cmd=api:conn=1:seq=1:text=update text origin set igp nhop set 10.0.1.1 nlri ipv4/unicast add 10.0.0.0/24 ``` ### Process Commands (Foreground/Background) For orchestrating multiple processes: ``` cmd=background:seq=<N>:exec=<command>[:stdin=<name>][:name=<handle>][:timeout=<dur>] cmd=foreground:seq=<N>:exec=<command>[:stdin=<name>][:timeout=<dur>][:exit=<N>] cmd=stop:seq=<N>:name=<handle>[:signal=kill|term] ``` | Key | Description | |-----|-------------| | `seq` | Execution order (lower first) | | `exec` | Command to execute | | `stdin` | Stdin block name to pipe | | `timeout` | Foreground test budget, or background process lifetime (e.g., `10s`). | | `exit` | Exit code asserted for **this** command (0..255). See below. | | `name` | Handle for a background process, so a later `cmd=stop` can target it. | | `signal` | `cmd=stop` only: `kill` (SIGKILL, default) or `term` (SIGTERM). | Markers may appear in any order; each value runs to the next key in the table above, whichever key that is. **The table is the whole vocabulary. Any other `:<word>=` on a `cmd=` line fails the file**, naming the key, the accepted set and the line. The scan reads the whole line, `exec=` included, because that is exactly where a key the parser does not read ends up: a value runs to the next KNOWN key, so an unknown one is swallowed into the value before it rather than dropped. `cmd=foreground:seq=2:exec=ze -:stdin=ze-bgp:timeout=15s:env=ZE_FWD_WRITE_DEADLINE=10s` parsed with `timeout="15s:env=ZE_FWD_WRITE_DEADLINE=10s"`, so the line got neither the timeout it declared nor the variable, and every assertion still passed. Set an environment variable with `option=env:var=<name>:value=<value>`. One consequence: a command carrying a `:<word>=` span of its own cannot be written on a `cmd=` line. Put it in a `tmpfs=` script and run the script. `timeout=` must be a Go duration (`10s`, `1m30s`). Both readers of the value keep their own default when it does not parse, so it is refused here instead. <!-- source: internal/test/runner/record_parse_cmd.go -- cmdExecKeys, cmdStopKeys, parseCmdExec --> <!-- source: internal/test/runner/record_parse_keys.go -- checkMarkerKeys --> <!-- test: internal/test/runner/record_parse_cmd_test.go TestCmdUnknownKeyRefused, TestCmdUnknownKeyNotSwallowedIntoExec --> **Background:** Starts and keeps running until its timeout, an explicit stop, or test completion. **Foreground:** Setup commands finish before the next step. A `ze` daemon starts without blocking later steps. Its peers or observer determine when teardown starts. **Stop:** Terminates a named background process mid-test (see below). When an embedded observer sends `request shutdown`, teardown gives that daemon its bounded self-stop grace before stopping the other background processes. <!-- source: internal/test/runner/runner_exec.go -- runOrchestrated, startBackgroundLifetime --> <!-- source: internal/test/runner/runner_exec_util.go -- tmpfsRequestsDaemonShutdown, terminateAfterSelfExit --> #### How an `exec=` value becomes argv The runner splits the value on spaces and tabs, and a span inside `"` or `'` stays ONE argument. Quote an argument that carries a space or a pipe, exactly as you would in a shell: ``` cmd=foreground:seq=1:exec=ze cli -c "show config dump - | json":stdin=config ``` `-c` receives `show config dump - | json`, and the quotes do not reach the process. Three limits apply: - A backslash escape is not handled. `\"` is a backslash and a quote character, never a literal quote inside an argument. - An unbalanced quote fails the test with `unclosed quote in ...`. - No shell runs, so `|`, `>` and `$HOME` are ordinary characters inside an argument. The runner expands `$PORT` and `$PORT2` and nothing else, and `ze-test fixture` expands the environment in its own arguments. One splitter serves every suite, so a `cmd=` line produces the same argv wherever it runs. **Provenance:** until 2026-09-02 the `.ci` runner split the value on whitespace alone while the parse suite honored quotes. The example above was already published here, so four `test/ui` tests were written against it. `-c` received only the first word of the quoted command, `ze cli` fell through to its SSH client, and each test failed with `no credentials for 127.0.0.1:2222`. <!-- source: internal/test/runner/runner_exec_util.go -- splitCommand --> <!-- source: internal/test/runner/runner_exec.go -- runExecCommands argv construction --> <!-- source: internal/test/runner/parsing.go -- runOneCommand, the parse suite's own execution --> <!-- source: internal/test/fixture/fixture.go -- Run, os.ExpandEnv over the fixture's own arguments --> <!-- test: test/runner/exec-quoted-argument.ci -- a quoted argument carrying a pipe reaches the callee as one argv element --> #### `cmd=stop` -- terminate a background process mid-test A background process started with `name=<handle>` can be stopped at a chosen step by `cmd=stop:seq=<N>:name=<handle>`. The runner looks the process up, signals it, and **waits for it to exit before the next step runs**, so a later step can deterministically observe what happens after the process dies (e.g. `show vpn ipsec sa` emptying once an IKE responder is killed and DPD fires). - `signal=kill` (default) sends **SIGKILL**: the process gets no chance to flush or send a protocol teardown. This is the choice for liveness/dead-peer tests where the peer must go **silent** (a clean shutdown would take a different code path). - `signal=term` sends **SIGTERM**, escalating to SIGKILL if the process does not exit within the teardown grace period -- a graceful stop. Fail-closed: a `cmd=stop` naming a process that was never started (no matching `name=`) **fails the test** with a clear error; it never silently no-ops, and it can only ever signal a process the runner itself started (never an arbitrary PID). Teardown still kills every remaining background process, and tolerates one the stop step already reaped. <!-- source: internal/test/runner/record_parse_cmd.go -- parseCmdExec (name=), parseCmdStop --> <!-- source: internal/test/runner/runner_exec_util.go -- stopNamedBackground, stopBackgroundProcess --> <!-- source: internal/test/runner/runner_exec.go -- modeStop step + namedBg registration --> <!-- test: internal/test/runner/record_parse_cmd_test.go TestParseStopBackgroundDirective, TestParseAndAdd_StopDirective --> <!-- test: internal/test/runner/runner_stop_test.go TestStopBackgroundKillsNamedProcess, TestTeardownToleratesStoppedProcess --> <!-- test: test/runner/stop-background.ci -- full parse->start->stop->assert wiring proof --> **Provenance:** added by spec-fixit-runner-kill-background to unblock end-to-end peer-death observation (the deleted `test/ipsec/ipsec-dpd-timeout.ci` needed it). #### `exit=` vs `expect=exit:code=` (per-command vs file-level) `expect=exit:code=` is **file-level**: `Record.ExpectExitCode` is a single value (a later `expect=exit:code=` silently overwrites an earlier one) and the runner compares it against `lastQuickZeErr` -- the exit status of the **last** quick-exit `ze` command in the file. A file that runs several `ze config validate` commands therefore asserts only the final one; every earlier command can exit with any code and the test still passes. Use `exit=` on the `cmd=` line to assert a specific command's own exit code. It is checked the moment that command finishes, and names the offending `seq` on failure: ``` cmd seq=2 (ze config validate -): expected exit code 1, got 0 ``` Prefer `exit=` whenever a file runs more than one quick-exit `ze` command. A "quick-exit `ze` command" is any foreground `ze` whose verb is not a daemon verb (`hub`, `start`, `cli`, `monitor`) and which has no config-file argument or `--web` flag. #### Every stream assertion is FILE-level, over ONE buffer This section describes the **generic** runner, which is every suite except `test/parse`. The parse suite scopes each assertion to one command; see "The parse suite reads its own dialect" below. `expect=stdout:`, `expect=stderr:`, `reject=stdout:` and `reject=stderr:contains=` all read `Record.ClientOutput`, which the runner builds ONCE at the end of the test as the concatenated stdout AND stderr of every command in the file. Two consequences, and an author who misses either writes an assertion that cannot mean what it says: - **There is no per-command scope.** `expect=stdout:contains=` can be satisfied by a different command than the one it sits under, and a reject trips on any command's output. A file that asserts a needle PRESENT for one command and ABSENT for another asserts two contradictory things about one string. - **The stream name selects nothing** for `contains=`. Only `pattern=` on `expect=stderr:` / `reject=stderr:` reads stderr alone, through `validateLogging`. So a negative assertion belongs in a file whose every command may satisfy it. Split the file otherwise: `test/plugin/vpp-doctor-hugepages.ci` and `vpp-doctor-hugepages-quiet.ci` are one scenario in two files for exactly this reason, as are `test/appliance/no-install-appliance.ci` and `appliance-help-not-deprecated.ci`, and each says so at the top. Also see `test/vrrp/vrrp-doctor-quiet.ci`, a single-command file so its reject is meaningful. <!-- source: internal/test/runner/runner_exec.go -- rec.ClientOutput = clientStdout.String() + clientStderr.String() --> <!-- source: internal/test/runner/runner_output_assert.go -- checkOutputAssertions --> <!-- source: internal/test/runner/runner_exec.go -- quickZe branch, per-command exit assertion --> <!-- source: internal/test/runner/record_parse_cmd.go -- parseCmdExec, markerExit --> <!-- source: internal/test/runner/record.go -- RunCommand.ExitCode --> <!-- test: internal/test/runner/record_newformat_test.go TestParseCmdExec -- exit= parsing, marker order, 0..255 bounds --> <!-- test: test/vrrp/vrrp-config-invalid.ci -- 11 rejections, each asserted via exit=1 --> <!-- test: test/vrrp/vrrp-doctor-quiet.ci -- single-command file so reject=stdout is meaningful --> **Known gap:** 108 quick-exit `ze` commands across 50 `.ci` files predate `exit=` and are still unasserted (their `expect=exit:code=` never reaches them). Arming them may surface real defects; tracked in `plan/known-failures/`. #### The parse suite reads its own dialect `test/parse` runs under `ParsingTests`, a second parser with its own execution model. Two differences are load-bearing for an author. **Every assertion is scoped to one command.** It is checked against the stdout and stderr of the `cmd=` line directly above it, not against a file-level buffer. So a needle asserted present under one command and absent under another means what it says here, and does not need the file split the section above describes. An assertion that appears before the first `cmd=` has nothing to assert against and **fails the file**; `expect=stderr:contains=` is the one exception, because a `.ci` holding an inline config and no command at all is a legacy negative test whose expected error it carries. **The dialect is a subset of the generic vocabulary, and nothing else parses.** A directive no arm reads **fails the file at discovery**, naming the directive, the file, and every directive the suite does read. It is the same rule as "Unknown Keys Are a Parse Error" above, applied to the whole line rather than to a key inside it. | Read by the parse suite | |---| | `cmd=`, `expect=exit:code=` | | `expect=stdout:contains=`, `expect=stdout:pattern=` | | `expect=stderr:contains=`, `expect=stderr:pattern=` | | `reject=stdout:contains=`, `reject=stdout:pattern=`, `reject=stderr:pattern=` | | `option=skip-os:value=`, `option=env:` | Two spellings this suite once had are **deleted**, not aliased. Both existed only here, and both are what two parsers over one corpus costs: - `expect=stdout:regex=` is now `expect=stdout:pattern=`. The generic parser reads `pattern=` and refuses `regex=`, so the gates that walk the whole corpus with it could not read three `test/parse` files at all. - `expect=stdout:not:contains=` is now `reject=stdout:contains=`, which this suite already read with the identical meaning. This is the one that mattered: the generic parser splits `not:contains=` at the `:contains=` key boundary and drops the bare `not`, so one written line meant "must be absent" to the suite that runs `test/parse` and "must be present" to every gate that reads it. **`cmd=...:timeout=<duration>` is honored**, and a bare `ze -` runs as `ze start <file>`, the same translation the generic runner makes. Substituting the path in place produced `ze <path>`, which is not a command: the daemon answered `unknown command: <path>` with its usage and exit 1 before reading a line of config, so a file could assert `expect=exit:code=1` and be satisfied by the usage error. <!-- source: internal/test/runner/parsing.go -- ciDirectives, ciFileParser.line, runOneCommand --> <!-- test: internal/test/runner/parsing_test.go -- TestParseCIRefusesUnknownDirective, TestParseCIAssertionsDiscriminate, TestParseCICorpusReadsUnderTheGenericParser --> **Daemon readiness (`ze` only):** a `ze` daemon launched **either** foreground or background is told (via `ZE_READY_FILE`) to write `daemon.ready` once startup completes, and the runner publishes its PID to `daemon.pid` in the tmpfs directory. Tests poll both files directly or through a compiled driver in `internal/test/fixture` before signaling the daemon or asserting on it. The readiness handshake is armed only for Ze daemons. <!-- source: internal/test/runner/runner_exec.go -- process orchestration --> ### Example (Decode Test) ``` stdin=payload:hex=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003C... cmd=foreground:seq=1:exec=ze-test decode --family ipv4/unicast -:stdin=payload expect=json:json={ "type": "update", ... } ``` ### Example (Multi-Process) ``` stdin=peer:terminator=EOF_PEER option=asn:value=65000 expect=bgp:conn=1:seq=1:hex=FFFF... EOF_PEER stdin=ze-bgp:terminator=EOF_CONF peer test-peer { remote { ip 127.0.0.1; } ... } EOF_CONF cmd=background:seq=1:exec=ze-peer --port $PORT:stdin=peer cmd=foreground:seq=2:exec=ze bgp server -:stdin=ze-bgp:timeout=10s ``` ### Example (Multi-Peer) Tests needing two or more BGP peers use the same `$PORT` on different loopback addresses. The `ze_bgp_tcp_port` override sets all peer ports uniformly, which is correct because every peer listens on `$PORT`. ``` # Source peer on 127.0.0.1 (default) stdin=source:terminator=EOF_SOURCE option=tcp_connections:value=1 action=send:conn=1:seq=1:hex=FFFF... EOF_SOURCE # Dest peer on 127.0.0.2 -- sink mode absorbs any UPDATE stdin=dest:terminator=EOF_DEST option=tcp_connections:value=1 EOF_DEST cmd=background:seq=1:exec=ze-peer --port $PORT:stdin=source cmd=background:seq=2:exec=ze-peer --bind 127.0.0.2 --mode sink --port $PORT:stdin=dest cmd=foreground:seq=3:exec=ze -:stdin=ze-bgp:timeout=20s ``` Each ze-peer gets independent output capture and `WaitFor` synchronization. The runner waits for each peer's "listening on" message before starting the next command. On Linux, 127.0.0.2 works automatically (127.0.0.0/8 routes to lo). On macOS and FreeBSD, the test runner adds loopback aliases via the `SIOCAIFADDR` ioctl. IPv6 works differently, because a host carries exactly one IPv6 loopback address. A fixture that needs a second one uses `fd00::2`, which is unique-local (RFC 4193) and never globally routable. `./le setup install` adds it, and `./le setup check` reports whether it is there. The runner never adds it: the ioctl returns EPERM to an unprivileged process, and `./le verify current mode full` runs as an ordinary user. A test that binds an address this host does not carry fails at once with `loopback_address_missing` and the command to run, rather than timing out on a bind that could not succeed. The check reads the three places a fixture names an address it binds. One is `ze-peer --bind <ip>` on a `cmd=` line. The second is `connection { local { ip <addr> } }` in the config the fixture embeds: Ze sends from that address and listens on it when `accept` is true, so the host must carry it too. The third is `local-address <addr>;` in an ExaBGP-syntax config the exabgp-compat suite keeps beside its `.ci` and names with `option=file:`. That suite has a parser and a runner of its own, so it calls the same scan through `EnsureConfigFileBindAddresses` rather than through the embedded path. A local address outside 127.0.0.0/8 and fc00::/7 is left alone. A config-validation fixture names a routable one (`local { ip 192.0.2.1 }`), the daemon exits before it binds anything, and `./le setup install` adds no such address. The `local-link-local fe80::1` leaf that sits beside `local-address` in an exabgp-compat config is left alone for the same reason. <!-- source: internal/test/runner/loopback.go -- probe, error text, --bind, config-local and local-address scan --> <!-- source: internal/test/cli/cmd_exabgp.go -- runOneExaBGPTest, the exabgp-compat preflight call --> <!-- source: internal/test/runner/loopback_linux.go -- no-op on Linux for IPv4 --> <!-- source: internal/test/runner/loopback_darwin.go -- SIOCAIFADDR on BSD --> <!-- source: internal/le/setup/actions.go -- Answer --> ## Expectations ``` expect=<type>:key=value[:key=value...] ``` ### BGP Wire Expectations ``` expect=bgp:conn=<N>:seq=<N>:hex=<hex-bytes> expect=bgp:conn=<N>:seq=<N>:prefix=<hex-bytes> expect=bgp:conn=<N>:seq=<N>:contains=<hex-bytes> expect=bgp:conn=<N>:seq=<N>:ordered=<hex-bytes> ``` Validates the BGP wire message received: `hex=` matches the exact message, `prefix=` the message start, `contains=` a substring anywhere in one message. Within one `seq` group, `hex`/`prefix`/`contains` checks match in any order and each consumes exactly one received message. `ordered=` checks in a `seq` group form a strict FIFO subqueue for asserting in-order delivery across message boundaries: the front needle must appear in the received message, and one message may consume several consecutive needles, each matched at an advancing offset (so order inside a packed message is enforced too). Use `ordered=` instead of per-message `contains=` when the sender may legally pack several NLRIs into one UPDATE (the forward rail's bucket merge): per-message framing is not a property ze owes, but delivery order is. A message whose content matches only a non-front needle consumes nothing and is reported as a mismatch. <!-- source: internal/test/peer/checker.go -- parseExpectRule, consumeMatches, consumeOrdered --> These forms are peer-block directives (inside a `stdin=<name>:` block); top-level `expect=bgp` lines support `hex=` only. ### JSON Expectations ``` expect=json:conn=<N>:seq=<N>:json=<json-object> expect=json:json=<json-object> ``` Validates the decoded message matches expected JSON. **Validation rules:** - Parsed and compared field-by-field (key order independent) - Volatile fields removed before comparison: `exabgp`, `ze-bgp`, `time`, `host`, `pid`, `ppid`, `counter` - Neighbor normalization: `peer` ↔ `neighbor` treated as equivalent, `direction` field ignored - All non-volatile fields must match exactly <!-- source: internal/test/runner/runner_validate.go -- JSON comparison, volatile field removal --> ### Exit Code Expectations ``` expect=exit:code=<N> ``` Validates the foreground process exit code. A test whose ONLY assertion is `expect=exit:code=0` is **accept-only** (weak) and is gated by a lint; see [Assertion Strength](#assertion-strength-accept-only-tests-and-readback). ### An Unknown Directive or Key Is a Parse Error Every directive the runner reads is declared: the `action=` word, the type after it, and the keys inside it. A word or a key outside its declared set fails the file at discovery, naming what was written, the accepted set, and the line. Nothing is dropped in silence, and nothing is guessed. ``` line 12: unknown action "exepct" (accepts action, await, cmd, command, expect, http, option, reject, stream) line 12: unknown expect type "stdoutt" (accepts bgp, command-error, event, exit, file, json, output, stderr, stdout, stream, syslog) line 12: cmd=foreground: unknown key "env" (accepts exec, exit, name, seq, stdin, timeout) ``` The accepted set in each message is the list the parser gates on, so it cannot describe a vocabulary the runner does not have. The line number is the line in the FILE: comments, blank lines and whole `stdin=` and `tmpfs=` blocks are consumed before the directives are parsed, and the refusal used to count only the directives that survived, which named line 2 for a line that was number 69. <!-- source: internal/test/runner/record_parse_vocabulary.go -- recordActions and the type lists each switch gates on --> <!-- source: internal/test/tmpfs/tmpfs.go -- Line, the directive text with its file line number --> <!-- test: internal/test/runner/record_parse_test.go TestUnknownDirectiveNamesLineAndAccepted, TestDirectiveVocabularyIsLive --> The rule exists because a dropped key takes the whole assertion with it. An `expect=stdout:not-contains=X` line recorded NO assertion and then passed whatever the command printed. Ten such lines were live across seven `.ci` files when the check was added, and one of them was the only assertion its test made. <!-- source: internal/test/runner/record_parse_keys.go -- checkKeys, retiredKeys --> Two spellings get a named message, because both are the right key somewhere else: `not-contains=` belongs to `expect=file:`, and `!contains=` was the `expect=stdout` spelling until stream non-containment moved to `reject=`. ### Stdout Expectations ``` expect=stdout:contains=<text> expect=stdout:pattern=<regex> ``` Two modes: - `contains=` -- substring match against stdout (multiple allowed, all must match) - `pattern=` -- regex match against stdout (uses Go `regexp` syntax) Stdout must NOT contain text is `reject=stdout:contains=`, below. A stream has ONE negation spelling and it lives on `reject=`. ### Stderr Expectations ``` expect=stderr:pattern=<regex> expect=stderr:contains=<text> ``` Two modes: - `pattern=`: regex match against stderr (uses Go `regexp` syntax) - `contains=`: substring match against stderr `pattern=` reads the daemon's stderr alone. `contains=` reads the same combined buffer every stdout assertion reads (see the scope note below), so it matches a needle the command wrote to stdout. Reach for `pattern=` when the stream matters. Stderr must NOT contain text is `reject=stderr:contains=`, below. ### Await (deterministic stderr fence) ``` await=stderr:contains=<text>[:timeout=<dur>] ``` Blocks the runner until the daemon's relayed stderr contains `<text>`, then tears the daemon down. This is a deterministic replacement for a blind `time.sleep` that only held the daemon open long enough for a line to appear. `timeout=` is an optional Go duration (default 10s); on timeout the test fails with a precise message. `<text>` follows the same rule as `expect=stderr:contains=` (the needle must not contain a literal `:`). Pair it with a matching `expect=stderr:contains=` so the line is both the fence and the assertion. Use it for the reject-fence bucket: an external plugin whose refusal aborts the daemon's plugin-startup coordinator (`StartupCoordinator.PluginFailed`) leaves no in-daemon observer able to poll it, so the relayed stderr line is the only non-plugin signal. Choose a needle that is SPECIFIC to the plugin under test, not just a shared phrase. Example (`test/plugin/as112-external-refuses.ci`): the bare "refusing to start as an external plugin process" is emitted verbatim by three plugins, so the needle includes the as112-only tail `-- the address-ownership registry`: ``` cmd=foreground:seq=1:exec=ze -:stdin=ze-bgp:timeout=15s await=stderr:contains=refusing to start as an external plugin process -- the address-ownership registry expect=stderr:contains=refusing to start as an external plugin process -- the address-ownership registry ``` <!-- source: internal/test/runner/await_stderr.go -- parseAwait / awaitDaemonStderr --> ### Syslog Expectations ``` expect=syslog:pattern=<regex> ``` Validates that captured syslog output matches the regex pattern. When any `expect=syslog:` line is present, the test runner automatically starts a UDP syslog server and injects `ze.log.backend=syslog` and `ze.log.destination=127.0.0.1:<port>` into the test environment. <!-- source: internal/test/syslog/testsyslog.go -- UDP syslog server for tests --> ### File Expectations ``` expect=file:path=<rel>:exists=true expect=file:path=<rel>:absent=true expect=file:path=<rel>:contains=<text> expect=file:path=<rel>:not-contains=<text> expect=file:glob=<rel-pattern>:count=<N> expect=file:glob=<rel-pattern>:contains=<text> expect=file:glob=<rel-pattern>:not-contains=<text> ``` Validates files after the test process or peer sequence has completed. Paths and glob patterns are relative to the test's own work directory, which is where every child ran and where a daemon writes its artifacts. A test that declares `tmpfs=` files gets them in that same directory, so the two spellings name one place. For glob `contains`, at least one matched file must contain the text. For glob `not-contains`, no matched file may contain it. Use file expectations for post-run artifacts such as generated configs, pointer files, and logs. Do not write shell just to inspect files. <!-- source: internal/test/runner/runner_validate.go -- validateFileChecks --> ### Negative Expectations (reject) ``` reject=stderr:contains=<text> reject=stderr:pattern=<regex> reject=stdout:contains=<text> reject=stdout:pattern=<regex> reject=syslog:pattern=<regex> reject=bgp:conn=<N>:pattern=<hex> ``` Inverse of `expect=` -- the test **fails** if the pattern matches. Used to verify that unwanted output (e.g., deprecated warnings, ERROR-level messages) does NOT appear. | Type | Description | |------|-------------| | `reject=stderr:contains=<text>` | Fail if the combined output contains substring (the mirror of `expect=stderr:contains=`) | | `reject=stderr:pattern=<regex>` | Fail if stderr matches regex | | `reject=stdout:contains=<text>` | Fail if stdout contains substring | | `reject=stdout:pattern=<regex>` | Fail if stdout matches regex | | `reject=syslog:pattern=<regex>` | Fail if syslog output matches regex | | `reject=bgp:conn=<N>:pattern=<hex>` | Fail if connection N of a ze-peer receives a frame carrying these wire bytes | <!-- source: internal/test/runner/runner_exec.go -- stdout reject handling --> <!-- source: internal/test/runner/runner_validate.go -- stderr/syslog reject handling --> <!-- source: internal/test/peer/reject.go -- reject=bgp, the wire rejection --> #### `reject=bgp` -- bytes a peer must never receive `reject=bgp` is the only reject type ze-peer reads, because ze-peer is what sees the wire. It goes inside the peer's stdin block, beside the `expect=bgp` lines. The hex needle is matched on wire bytes at a byte BOUNDARY, which `expect=bgp:contains=` is not: that one is a plain substring match over the hex text, so it can also match at an odd nibble offset. Five properties make it an assertion rather than a hope: - It is never consumed. Every frame the peer's message loop reads is checked against it. The `option=linger` loop keeps checking after completion. Pair the rejection with `option=linger:value=true` to hold it open for the whole test. - **A rejection found during linger RETRACTS the success token.** Under `option=linger` the peer prints its success token BEFORE the loop, because teardown is a kill and a post-run print can be lost. The verdict reads that token, so a rejection arriving afterwards has to withdraw it: the peer prints `ZE-PEER-REJECTED` on its own output, and the verdict reads the retraction after the token and lets it win. Without that channel a linger rejection was detected, returned, and then discarded, which made every negative assertion held open by linger vacuous. <!-- source: internal/test/peer/reject.go -- RejectionMarker, (*Peer).rejected --> <!-- source: internal/test/runner/peer_contract.go -- failedCheckPeers, peerRejectionMarker --> - **Check mode only.** Sink and echo peers read every accepted connection concurrently against one checker, so a `conn=` could not select the session the frame arrived on. The runner refuses the file, and ze-peer refuses to start. - The block MUST also carry an `expect=bgp:conn=<N>` on the same connection, and the runner refuses the file otherwise. That rule is necessary and it is not sufficient. It bounds how long the rejection is checked. It cannot say whether the forbidden bytes would have arrived inside that window. **The author owes the second half.** Send the delivery LAST on that connection, so a leak of the governed route arrives ahead of it. Or hold the session open with `option=linger`. A connection stops being read once its expectations complete, and its rejection then passes for the one reason a rejection must not. - An odd-length or non-hexadecimal needle is a parse error, because a needle that can never match would pass for that same reason. The runner reads the line with ze-peer's own parser (`peer.ParseRejectRule`), so a typo fails the FILE rather than the peer: a rejection rejected inside ze-peer would stop it binding, and the runner would report that as a bind timeout. ``` stdin=external:terminator=EOF_EXTERNAL option=tcp_connections:value=1 option=linger:value=true # The delivery that makes the rejection an assertion rather than an empty session. expect=bgp:conn=1:seq=1:contains=18C00002 # 180A0100 is 10.1.0.0/24, which NO_ADVERTISE forbids on any peer. reject=bgp:conn=1:pattern=180A0100 EOF_EXTERNAL ``` ## Assertion Strength: accept-only tests and readback A test whose ONLY assertion is `expect=exit:code=0` is **accept-only** (weak): it proves a config or command was ACCEPTED, never that it parsed to the CORRECT tree. A parser that accepts `interval 300` but stores `0`, or silently drops a `source 0.0.0.0/0` block, still passes such a test green. This is the functional-suite analog of the "count-only assertion" mistake class (`ai/rules/testing.md`). A lint enforces that the class cannot GROW: `TestCIAcceptOnlyLint` walks every `test/**/*.ci`, classifies each with the single accept-only predicate, and FAILS on a NEW accept-only test that is neither strengthened nor annotated. Existing accept-only tests are grandfathered in `test/.accept-only-baseline` (a sorted allow-list that only shrinks; strengthening or annotating a test removes its line). Correctly EXCLUDED (never weak): a test whose real check lives in a tmpfs `set -e` script (e.g. `test/managed/auth-reject.ci`), and any `reject=` test. <!-- source: internal/test/runner/accept_only.go -- isAcceptOnly, acceptOnlyAnnotation, the accept-only predicate + baseline --> <!-- source: internal/test/runner/accept_only_lint_test.go -- TestCIAcceptOnlyLint, TestCIAcceptOnlyLintFlags/Allows --> ### Strengthen with a readback Add a second `cmd=` that dumps the parsed tree and assert a representative value with `expect=stdout:contains=` / `pattern=`. `show config dump -` reads a config from stdin and answers the stored tree, and `| json` renders it, so the assertion observes the parsed VALUE, not just that parsing did not error. ``` cmd=foreground:seq=1:exec=ze config validate -:stdin=config:exit=0 cmd=foreground:seq=2:exec=ze cli -c "show config dump - | json":stdin=config-dump expect=exit:code=0 expect=stdout:pattern="interval": "300" ``` Two gotchas, both load-bearing: - **`ze config dump` requires a `bgp { }` block** (it resolves the full BGP tree) where `ze config validate` does not. To keep proving that a subsystem-only config validates, keep the original `validate` step on the subsystem-only stdin block and run the `dump` readback against a second stdin block that prepends a minimal `bgp { router-id ... }`. - **A needle containing `:` followed by something shaped like `key=` must use `pattern=`.** ~~Only `json=`/`text=`/`hex=`/`pattern=` preserve colons, and `contains=` truncates at the first colon.~~ - **Corrected 2026-07-30.** That claim was true until `ParseKVPairs` became boundary-aware. `contains=` now keeps an ordinary colon, so `contains=error: no such peer` asserts the whole sentence. - What still splits is a colon that introduces a real key token: a letter, then letters, digits, `-` or `_`, then `=`. That split is deliberate, because it is how the engine-step form `contains=aes-cbc:timeout=25` keeps working. So `contains=note:level=high` still splits at `:level=`, and it is the one shape that needs `pattern=`. - The old behavior was not harmless while it lasted. A sweep on 2026-07-29 found **203 assertions across 15 suites** silently reduced to the text before their first colon. The re-armed assertions then exposed a security test (`test/appliance/appliance-push-image-escape.ci`). That test had never once executed the path-traversal guard it was named for. - **Keep a `pattern=` needle free of the substrings `json=`, `text=`, and `hex=`.** `ParseKVPairs` extracts a complex-key value by `strings.Index` of the first such marker, so a needle that itself contains one of them is mis-split. None of the readback needles above contain these markers. This note is forward guidance for new needles. <!-- source: internal/test/ci/ciformat.go -- ParseKVPairs, complexKeys (json/text/hex/pattern consumed whole), splitOnKeyBoundary (an ordinary colon stays in the value; only a colon introducing a key= token splits) --> <!-- source: internal/component/config/cli/cmd_dump.go -- cmdDump reads stdin and prints the parsed tree --> ### Annotate when a unit test already covers the value When a unit test already asserts the parsed value, a readback would duplicate it. Mark the test accept-only instead, with a comment naming the covering test: ``` # accept-only: md5 { password; ip } value-parsing is unit-covered by # TestParsePeerMD5FieldsParsed (internal/component/bgp/reactor/config_test.go). ``` The marker is a comment line whose content is `accept-only:` followed by a non-empty reason. A file carrying it is allowlisted by the lint without a baseline entry. Keep the reason greppable and truthful (name the covering test). <!-- source: internal/test/runner/accept_only.go -- acceptOnlyAnnotation, hasAcceptOnlyAnnotation --> ## Actions ``` action=<type>:key=value[:key=value...] ``` ### Notification ``` action=notification:conn=<N>:seq=<N>:text=<message> ``` Sends NOTIFICATION with shutdown message. ### Send Raw ``` action=send:conn=<N>:seq=<N>:hex=<hex-bytes> ``` Sends raw bytes to peer. ### Rewrite Config File ``` action=rewrite:conn=<N>:seq=<N>:source=<tmpfs-file>:dest=<config-file> ``` Copies a tmpfs file over the daemon's config file. Used with `action=sighup` to test config reload. It also writes a marker a second process can wait on, which is its other use: `test/reload/config-apply-ordering-address-swap.ci` copies one file to `session-returned.txt` once the restarted BGP session has re-announced its routes, and the test's own driver waits for that file before it signals the daemon. When the rewrite is the LAST item the peer block queues, the exchange is over and the peer completes on it, exactly as `action=sighup` and `action=sigterm` do. Without that the peer kept matching, and the daemon's shutdown NOTIFICATION arrived against an empty expectation list and failed the test as a message mismatch. | Key | Description | |-----|-------------| | `conn` | Connection number triggering the rewrite | | `seq` | Sequence number (after matching messages) | | `source` | Source file name in tmpfs | | `dest` | Destination file name in tmpfs (usually `ze-bgp.conf`) | ### Send SIGHUP ``` action=sighup:conn=<N>:seq=<N> ``` Sends SIGHUP to the daemon process. Reads PID from `daemon.pid` in the tmpfs directory (written automatically by the test runner). | Key | Description | |-----|-------------| | `conn` | Connection number triggering the signal | | `seq` | Sequence number (after matching messages) | ### Send SIGTERM ``` action=sigterm:conn=<N>:seq=<N> ``` Sends SIGTERM to the daemon process. Reads PID from `daemon.pid` in the tmpfs directory (written automatically by the test runner). After sending SIGTERM, the connection is expected to close (daemon shuts down gracefully). | Key | Description | |-----|-------------| | `conn` | Connection number triggering the signal | | `seq` | Sequence number (after matching messages) | ## HTTP Checks HTTP checks validate web endpoint responses after all `cmd=` processes have started. Executed in `seq` order with automatic retry on connection errors (server starting up). ### Assertion Checks (get/post) ``` http=get:seq=N:url=URL:status=CODE[:contains=TEXT][:bodyfile=PATH][:header=NAME: VALUE] http=post:seq=N:url=URL:status=CODE[:contains=TEXT][:bodyfile=PATH][:sendfile=PATH][:content-type=TYPE][:header=NAME: VALUE][:insecure-tls=true] ``` | Key | Required | Description | |-----|----------|-------------| | `seq` | Yes | Execution order (>= 1, lower first) | | `url` | Yes | Request URL (supports `$PORT` and `$PORT2` substitution) | | `status` | Yes | Expected HTTP status code | | `contains` | No | Expected body substring | | `bodyfile` | No | Path to file with expected body (exact match, resolved relative to `.ci` file) | | `sendfile` | No | Path to file sent as POST request body, resolved from tmpfs first | | `content-type` | No | Request body content type for `sendfile`, defaults to `application/json` | | `header` | No | Request header in `Name: Value` wire form. **Repeatable** -- the only key that can appear more than once on one line | | `insecure-tls` | No | Set `true` for self-signed local HTTPS endpoints | <!-- source: internal/test/runner/runner_validate.go -- executeOneHTTPCheck --> #### Request Headers (`header=`) `header=` takes the header exactly as it appears on the wire, `Name: Value`. Repeat `header=` to set several headers on one check. It works on `get`, `post`, and `wait`. ``` http=post:seq=1:url=http://127.0.0.1:$PORT/mcp:status=200:sendfile=call.json:header=MCP-Protocol-Version: 2026-07-28:header=Mcp-Method: tools/call:header=Mcp-Name: ze ``` | Behavior | Detail | |----------|--------| | Splitting | On the **first** colon only, so a value can contain colons (`header=Referer: http://127.0.0.1:8080/page`) | | Whitespace | Trimmed around both name and value, so `header=Foo: bar` and `header=Foo:bar` are equivalent | | Precedence | Applied **after** the `sendfile` default `Content-Type`, so an explicit `header=Content-Type: ...` wins | | Repeats of one name | The first occurrence replaces the value. Each later occurrence adds one more value to that field | | `Host` | Routed to the request's Host field, because `net/http` ignores a `Host` entry in the header map | | Malformed | A `header=` value with no colon is a **parse error** naming the offending value, never a silent drop | | Value stops at | The next known key marker (`:status=`, `:contains=`, the next `:header=`, ...), like every other key | <!-- source: internal/test/runner/record_parse.go -- parseHTTP header scan loop --> <!-- source: internal/test/runner/runner_validate.go -- applyCheckHeaders --> Retries up to 20 times at 200ms intervals on transient connection errors (ECONNREFUSED, ECONNRESET, EOF). Non-connection errors (wrong status, missing content) fail immediately. ### Readiness Polls (wait) ``` http=wait:seq=N:url=URL:status=CODE[:contains=TEXT][:header=NAME: VALUE][:timeout=DUR] ``` | Key | Required | Default | Description | |-----|----------|---------|-------------| | `seq` | Yes | - | Execution order (>= 1) | | `url` | Yes | - | Request URL | | `status` | Yes | - | Expected HTTP status code | | `contains` | No | - | Expected body substring | | `header` | No | - | Request header in `Name: Value` form, repeatable (see above) | | `timeout` | No | `15s` | Poll timeout duration | <!-- source: internal/test/runner/runner_validate.go -- executeOneHTTPWait --> Unlike assertion checks, `wait` retries on **all** failures: connection errors, wrong status codes, and content mismatches. Polls at 500ms intervals until the condition is met or the timeout expires. Wait checks run **before** assertion checks, making them suitable for waiting until a server has populated data (e.g., routes injected by an async plugin). ### Example ``` cmd=background:seq=1:exec=ze-peer --port $PORT:stdin=peer cmd=background:seq=2:exec=ze -:stdin=ze-bgp # Wait until routes are available before checking graph output http=wait:seq=1:url=http://127.0.0.1:$PORT2/lg/graph?prefix=10.10.1.0/24&mode=aspath&format=text:status=200:contains=AS2914:timeout=15s # Exact SVG match against reference file http=get:seq=1:url=http://127.0.0.1:$PORT2/lg/graph?prefix=10.10.1.0/24&mode=aspath:status=200:bodyfile=expect/graph.svg # Substring check http=get:seq=2:url=http://127.0.0.1:$PORT2/lg/graph?prefix=10.10.1.0/24&mode=nexthop&format=text:status=200:contains=egress ``` ## Sleeps and their justification markers <!-- source: internal/le/doc/wiring -- checkSleepJustification --> <!-- source: internal/le/hookruntime/writeedit.go -- writeCISleep --> Every `time.sleep(` in a live `.ci` carries a marker comment in the form `// sleep(<kind>): <reason>`, on one `#` comment line directly above the sleep, indented to match it exactly. The embedded `.ci` observer body is indentation sensitive. Two producers enforce the marker: `checkSleepJustification` in `internal/le/doc/wiring` (run by `./le doc wiring`, scoped to changed `.ci` files, listing every unjustified `file:line` and exiting 1), and `writeCISleep` in `internal/le/hookruntime/writeedit.go`, which blocks a Write or Edit that introduces an unmarked sleep. The kinds are a closed set, and each one owes a different reason: | Kind | What the reason states | |------|------------------------| | `poll-interval` | The real condition the enclosing loop breaks or returns on. This is already a deterministic wait; the sleep is only its granularity | | `timer` | The delay itself IS the behavior under test, and the mechanism plus where its period is set | | `timeout-under-test` | The fixed internal timeout the sleep waits out, which the test asserts on | | `needs-linux` | A dataplane effect (tc, qdisc, nft, kernel FIB) with no readback in the driver, convertible only after a QEMU run | | `no-signal` | The awaited effect exposes no queryable state to this driver, and what is held until instead | A free-text `// settle` comment is insufficient: it names no mechanism a reader can check. "The tracker pushes live carrier once a second" is a reason a later reader can overturn; "needs a moment" is the shape that makes a deliberate timer and a guessed duration the same line of code. A separate ratchet caps how MANY sleeps exist: the total `time.sleep(` count across `test/**/*.ci` may not exceed the committed baseline in `test/.ci-sleep-baseline`, and `./le doc wiring` fails when it does. The markers cap how many are unexplained. ## The compiled observer API <!-- source: internal/test/fixture/fixture.go -- Register, Run, Observe, observeConfigured, Dispatch, Poll, ReportFailure --> Compiled `.ci` observers live under `internal/test/fixture`. They use `pkg/plugin/sdk` for the five-stage plugin protocol (`Plugin.Run` owns it) and the local `fixture` package for registration, dispatch, polling and failure reporting. | Function | Purpose | |----------|---------| | `fixture.Register(name, driver)` | Register one compiled fixture command | | `fixture.Run(args)` | Dispatch `ze-test fixture <name> [args...]` | | `fixture.Observe(...)` | Connect through the SDK, complete startup, run the scenario after all plugins are ready, then request shutdown | | `observeConfigured(...)` | Install callbacks before startup, then run the same observer lifecycle. It is unexported, so only a fixture in this package calls it | | `fixture.Dispatch(...)` | Send one command and decode its JSON answer into a Go value | | `fixture.Poll(...)` | Retry a predicate until success, exhaustion, or context cancellation | | `fixture.ReportFailure(err)` | Emit the `ZE-OBSERVER-FAIL` sentinel `checkObserverSentinel` (`internal/test/runner/runner_validate.go`) detects | | `sdk.Plugin.DispatchCommand(...)` | Send a typed command request through the plugin connection | `fixture.Poll` around `fixture.Dispatch` is the payload-predicate wait: it blocks until the observed payload matches, within a bounded attempt count. It is what replaces a `time.Sleep` followed by a one-shot assertion. `fixture.Observe` can request a clean daemon shutdown even after an assertion failed, so the daemon's exit code does not prove the observer's assertion. A failing observer returns an error, which `fixture.Run` hands to `fixture.ReportFailure`. **A fixture-driven `.ci` needs no `bgp` block and no `ze-peer` to get a daemon that stops.** `request shutdown` reaches a reactorless daemon through the shutdown callback the daemon wires beside its plugin server, before any plugin can dispatch, so a BFD-only or DHCP-only configuration stops on the fixture's request like a BGP one. Adding a BGP peer only to make the daemon stoppable adds a second protocol to the test's failure surface: `test/bfd/bfd-detection-interval.ci` was red for exactly that reason until 2026-09-07. <!-- source: cmd/ze/hub/main.go -- apiServer.SetShutdownFunc; internal/component/plugin/server/system.go -- handleDaemonShutdown --> **`option=asn` moves both AS numbers.** The AS is resolved once and written into the two-octet My Autonomous System field AND the four-octet AS capability, so the two disagree only where a `.ci` states a malformed capability 65 itself (see "Capability Control" above). RFC 6793 Section 4.1 states the precedence a receiver applies: it "MUST use the AS number encoded in the Capability Value field of the 'support for four-octet AS number capability' in lieu of the 'My Autonomous System' field of the OPEN message". So a half-applied option leaves the capability carrying ze's own AS, ze reads THAT, finds an AS the session is not configured for, and answers OPEN Message Error / Bad Peer AS under RFC 4271 Section 6.2. The refusal is RFC 4271's; RFC 6793 decides which field ze read. <!-- source: internal/test/peer/open.go -- buildOpen; internal/component/bgp/reactor/peer.go -- openAdvertisedAS --> **The sentinel is written where the scenario fails, BEFORE `request shutdown`.** The runner reads it out of the DAEMON's stderr, and the daemon relays a plugin's stderr only while both processes live (`internal/component/plugin/process`, `relayStderrFrom`). `fixture.Run` reports the same error after `Plugin.Run` returns, which is after the daemon was asked to stop, so that line reaches the runner only when it wins the race with the shutdown. Measured 2026-09-02: an observer asserting a route that can never arrive still passed `test/plugin/rpki-group-action.ci`, and three plugin cases (`fib-table`, `metrics-name-show`, `modify-increment-localpref`) passed with a failing observer. `fixture.ReportFailure` emits the FIRST failure only, so the report at the failure site and the one on the way out are one line. <!-- source: internal/test/fixture/fixture.go -- observeConfigured, ReportFailure --> <!-- source: internal/component/plugin/process/process.go -- relayStderrFrom --> **A driver's working directory is the per-test directory, so a driver names its files relatively and `fixture.Run` refuses to start in the checkout.** The runner creates one directory for each test, gives it to every child of that test and removes it at the end (`Record.WorkDir`, `internal/test/runner/runner_exec.go`), so `daemon.ready`, `testkey` and `<case>.conf` are written where the test can find them and nothing survives the run. The same names in the checkout land beside tracked source: on 2026-09-05 a run left two ed25519 private keys and five config files at the repository root, none of them ignored, so a `git add -A` from any session would have committed a private key. `fixture.Run` therefore reads its own working directory first and exits 1 with the `ZE-OBSERVER-FAIL` sentinel when that directory holds the ze `go.mod` (`sessionpath.IsRepoRoot`). A driver that needs the checkout reads `$ZE_REPO_ROOT`, which the runner exports; it never reads the working directory for one. <!-- source: internal/test/fixture/fixture.go -- Run, refuseRepoRoot --> <!-- test: internal/test/fixture/fixture_test.go -- TestRunRefusesADriverStartedInTheCheckoutRoot, TestRunDispatchesADriverStartedOutsideTheCheckoutRoot --> An observer assertion is therefore worth only what its wait is worth: a value that a plugin fills asynchronously (the Adj-RIB-In after RPKI validation, the SPF table after the first run, a session that drops when the peer completes) needs `fixture.Poll` around the read, never a fixed settle before it. ## Engine Steps Engine steps drive a live daemon through CLI dispatch, first-class in `.ci` instead of an embedded Python observer. The runner serializes the parsed steps to `engine-steps.json` in the test tmpfs; the `.ci` declares the executor as an external plugin (`run "ze-test engine-steps ./engine-steps.json"`), which runs the steps from `OnAllPluginsReady` and reports failures via the `ZE-OBSERVER-FAIL` sentinel the runner gates on. ``` command=<cli command text> stream=<monitor command text> expect=output:<predicate>[:timeout=<dur>] expect=event:namespace=<ns>:name=<name>[:timeout=<dur>] expect=stream:<predicate>[:timeout=<dur>] expect=command-error:contains=<text> ``` `command=`/`stream=` keep their full raw text (colons included). `expect=output` re-dispatches the most recent `command=` until its predicate holds or the timeout expires; `expect=stream` matches delivered `stream=` events; `expect=event` matches a delivered event by its `(namespace, name)` identity. ### expect=event The executor declares ONE startup event subscription, derived from the `expect=event` steps of the file itself, and asks for enveloped delivery. A `.ci` therefore names each event once, in the step that waits for it, and nothing else declares the subscription. A startup subscription rides the `ready` RPC, so it is registered before any plugin's `OnAllPluginsReady` runs. That is what lets a step observe the FIRST delivery of an event the daemon emits while it starts. `test/ipsec/ipsec-sa-installed.ci` is the case that forced it: the IKE engine emits `vpn-ipsec`/`sa-up` from its startup peer reconciliation, so a subscription dispatched from the step could only ever see a SECOND establishment, and a test with no re-negotiation has none. Enveloped delivery wraps each event with its `(namespace, event)` identity (`rpc.EventEnvelope`), which is what tells one subscribed event from another once several share the one subscription. A bare payload decodes with an empty namespace and event, so a delivery the executor did not ask to be enveloped can never satisfy a step. Two consequences follow, and both are load-bearing when writing a test: - **A step list names ONE namespace.** `rpc.SubscribeEventsInput` carries one, so steps naming two are REFUSED before the executor dials, with a message naming both. Split the test rather than subscribing to one and dropping the rest. - **A step matches ANY delivery of that identity, scrollback included.** It cannot assert "another one, after this point". A second `sa-up` after an operator `clear` re-matches the first, so re-establishment is asserted by a separate test (`test/ipsec/ipsec-clear-reestablish.ci`), not by a second `expect=event`. <!-- source: internal/test/runner/engine_steps.go -- EngineStepSubscriptionFor, RunEngineSteps --> <!-- source: internal/test/cli/cmd_engine_steps.go -- cmdEngineSteps --> <!-- source: internal/component/plugin/server/dispatch.go -- buildEventEnvelope, resolveSubscriptionNamespace --> ### expect=command-error `expect=command-error:contains=<text>` asserts that the PRECEDING `command=` FAILED, and that its message contains the text. Use it for a command that must refuse. The plugin SDK turns a `StatusError` response into a Go error, so without this directive the command step aborts the run before any `expect=` is reached, and no `.ci` can assert an operational error at all. That left one class untestable end to end, and it is the class where a wrong answer costs most: a command that must refuse is exactly the one whose failure mode is answering confidently instead. `test/ipsec/ipsec-dataplane-show.ci` uses it to prove that a dataplane which cannot be read SAYS so rather than rendering an empty table. It takes `contains=` only, and no timeout. An error is the result of one dispatch, so re-dispatching until one appears would wait for a state change no `expect=` can cause. A command failure that NO `expect=command-error` consumes still fails the run, whether the next step is of another kind or the command is the last step in the file. Every `.ci` written before this directive existed relies on that. <!-- source: internal/test/runner/engine_steps.go -- parseEngineExpectCommandError, RunEngineSteps --> **It must not be vacuous.** A `command=` that SUCCEEDS does not satisfy `expect=command-error`: the step fails with "the preceding command succeeded". Without that rule the directive would pass for the very regression it guards, a refusal quietly becoming a successful empty answer (`TestRunEngineStepsCommandErrorRequiresAFailure`). ### expect=output / expect=stream predicates The optional trailing `:timeout=<dur>` is split off the END first, so a predicate operand may itself contain `:` (a compact-JSON fragment, an IPv6 address). The remainder is one predicate: | Predicate | Surfaces | Holds when | |-----------|----------|-----------| | `contains=<text>` | output, stream | the output contains the substring (the default) | | `matches=<regexp>` | output, stream | the Go `regexp` matches the output (compiled at parse time, so a bad regexp fails the test immediately, not at timeout) | | `absent=<text>` | output only | the output does NOT contain the substring | | `json=<dotted.path>=<value>` | output only | the dotted path into the JSON `data` field stringifies to `<value>` | `json=` walks the raw `data` field (not `status data`): each `.`-segment indexes a JSON object by key or a JSON array by integer index (0..len-1; out-of-range or missing is "not yet", named at timeout). The leaf is compared as a string (numbers/bools stringified via JSON). `absent=`/`json=` are `expect=output` only: they re-dispatch a query, whereas `expect=stream` is an append-only event stream with no "absent" and no single-event JSON path. **`absent=` must be non-vacuous.** An `absent=` on output that was never populated passes instantly (false green). Precede it with a step that makes the substring present (a `contains=`/`json=` after an inject), then the transition (e.g. a withdraw) the `absent=` proves. See `test/plugin/engine-steps-predicates.ci`. <!-- source: internal/test/runner/engine_steps.go -- parseEngineExpectContains, engineOutputSatisfied, engineJSONPathValue --> ### Example ``` command=request bgp rib inject 10.0.0.1 ipv4/unicast 172.16.0.0/16 origin igp nexthop 10.0.0.2 command=show rib expect=output:matches=172\.16\.[0-9.]+/16:timeout=10 expect=output:json=0.prefix=172.16.0.0/16:timeout=10 command=request bgp rib withdraw 10.0.0.1 ipv4/unicast 172.16.0.0/16 command=show rib expect=output:absent=172.16.0.0/16:timeout=10 ``` ## Complete Example ``` # Embed config using Tmpfs tmpfs=test.conf:terminator=EOF_CONF peer test-peer { remote { ip 127.0.0.1; as 65000; } router-id 10.0.0.2; local-address 127.0.0.1; local-as 65533; hold-time 180; family { ipv4/unicast; } announce { ipv4 { unicast 10.0.0.0/24 next-hop 10.0.1.254; } } } EOF_CONF # Test configuration option=file:path=test.conf option=asn:value=65000 # Expected API command and wire output cmd=api:conn=1:seq=1:text=update text origin set igp nhop set 10.0.1.254 nlri ipv4/unicast add 10.0.0.0/24 expect=bgp:conn=1:seq=1:hex=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF002F02000000144001010040020602010000FFFD4003040A0001FE180A0000 # EOR cmd=api:conn=1:seq=1:text=announce eor ipv4/unicast expect=bgp:conn=1:seq=1:hex=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00170200000000 ``` ## Consumers Different components consume different line types: | Line Type | Consumer | |-----------|----------| | `stdin=` | Test runner (pipes to processes) | | `tmpfs=` | Test runner (writes to temp) | | `option=` | Test runner + ze-peer | | `cmd=api:` | nobody. `parseCmd` stores the text on the message and only the failure reporter prints it, so the line documents the command that produced the expected bytes and executes nothing. A test that expects it to inject routes asserts against an empty RIB | | `cmd=foreground:`, `cmd=background:` | Test runner (process orchestration) | | `expect=exit:`, `stdout:`, `stderr:`, `json:`, `syslog:`, `file:` | Test runner | | `reject=stderr:`, `reject=syslog:` | Test runner (negative expectations) | | `http=get:`, `http=post:` | Test runner (HTTP assertion checks) | | `http=wait:` | Test runner (HTTP readiness polls) | | `expect=bgp:` | ze-peer | | `action=notification:`, `action=send:` | ze-peer | | `action=rewrite:`, `action=sighup:`, `action=sigterm:` | ze-peer (reload/signal tests) | <!-- source: internal/test/peer/expect.go -- ConsumesLine, the ze-peer-consumed set --> <!-- source: internal/test/runner/record.go -- Record, State --> Lines not recognized by a consumer are ignored. ### A check-mode peer block MUST declare a ze-peer-consumed expectation **The consumer split above is load-bearing, not trivia.** Only the four ze-peer rows reach ze-peer. Everything else -- including `expect=json` -- is validated by the test runner from its own copy of the messages. A check-mode `ze-peer` with no consumed directive has nothing to check, so it prints `no test data available to test against` and exits 1 **before binding a listening socket**. ze then dials a dead port, gets connection refused, and backs off 5->10->20->40s. That looks exactly like a BGP establishment stall and cost a multi-day investigation before the cause was found in the harness. So a peer block whose only expectation is `expect=json` runs no BGP at all. The parser now rejects that at discovery time, naming the file and the remedy: ``` stdin=peer block: check-mode ze-peer (cmd seq=1) declares no ze-peer-consumed expectation, so it exits with "no test data available to test against" before binding a listening socket and the test can only pass vacuously. ``` | Want | Do | |------|----| | Assert the wire exchange | Add `expect=bgp:conn=N:seq=N:hex=...` (or an `action=send/notification/rewrite/close/sighup/sigterm`) to the peer block | | A peer that is only a dial target for ze (routes injected through an external process plugin, assertions made by that plugin or `http=`) | Run it as `ze-peer --mode sink` -- sink/echo/inject peers legitimately declare nothing | `expect=json` still works, but only **in addition to** a consumed directive: it cannot make the peer listen. <!-- source: internal/test/runner/peer_contract.go -- validatePeerBlocks, the parse-time guard --> <!-- test: internal/test/runner/peer_contract_test.go TestParseAndAdd_CheckPeerWithOnlyJSONExpectRejected --> ### A ze-peer always governs its test's result A test with a **check-mode** ze-peer is never self-validated: the peer must print `successful` and its `expect=json` expectations must match, whatever else the file asserts. An `expect=exit:code=0` is additive and does NOT disable the BGP checks. Until 2026-07-16 it did, which is how a test could pass on ze's exit code while its peer never listened and its JSON assertion never ran. sink/echo/inject peers do not govern (they never report completion), so a test whose only peers are scaffolding is still governed by its own exit/output/file assertions. <!-- source: internal/test/runner/peer_contract.go -- isSelfValidated, hasCheckPeer --> <!-- test: internal/test/runner/peer_contract_test.go TestIsSelfValidated, TestHasCheckPeer --> ### A scaffolding ze-peer is signaled at teardown A sink, echo or inject `ze-peer` never ends itself: its accept loop runs until its context is cancelled, and `ze-test peer` maps SIGTERM to that cancel. The runner sends that SIGTERM at teardown, after the last step and before the barrier that collects peer output. The peer exits with status 0 and its capture is complete, so a `.ci` author needs no teardown directive and must not add one. A **check-mode** peer is never signaled. It exits by itself when its expectations are met, and the runner reads its verdict from the capture a signal would truncate. Before 2026-08-10 no code signaled a scaffolding peer, so the drain barrier waited out its full 10s grace on every `--mode sink` or `--mode echo` test. That cost was not only latency: `test/plugin/event-predicate-wait.ci` failed at its 15s budget with `TYPE: timeout` while the daemon itself completed correctly. <!-- source: internal/test/runner/runner_exec_util.go -- terminateScaffoldPeers, drainPeers --> <!-- source: internal/test/runner/runner_exec.go -- runOrchestrated teardown, terminateScaffoldPeers call --> <!-- source: internal/test/cli/cmd_peer.go -- cmdPeer, SIGTERM mapped to the peer's context cancel --> <!-- test: internal/test/runner/peer_teardown_test.go TestTerminateScaffoldPeersReapsSinkPeer, TestTerminateScaffoldPeersLeavesCheckPeer --> ## Migration from Old Format Old format (deprecated): ``` option:file:test.conf option:asn:65000 1:raw:FFFF... 1:json:{...} ``` New format: ``` option=file:path=test.conf option=asn:value=65000 expect=bgp:conn=1:seq=1:hex=FFFF... expect=json:conn=1:seq=1:json={...} ``` Key changes: - `=` instead of `:` after action - Explicit `conn=` and `seq=` for message ordering - `hex=` prefix for wire bytes - `json=` prefix for JSON data ## Editor Test Format (.et) The `.et` format extends `.ci` for interactive editor testing. Tests are located in `test/editor/`. <!-- source: internal/component/cli/testing/parser.go -- editor test parsing --> ### Overview Editor tests simulate user input sequences against the headless configuration editor and verify state changes. ### Input Actions | Action | Purpose | Example | |--------|---------|---------| | `input=type:text=<text>` | Type text | `input=type:text=edit bgp` | | `input=key:name=<key>` | Send special key | `input=key:name=tab` | | `input=tab` | Tab key (shorthand) | `input=tab` | | `input=enter` | Enter key (shorthand) | `input=enter` | | `input=ctrl:key=<c>` | Ctrl+key | `input=ctrl:key=u` | | `input=space` | Space key | `input=space` | Named keys `input=key:name=<key>` accepts: `tab`, `enter`, `esc`, `escape`, `up`, `down`, `left`, `right`, `backspace`, `delete`, `home`, `end`, `pgup`, `pgdn`, `pgdown`, `space`. `shift+tab` is handled separately as a modifier. <!-- source: internal/component/cli/testing/input.go -- keyNameToCode, toKeyMessages --> ### Expectations | Expectation | Purpose | Example | |-------------|---------|---------| | `expect=context:path=<p>` | Context equals path | `expect=context:path=bgp.peer.1.1.1.1` | | `expect=context:root` | Context is root | `expect=context:root` | | `expect=completion:contains=<list>` | Completions include all items | `expect=completion:contains=set,delete,edit` | | `expect=completion:excludes=<list>` | Completions must NOT include items | `expect=completion:excludes=vpp,kernel` | | `expect=completion:count=<N>` | Number of completions | `expect=completion:count=5` | | `expect=ghost:text=<suffix>` | Ghost text suggestion | `expect=ghost:text=-id` | | `expect=dirty:true` | Has unsaved changes | `expect=dirty:true` | | `expect=content:contains=<text>` | Config content includes text | `expect=content:contains=router-id` | | `expect=content:not-contains=<text>` | Config content must NOT include | `expect=content:not-contains=old-value` | | `expect=content:lines=<N>` | Config content line count | `expect=content:lines=5` | | `expect=viewport:contains=<text>` | Displayed output includes text | `expect=viewport:contains=10.0.0.1` | | `expect=viewport:not-contains=<text>` | Displayed output must NOT include | `expect=viewport:not-contains=error` | | `expect=errors:count=<N>` | Validation error count | `expect=errors:count=0` | | `expect=status:contains=<text>` | Status message | `expect=status:contains=committed` | | `expect=hint:contains=<text>` | Second message line includes text | `expect=hint:contains=show the peers` | | `expect=hint:empty` | Second message line is blank | `expect=hint:empty` | | `expect=explanation:contains=<text>` | Revealed long explanation includes text | `expect=explanation:contains=established` | | `expect=explanation:empty` | No explanation is revealed | `expect=explanation:empty` | | `expect=error:none` | No command error | `expect=error:none` | | `expect=timer:active` | Confirm timer running | `expect=timer:active` | | `expect=file:path=<rel>:contains=<text>` | On-disk file content | `expect=file:path=test.conf:contains=bgp` | | `expect=file:path=<rel>:not-contains=<text>` | File must NOT contain | `expect=file:path=test.conf:not-contains=old` | | `expect=file:path=<rel>:absent=true` | File does not exist | `expect=file:path=test.conf:absent=true` | <!-- source: internal/component/cli/testing/expect.go -- editor expectation types --> `hint` reads the second message line with every style stripped. That row carries the completion hint, and the summary of the candidate the operator selected. `explanation` reads the long explanation Tab reveals, without the box that frames it on screen. Both accept `empty` and `contains=`. Both refuse an expectation that names neither key. <!-- source: internal/component/cli/testing/expect.go -- checkHint, checkExplanation --> A test that runs `option=mode:value=command` or `option=mode:value=operational` completes against the real command tree, built from the registered `-cmd` YANG modules. The tree carries no value hints and no plugin commands. A hint provider reads the live RIB, and a plugin command comes from the live dispatcher. A headless test has neither, so a completion over a VALUE stays empty there. <!-- source: internal/component/cli/testing/headless.go -- headlessCommandTree --> ### Wait Actions | Action | Purpose | Example | |--------|---------|---------| | `wait=ms:<N>` | Wait N milliseconds | `wait=ms:200` | | `wait=validation` | Wait for validation | `wait=validation` | | `wait=timer:expire` | Wait for timer expiry | `wait=timer:expire` | ### Example ``` # Test: Edit navigation tmpfs=test.conf:terminator=EOF_CONF bgp { router-id 1.2.3.4; peer upstream1 { remote { ip 1.1.1.1; as 65001; } } } EOF_CONF option=file:path=test.conf expect=context:root input=type:text=edit bgp input=enter expect=context:path=bgp expect=error:none input=type:text=set input=space expect=completion:contains=router-id,local-as,peer ``` ### Test Categories | Category | Location | Tests | |----------|----------|-------| | Navigation | `test/editor/navigation/` | edit, up, top, context | | Completion | `test/editor/completion/` | commands, YANG paths, values | | Commands | `test/editor/commands/` | set, delete, show, compare | | Lifecycle | `test/editor/lifecycle/` | commit, rollback, load, history | | Validation | `test/editor/validation/` | hold-time, peer-as | | Pipe | `test/editor/pipe/` | grep, head, tail | <!-- source: internal/component/cli/testing/parser.go -- editor test file parsing --> <!-- source: internal/component/cli/testing/session_test.go -- editor session tests --> Full format specification: `plan/spec-editor-testing-framework.md` --- ### Page: Interoperability Testing https://ze-software.net/architecture/testing/interop/ # Interoperability Testing Ze validates protocol correctness against production BGP daemons in two complementary ways: live session interop tests (Docker containers running real daemons) and byte-level wire format validation against ExaBGP (Ze's predecessor, a BGP implementation in Python). For BGP terminology used in this document, see [docs/features.md](../../../reference/feature-status/index.md). `ai/rules/interop-and-goal-validation.md` states when an interop test is owed. This page is the infrastructure it is owed against. ## The suites, one per protocol area | Protocol area | Peer implementation | Scenario directory | Native action | |---------------|---------------------|--------------------|---------------| | BGP (session, capability, NLRI, community, policy) | Docker: FRR, BIRD, GoBGP, StayRTR | `test/interop/scenarios/` | `./le integration interop` | | IPsec (IKEv2, EAP, MOBIKE) | Docker: strongSwan | `test/interop-ipsec/` | `./le integration interop-ipsec` | | L2TP | Docker | `test/interop-l2tp/` | `./le deployment l2tp-test`, and `./le deployment l2tp-ppp-test` for the full PPP and NCP path | | PPPoE (Ze as client) | Docker: accel-ppp | `test/interop-pppoe/` | `./le deployment docker-pppoe-accel-test` | | RADIUS (admin login: PAP, CHAP, EAP, Filter-Id) | Docker: FreeRADIUS | `test/interop-radius/scenarios/` | `./le integration interop-radius` | <!-- source: internal/le/integration/gates.go -- interop, interop-ipsec and interop-radius verbs --> <!-- source: internal/le/deployment/actions.go -- l2tp-test, l2tp-ppp-test, docker-pppoe-accel-test verbs --> Every suite discovers its scenarios the same way. `Discover` (`internal/le/interoplab/discover.go`) reads the scenario directory, keeps the subdirectories, sorts the names lexically, and joins each one against the owning checker registry. A scenario with no checker, or a nil checker, is an ERROR rather than a skipped test, so a fixture and its registry cannot silently disagree. Nothing depends on the order: each scenario gets its own setup, check and teardown. A scenario directory carries only declarative inputs its runner reads: `ze.conf` plus the peer configuration and argument files that topology needs. Assertions never live there. They are typed Go checkers under `internal/le/interoplab/`: BGP builds its registry from `scenarioOperations` in `internal/le/interoplab/bgp/checkers.go`, and IPsec declares `scenarioCheckers` in `internal/le/interoplab/ipsec/checkers.go`. A checker waits for readiness, asserts the protocol behaviour, verifies stability where the scenario needs it, and returns an error on failure. A scenario directory is NAMED and carries no numeric prefix. The name is the scenario's identity: `Discover` matches it exactly, `./le integration` takes it as a scenario selector, and specs, journal rows and code comments cite it. ## Tested Daemons | Daemon | Version | Image | Query Method | What It Validates | |--------|---------|-------|--------------|-------------------| | FRR | 10.3.1 | `quay.io/frrouting/frr:10.3.1` | vtysh | eBGP, iBGP, route exchange, GR, communities, MD5, route server | | BIRD | 2.x (Alpine 3.21) | Alpine build | birdc | eBGP, route exchange, triangle topologies | | GoBGP | 3.31.0 | Go builder | gobgp CLI | eBGP, route injection and verification | | StayRTR | 0.6.4 | Go builder | HTTP `/rpki.json` export | RTR (RFC 8210) as the CACHE, so Ze is the client of an implementation that is not its own. Origin validation answers (RFC 6811) against VRPs a third party encoded | | pmacct `pmbmpd` | latest | `pmacct/pmbmpd:latest` | JSON msglog file | BMP (RFC 7854, RFC 9069) as the COLLECTOR. pmacct decodes the per-peer header, the Peer Up Information TLVs and the Peer Down reason itself, so a scenario reads another implementation's reading of Ze's bytes rather than a string Ze emitted | | ExaBGP | API 6.0.0 contract fixtures | Compiled Go wire server | Wire byte comparison | Byte-for-byte encoding across all address families | <!-- source: internal/le/interoplab/bgp/prepare.go -- peer helpers and scenario preparation --> <!-- source: internal/le/interoplab/bgp/run.go -- scenario orchestrator --> ## Prerequisites | Requirement | Used By | Notes | |-------------|---------|-------| | Docker | Interop tests | Containers for FRR, BIRD, GoBGP, Ze | | ~1.5 GB disk | Interop tests | Docker images (Go builder, FRR, Alpine) | The interop test network uses `172.30.0.0/24`. MD5 authentication scenarios require `NET_ADMIN` capability (granted automatically by the orchestrator). ## Live Interop Tests (`test/interop/`) Each scenario runs Ze and one or more peer daemons in Docker containers on a shared network (`172.30.0.0/24`), establishes real BGP sessions, and asserts correct behavior via each daemon's native CLI. ### How It Works The native `internal/le/interoplab/bgp` package discovers scenario directories in `test/interop/scenarios/`. For each scenario, the shared `interoplab.Suite` engine: 1. Creates an isolated Docker network. 2. Starts Ze and the peer daemons declared by the scenario's config files. 3. Waits for every declared readiness probe. 4. Runs the scenario's typed checker from the package-local BGP registry. 5. Tears down every container and network, including after setup or checker failure. <!-- source: internal/le/interoplab/lab.go -- suite lifecycle --> Daemons start conditionally: FRR if `frr.conf` exists, BIRD if `bird.conf` exists, GoBGP if `gobgp.toml` exists. This means each scenario only runs the daemons it needs. **A scenario directory is NAMED, never numbered** (owner directive, 2026-08-24). Run order is lexical by scenario directory name and no scenario depends on it. The rule and its reasoning live in `ai/rules/interop-and-goal-validation.md`, which is always-on, so a spec author planning a scenario meets it without opening this page. ### Container Addresses | Daemon | IP | Container | |--------|----|-----------| | Ze | 172.30.0.2 | `ze-iop-ze-<pid>` | | FRR | 172.30.0.3 | `ze-iop-frr-<pid>` | | BIRD | 172.30.0.4 | `ze-iop-bird-<pid>` | | GoBGP | 172.30.0.5 | `ze-iop-gobgp-<pid>` | | Raw injector | 172.30.0.9 | `ze-iop-inject-<pid>` | | Compiled strict speaker | 172.30.0.10 | `ze-iop-speaker-<pid>` | | Compiled strict speaker (2nd) | 172.30.0.11 | `ze-iop-speaker2-<pid>` | | StayRTR | 172.30.0.12 | `ze-iop-stayrtr-<pid>` | | pmacct `pmbmpd` | 172.30.0.13 | `ze-iop-pmacct-<pid>` | Container names include the runner PID as suffix, so concurrent runs do not conflict. <!-- source: internal/le/interoplab/bgp/prepare.go -- container naming, IP addresses --> ### Scenario Structure Each scenario is a directory under `test/interop/scenarios/`: ``` scenarios/bgp-ebgp-ipv4-frr/ ze.conf # Ze configuration (required) frr.conf # FRR configuration (starts FRR container) ``` Every directory has one typed checker in `internal/le/interoplab/bgp`. The catalogue uses explicit operations for ordinary session, route, adjacency, log, and negative assertions, plus bespoke checkers for scenarios whose control flow cannot be represented as an ordered operation list. A bespoke checker is written in two halves. The body in `check_rfc.go` does the lab I/O and numbers each assertion, so a failure names the assertion that found it. The pure predicate in `check_rfc_predicate.go` takes the text or the decoded JSON a peer daemon produced, holds no lab handle, and decides. The split is what lets `TestBespokeCheckerBranches` drive both polarities of every predicate in seconds with no container. <!-- source: internal/le/interoplab/bgp/check_rfc_predicate.go -- pure predicates --> <!-- source: internal/le/interoplab/bgp/check_rfc.go -- tagged checker bodies --> A body MUST NOT call `checkScenario` (`check_engine.go`). That function answers `scenario %s has no typed assertions` for every name absent from `scenarioOperations`, and a bespoke name is absent from that table by design, so the call is an error that always fires and every line under it is unreachable. ### Optional sidecars A scenario directory may also carry files that start extra containers before Ze: | File | Sidecar | Purpose | |------|---------|---------| | `inject.msg` | `ze-test peer` (raw injector, 172.30.0.9) | Drive Ze with wire bytes no conforming daemon would emit. An optional `inject-args` file adds flags. Because the injector and Ze start before the peer daemons, an early route exercises Ze's replay-on-peer-up path. | | `speaker-args` (and optional `speaker2-args`) | `ze-test interop-bgp speaker` (172.30.0.10; second at 172.30.0.11) | Dial Ze with an independent strict peer. The compiled speaker negotiates the requested families and ADD-PATH mode, frames BGP itself, applies the named native oracle, and writes a structured verdict to container logs. It catches wire output that Ze's own lenient decoder could accept. | | `vrps.json` | StayRTR (172.30.0.12:8282) | Serve RPKI VRPs from a real third-party cache, so Ze is the RTR client of an implementation that is not its own. The typed checker asserts each per-prefix validation answer, not merely the RTR session. | | `pmbmpd.conf` | pmacct `pmbmpd` (172.30.0.13:1790) | Read Ze's BMP stream with a collector Ze did not write. The file is also the selector: a scenario that carries it starts pmacct INSTEAD of Ze's own collector, because two collectors are two readings of one stream and only the third-party one is interop evidence. The typed checker greps pmacct's JSON msglog, so every needle is a field pmacct printed after decoding. | A scenario carrying `frr.conf` may also carry its own `daemons` file. That file names which FRR daemons run and what each one is started with, so a scenario needing a `bgpd` module (`-M bmp` for one that drives Ze's BMP receiver) carries its own copy instead of adding the module to every scenario in the suite. Without one, the shared `test/interop/daemons` is mounted. A BMP scenario with no `pmbmpd.conf` starts `ze-test interop-bgp bmp-collector`. Announcement and observer process plugins use `ze-test interop-bgp process <scenario> <plugin>`. These personalities are compiled into `ze-test`; no interpreter or source mount is present in the Ze image. <!-- source: internal/le/interoplab/bgp/prepare.go -- sidecar startup --> <!-- source: internal/le/interoplab/bgp/helper.go -- compiled process and BMP helpers --> <!-- source: internal/le/interoplab/bgp/speaker.go -- compiled strict speaker --> <!-- source: test/interop/scenarios/bgp-rfc7606-relay-shape-frr/ -- injector worked example --> <!-- source: test/interop/scenarios/bgp-rfc7606-speaker-dup-attr/ -- speaker worked example --> <!-- source: test/interop/scenarios/rtr-stayrtr/ -- StayRTR worked example --> <!-- source: test/interop/scenarios/bmp-locrib-pmacct/ -- pmacct collector worked example --> ### Prove a scenario discriminates An interop scenario is evidence only if it goes RED when the behaviour it tests is broken. Before you rely on a new scenario, revert the fix, run the scenario, and confirm that it fails. Then restore the fix and confirm that it passes. **Let the harness build. Do NOT `docker build -t ze-interop` by hand and then run with `NO_BUILD=1`.** A tag is shared by every run on the host. A build in another session rebinds that tag between yours and your container start. Your mutation run then measures a daemon you did not build, and that inverted a proof twice in one review on 2026-08-05. `Docker.Build` reads the image ID from `docker build -q`, and the suite pins every container of the run to that immutable ID. Quote that line beside the result, because it names the binary the run measured. A scenario that passes either way (common when the peer must accept both the old and the new wire form) proves acceptance, not correctness. Say which one it proves in the spec's Goal Validation, and move the discrimination to a unit or mutation test that CAN fail. A scenario added to ALREADY-WORKING code never had a red phase, so its discrimination is unproven until you force one. That is not TDD's red-then-green: a regression test and a scenario for existing behaviour both start green. Five traps make a scenario pass whatever the code does. Check each by its tell before you call the scenario evidence: | Vacuity trap | Why it passes anyway | The tell | |--------------|----------------------|----------| | A scenario for a sender-side wire change whose receiver is obliged to accept any form (RFC 7606 Section 5.1: receivers accept any field combination) | A conforming peer accepts the old and the new wire equally | Reverting the sender change leaves the peer's routing table identical | | A test asserting the ABSENCE of something (no log line, no allocation, no route) | Deleting the mechanism leaves the same absence | Ask what would still be absent if the code were removed | | A test whose fixture is at an extreme (all fields set, maximum value) | An off-by-one or a partial break still handles the extreme | Boundary the fixture: test one below and one above | | A test whose data reaches the peer by a DIFFERENT path than the one changed | The unchanged path still delivers | Trace which code path actually produces the asserted bytes | | An assertion whose clauses are all satisfied by ONE stimulus | Each clause reads as an independent observation, and they are one observation written twice | Name the single event that satisfies every clause. Then ask which clause a peer that did nothing would still satisfy | The fifth trap is what the IPsec suite carried until 2026-09-04, and it is worth reading in full because the shape recurs. `verifyTunnelTraffic` (`internal/le/interoplab/ipsec/helpers.go`) pinged from Ze to strongSwan and passed when each peer's ESP byte counters advanced. Both clauses were satisfied by that ONE ping: Ze encrypting the echo request advances Ze's outbound SA, and strongSwan decrypting the same request advances strongSwan's inbound SA. Nothing required strongSwan to have encrypted anything toward Ze. RFC 4301 Section 4.1 says why the aggregate could not discriminate: > An SA is a simplex "connection" that affords security services to the traffic > carried by it. A protected bidirectional flow is two SAs, and the RECEIVER chooses the SPI, so both peers name one direction by the same SPI value. Measured in `psk-site-to-site`: Ze holds `src 172.28.0.2 dst 172.28.0.3 spi 0xc12fa7e3` and `src 172.28.0.3 dst 172.28.0.2 spi 0xf008af63`, and strongSwan holds those same two SPIs. A counter map keyed by SPI alone therefore folds the two peers' views of one direction into one entry. `verifyESPDirections` now reads each simplex SA by its own `src`/`dst` header and takes the set of directions its caller can claim, and it also refuses a ping whose `% packet loss` summary is missing or non-zero. <!-- source: internal/le/interoplab/ipsec/helpers.go -- directed ESP counters and the lossless-ping clause --> ### The IPsec NAT box A scenario that carries `nat.conf` starts a third container, `nat`, at 172.28.0.5. It is keyed on that file exactly as strongSwan is keyed on `swanctl.conf` and FRR on `frr.conf`, and it is started FIRST so its addresses answer ARP before either daemon sends a datagram. It exists because RFC 7296 Section 2.23.1 is only reachable across a REAL translation. `natt-transport-inner-checksum` and `natt-tunnel-inner-checksum` reach the UDP-encapsulated path through strongSwan's `encap = yes`, which fakes the NAT_DETECTION_SOURCE_IP hash with no middlebox on the path. The pre-NAT address and the observed address are therefore equal in those two, every Section 2.23.1 substitution is the identity, and its absence cannot show. That is the fourth vacuity trap above: the asserted bytes reach the peer by a path the mechanism under test does not touch. `nat.conf` is one translation per line, `<real> <public>`. The box gives itself each public address as a SECONDARY address on `eth0`, then installs one DNAT rule in PREROUTING and one SNAT rule in POSTROUTING per line. Three consequences are load-bearing: - **No peer needs a route.** Both public addresses sit inside the lab bridge's own prefix, so a peer resolves them by ARP and the box answers. A NAT on its own segment would need a second Docker network, and `interoplab.ScenarioPlan` carries one `NetworkSpec` that the BGP suite shares. - **Both addresses of a crossing datagram are rewritten,** which is the two-NAT figure of Section 2.23.1 drawn with one box, and it is what makes all four NAT_DETECTION comparisons mismatch. - **The rules carry no port and no protocol.** IKE floats from UDP 500 to UDP 4500 mid exchange, so a port-scoped rule would translate the handshake and drop the ESP that follows. Three scenarios use it, and they differ in one variable each. The two transport scenarios differ in ROLE, because the substitution has two producers and a single scenario cannot fail on one of them. `real-nat-tunnel-control` differs from them in MODE, and it does two jobs: it proves the substitution stays out of tunnel mode, and it makes a red transport scenario readable, because a broken topology reds it too. Its selectors are INNER addresses the box never sees, which is the one selector pair in this lab a translation cannot move. <!-- source: internal/le/interoplab/ipsec/nat.go -- readNATConfig, natSetupScript --> <!-- source: internal/le/interoplab/ipsec/checkers.go -- checkRealNATTransport, checkRealNATTunnelControl --> ### The strongSwan lab drop-in Every scenario that starts a strongSwan peer mounts `test/interop-ipsec/strongswan-lab.conf` read-only at `/etc/strongswan.d/98-lab.conf`. It carries the charon settings the whole lab needs, and today that is one: `charon.plugins.bypass-lan.load = no`. charon loads `bypass-lan` by default, and that plugin installs a PASS shunt for every locally attached subnet. This lab puts both containers on 172.28.0.0/24, so the shunt covers the peer. Measured on 2026-08-30 in `psk-site-to-site`, the shunt sits at priority 175423 against the Child SA policy's 399999, the lower number wins, and every packet strongSwan sends to Ze leaves in the clear. A ping still succeeds under that shunt, which is exactly why a lossless ping is necessary and not sufficient evidence that a tunnel carried anything. A scenario's own `strongswan.conf` still mounts at `/etc/strongswan.d/99-interop.conf` and composes with the lab file. A scenario MUST NOT set `bypass-lan` itself: `TestNoScenarioCarriesItsOwnBypassLanOverride` (`test/interop-ipsec/parity_test.go`) refuses a second copy, because two files setting one value is a disagreement with nothing to arbitrate it. <!-- source: internal/le/interoplab/ipsec/ipsec.go -- prepareScenario mounts the lab drop-in --> <!-- source: test/interop-ipsec/strongswan-lab.conf -- the lab-wide charon settings --> ### The FreeRADIUS admin-login suite `internal/le/interoplab/radius/` runs ze's operator login against a real FreeRADIUS server at a pinned tag, pulled through `ImageBuild{Pull: true}`. It exists because every other RADIUS proof ze holds runs against a mock ze wrote: `test/plugin/aaa-radius-admin.ci` drives `internal/test/mock/radius/radius.go`, and the L2TP lab's peer is `internal/le/interoplab/l2tp/radiusmock/`, which is ze's own Go program in a container. A mock built beside ze's encoder agrees with ze by construction, and ze now computes a CHAP digest a server must reproduce from its own stored password. Only a server ze did not write can disagree. It is its own suite rather than four more L2TP scenarios. The L2TP lab probes for the `l2tp_ppp` or `pppol2tp` kernel module and refuses to run without it, which is correct for a suite that carries PPP sessions. Admin login is ze's SSH listener, a UDP socket and a RADIUS server, so this lab declares no preflight beyond its own ze cross-compile, mounts no module tree, asks for no capability and runs nothing privileged. `TestSuiteNeedsNoKernelModule` holds that. Every checker reads BOTH sides. Ze's log saying `source=radius` is not enough on its own, because a login the local bcrypt backend satisfied produces a line of the same shape and no server traffic at all. The lab therefore mounts a `linelog` module at `/etc/raddb/mods-enabled/ze_request_log` that writes one line per answered request to `/var/log/freeradius/ze-request.log`, carrying the verdict, the User-Name, the PRESENCE of a User-Password, of a CHAP-Password and of an EAP-Message, and the NAS-Identifier. Presence and not value: a fixture must not put a password or a digest in a log file. `parseServerRecord` refuses a line missing any of the six fields, so a truncated or reformatted line is never read as a partial verdict. An EAP login is several requests and FreeRADIUS runs no `post-auth` section for an Access-Challenge, so the reply that asked the question records nothing. A second module, `ze_state_echo_log`, is called from `authorize` on a request carrying both an EAP-Message and a State, and writes `verdict=state-echo`. That is the server's own evidence that ze returned the State unmodified, which RFC 2865 Section 5.24 requires, and that the login was a conversation rather than one request. | Scenario | Ze's side | The server's side | |----------|-----------|-------------------| | `radius-admin-pap-freeradius` | An operator logs in over ze's real SSH listener, ze's log says `source=radius`, the Filter-Id profile denies `show bgp`, and the local account's own password is refused | `verdict=accept` with a User-Password present, a CHAP-Password absent and ze's NAS-Identifier, then `verdict=reject` for the wrong password | | `radius-admin-chap-freeradius` | The same, with `auth-method chap` against a `Cleartext-Password` entry | `verdict=accept` with a CHAP-Password present and NO User-Password beside it, which is what RFC 2865 Section 4.1 demands of an Access-Request | | `radius-admin-chap-hashed-freeradius` | The CHAP login is REFUSED, and ze authenticates the user through no backend at all | A `radclient` probe first proves the same entry accepts the same password over PAP, then `verdict=reject` for the CHAP request | | `radius-admin-eap-freeradius` | The same, with `auth-method eap-mschapv2`. Ze answers the EAP conversation itself from the operator's password, Naks the server's MD5-Challenge toward MSCHAPv2, and returns the State on every later round | `verdict=state-echo` for at least one round carrying the State the server issued, then `verdict=accept`, both with an EAP-Message present and NEITHER password attribute beside it | The third scenario is the one that proves `docs/guide/radius.md` is telling the truth. RFC 2865 Section 2.2: > For example, CHAP requires that the user's password be available in cleartext > to the server so that it can encrypt the CHAP challenge and compare that to > the CHAP response. If the password is not available in cleartext to the > RADIUS server then the server MUST send an Access-Reject to the client. A rejection on its own would also follow from a typo in the user file, which is the fourth vacuity trap wearing another face: the asserted result would be produced by a different cause than the one under test. The `radclient` PAP probe removes it, because the storage form is then the only thing left to explain the CHAP rejection. Two credentials share one username on purpose. `radiusop` exists in the server's user file AND in ze's local account list with a DIFFERENT password, so the scenario can send the local password while RADIUS rejects it: a chain that fell through to local bcrypt would accept that login. `localop` exists only locally and the server answers nothing for it, which is the positive control that proves the SSH listener and the local backend are both live before any refusal is read as evidence. <!-- source: internal/le/interoplab/radius/radius.go -- the suite, its pinned image, its peers and its probes --> <!-- source: internal/le/interoplab/radius/checkers.go -- every observation, on ze's side and on the server's --> <!-- source: test/interop-radius/mods-ze-request-log -- the linelog module the server's record comes from --> ### Typed checker operations `checkers.go` is the complete scenario catalogue. Each operation identifies the peer, exact command, required or forbidden evidence, proof for negative assertions, and a bound. `check_engine.go` executes those operations through the shared `CheckerLab` interface. FRR, BIRD, GoBGP, Ze, speaker logs, and kernel state are all queried through explicit typed branches. An absent value never proves a negative assertion by itself. The operation must also name positive evidence that the query mechanism ran. Failed and empty queries remain errors rather than becoming plausible empty protocol state. That rule decides which command a negative assertion sends. `opBIRDRouteAbsent` reads the whole table with `birdc show route`, and never `show route for <prefix>`: BIRD answers a lookup for a network it does not hold with "Network not found", and birdc exits 1. A lookup therefore fails in the exact state the assertion exists to observe, and the failure is not absence. Ask a question the peer answers in both states, then read the absence out of the answer. Scenarios with non-linear behavior register a bespoke checker in `specialCheckers` (`check_special.go`). Each one owes a named subtest in `TestBespokeCheckerBranches` that drives its predicate in BOTH polarities: the true case, and a false case written against one stated wrong reading, such as two tokens matched across two log lines, a peer-originated event passing as a received one, or an absence with no proof that the query ran. <!-- source: internal/le/interoplab/bgp/bgp_test.go -- TestBespokeCheckerBranches --> ### Querying Ze `Ze.cli(command)` is the only way to ask the Ze daemon anything. It runs `ze cli -c <command> --user ... --format json` inside the container. Two properties of that line are load-bearing and neither is obvious. `ze cli -c` rather than the verb form `ze show bgp rib status`: `--user` and `--format` are flags of `ze cli`, and the verb form has no slot for either. The daemon starts an SSH listener only when its config asks for one (`infraSetup`, `cmd/ze/hub/infra_setup.go`), and `ze cli` reaches the daemon over SSH. No scenario `ze.conf` asks. The harness appends `ZE_CLI_CONFIG` -- the listener plus the account it authenticates against -- to the RENDERED copy of every `ze.conf` (`renderScenario`), so no scenario carries the boilerplate and none can forget it. The native IPsec plan appends the same blocks in `renderZeConfig` (`internal/le/interoplab/ipsec/ipsec.go`). Rendered configurations stay under `tmp/interop-rendered` in the checkout. Docker must share the checkout with its Linux VM. An unshared system-temp path gives the container a directory instead of the configuration file. IPsec and L2TP create private directories there even without an AI session ID. <!-- source: internal/le/interoplab/lab.go -- RenderedConfigDirectory --> <!-- source: internal/le/interoplab/ipsec/ipsec.go -- prepareScenario --> <!-- source: internal/le/interoplab/l2tp/l2tp.go -- renderInitiatorConfig --> **A Ze helper never converts a failed query into a plausible number.** `Ze.rib_count` raises when the command fails or answers without a `routes-in` field, because 0 is a legitimate RIB size and a failed query is not (`ai/rules/evidence.md`). It returned 0 on failure until 2026-08-07 and three separate faults hid behind that one number for three days (`spec-fixit-test-harness-fail-open-guards`, guard 3). Write new Ze helpers the same way. **A checker asserts on the STRUCTURED answer, never on the rendered text.** The IPsec suite asks with `show vpn ipsec sa | json` and decodes the list of SA records (`zeIKESAs`, `internal/le/interoplab/ipsec/helpers.go`). The text rendering is a table, and its column order follows the field names, so a line-anchored `<field> <value>` regex over that table matches by accident. On 2026-09-06 one new field, `behind-nat`, took first place from `child-sa` in that order. Every nested Child SA key moved off the line start, and `child-rekey-narrowing`, `peer-reload-narrowing` and `initiator-rekey-answer-narrows` went red with no daemon behavior changed. <!-- source: internal/le/interoplab/ipsec/helpers.go -- zeIKESAs, assertNATVerdict, assertZeSelectors --> **A number two readers print in two bases is normalized to one TYPE, and the decode is typed.** The `dataplane-readback` scenario joins the SPI set `show vpn ipsec dataplane sa | json` answers against the set `ip xfrm state` prints in the same container. iproute2 writes an SPI as `0xc1a2b3c4` and Ze answers a JSON number, so a string comparison is false for every SPI. The checker decodes the Ze answer into a struct whose `spi` field is a `uint32`, and parses the printed form to the same type. Decoding into `map[string]any` would route the number through `float64`, which holds a uint32 today and says nothing about the uint64 byte counter beside it. **An agreement between two readers asserts NON-EMPTY before it asserts EQUAL.** Two empty sets are equal, so a read-only comparison passes over a kernel that holds nothing, which is what the dump answers with its whole body deleted. `requireSameSPISet` refuses an empty side first and names which side was empty. `requireSPISetChanged` refuses any old SPI that remains after rekey. The checker polls through the bounded old-SA cleanup window until Ze and iproute2 agree on a nonempty replacement set, and checks strongSwan's kernel against that set too. RFC 7296 Section 2.8 permits overlap during rekey; overlap after cleanup fails. <!-- source: internal/le/interoplab/ipsec/helpers.go -- decodeZeDataplaneSPIs, spiValues, requireSameSPISet, requireSPISetChanged --> <!-- source: internal/le/interoplab/ipsec/checkers.go -- checkDataplaneReadback --> The same scenario reads all four Child SA byte and packet counters as `uint64` values and compares them with the directed lifetime-current records from `ip -s xfrm state`. It samples before and after bidirectional traffic and requires each counter to advance. A bounded one-way probe makes the directions differ, so swapping inbound and outbound counters cannot pass on a symmetric ping. <!-- source: internal/le/interoplab/ipsec/checkers.go -- requireZeCountersAdvanced, readbackAsymmetricTraffic, requireCounterAdvancement --> <!-- source: internal/le/interoplab/ipsec/helpers.go -- readbackCounters, requireDirectedCounters --> Dataplane observation also crosses the live operator surfaces. With the Prometheus exporter enabled, the checker scrapes the installed-SA count and clean drift gauge, deletes one kernel SA, and requires CLI drift, degraded `show health`, and the corresponding gauge transition. It restores the kernel state and waits for recovery. Removing the configured peer then removes both the peer and interface-label series. <!-- source: internal/le/interoplab/ipsec/checkers.go -- checkDataplaneMonitoring, requireLiveDataplaneHealth --> An unreadable observation is induced inside the Ze scenario container by temporarily setting only the daemon's soft `RLIMIT_NOFILE` to zero. The netlink package's default handle opens a socket for each XFRM dump. A Python helper opens and warms an HTTP connection before changing the limit, scrapes that same connection until both dataplane metric families have no series, and restores the original limit in `finally`. Reconnection is refused during the fault. During the fault, live health must identify an unknown or unreadable dataplane; the preceding known-drift diagnosis cannot satisfy that assertion. Restoring the limit must restore both the drift diagnosis and the published drift gauges. The checker requires published series before the fault and their return after restoration, so an exporter that never publishes cannot satisfy the test. This helper needs Python in the lab image; it changes no production backend. <!-- source: internal/le/interoplab/ipsec/helpers.go -- unreadableDataplaneProbe, requireUnreadableDataplane --> All session waiters use explicit bounds (default 90 seconds, override via `SESSION_TIMEOUT`). The harness passes that value into the Ze container so a compiled process helper can size its barriers against the same budget. <!-- source: internal/le/interoplab/wait.go -- bounded wait --> <!-- source: internal/le/interoplab/bgp/prepare.go -- container environment --> ### A compiled process helper that fails Scenario process personalities use the Go plugin SDK. A helper failure writes the `ZE-OBSERVER-FAIL` sentinel to stderr and requests daemon shutdown. The checker failure path reads the last 2,000 Ze log lines and appends that measured cause when the sentinel is present. An unreadable log is not a plugin verdict. `checkerFailure` retains the original scenario assertion when the log read fails, so a Docker diagnostic cannot replace the protocol failure that triggered it. <!-- source: internal/le/interoplab/bgp/helper.go -- runtimeFailure --> <!-- source: internal/le/interoplab/bgp/check_engine.go -- checkerFailure --> <!-- source: internal/le/interoplab/bgp/bgp_test.go -- TestCheckerFailureKeepsPrimaryCauseWhenLogsFail --> ### Scenario Inventory The suite has grown to over 100 scenario directories in `test/interop/scenarios/`. The table below lists the core BGP scenarios (01-37); beyond these, the suite also covers route reflection, policy import/export, RPKI origin validation, BMP monitoring (`bmp-locrib-pmacct` puts Ze's RFC 9069 Loc-RIB feed in front of pmacct and requires pmacct to print the configured Peer AS, the configured Peer BGP ID, the `global` VRF/Table Name TLV and reason code 6 on the Peer Down; `bmp-locrib-receiver-frr` turns the direction around, so FRR's `bmpd` drives Ze's BMP receiver and `show bmp peers` must report the third party's Loc-RIB peer and its address family), PATHS-LIMIT, max-prefix cease, GTSM, AS112, the RFC 7454 Section 9 transit leak (`bgp-path-asn-leak-frr` gives FRR two prefixes that differ only in their AS_PATH, and requires ze to drop the one reached through a listed transit ASN, keep the other, and keep the session), ADD-PATH re-advertisement (`bgp-addpath-readvertise-collision-frr` proves a receiver keeps two paths whose sources both chose one Path Identifier, and `bgp-addpath-rail-agreement-speaker` proves the live forward and the peer-up replay emit the same bytes for one path), the RFC 6793 mixed-width relay (`as-path-mixed-width-relay-frr` gives ze a route from a two-octet injector whose AS_PATH carries AS_TRANS and whose AS4_PATH carries the real four-octet AS number, and requires FRR to report that AS number and never 23456; `as-path-prepend-two-octet-peer` turns the direction around, so ze's own non-mappable AS is prepended toward an FRR that refused the four-octet AS capability), and full IS-IS (auth, convergence, dual-stack, LAN DIS, P2P, redistribution) and OSPFv2/OSPFv3 (auth, BFD, TE, LFA/TI-LFA, graceful restart, segment routing, opaque LSAs, stub/NSSA, virtual links, and more) interop families. | # | Scenario | Daemons | What It Tests | |---|----------|---------|---------------| | 01 | ebgp-ipv4-frr | Ze, FRR | Basic eBGP session establishment | | 02 | ebgp-ipv4-bird | Ze, BIRD | Basic eBGP session with BIRD | | 03 | ibgp-frr | Ze, FRR | iBGP session (same AS) | | 04 | 4byte-asn-frr | Ze, FRR | 4-byte ASN negotiation (RFC 6793) | | 05 | routes-from-frr | Ze, FRR | Ze receives routes originated by FRR | | 06 | routes-from-bird | Ze, BIRD | Ze receives routes originated by BIRD | | 07 | routes-to-frr | Ze, FRR | FRR receives routes originated by Ze | | 08 | triangle | Ze, FRR, BIRD | Three-way topology, multi-peer stability | | 09 | route-withdrawal-frr | Ze, FRR | Route withdrawal propagation | | 10 | ipv6-ebgp-frr | Ze, FRR | IPv6 eBGP session and route exchange | | 11 | addpath-frr | Ze, FRR | ADD-PATH capability (RFC 7911) | | 12 | route-refresh-frr | Ze, FRR | Route Refresh (RFC 2918) | | 13 | graceful-restart-frr | Ze, FRR | Graceful Restart negotiation (RFC 4724) | | 14 | route-server-frr | Ze, FRR, BIRD | Route server: forwards without inserting own ASN | | 15 | community-frr | Ze, FRR | Standard community propagation | | 16 | extended-community-frr | Ze, FRR | Extended community propagation | | 17 | md5-auth-frr | Ze, FRR | TCP MD5 authentication (RFC 2385) | | 18 | ebgp-gobgp | Ze, GoBGP | eBGP session with GoBGP | | 19 | routes-gobgp | Ze, GoBGP | Route exchange with GoBGP | | 20 | role-frr | Ze, FRR | RFC 9234 Role capability negotiation | | 21 | role-gobgp | Ze, GoBGP | RFC 9234 Role capability negotiation | | 22 | evpn-frr | Ze, FRR | EVPN Type-2 route exchange | | 23 | vpn-frr | Ze, FRR | VPN (L3VPN) route exchange | | 24 | flowspec-frr | Ze, FRR | FlowSpec rule exchange | | 25 | ipv6-ebgp-bird | Ze, BIRD | IPv6 eBGP route exchange | | 26 | ipv6-ebgp-gobgp | Ze, GoBGP | IPv6 eBGP route exchange | | 27 | multihop-ebgp-frr | Ze, FRR | Multi-hop eBGP with outgoing-ttl | | 28 | evpn-gobgp | Ze, GoBGP | EVPN Type-2 route exchange | | 29 | vpn-gobgp | Ze, GoBGP | VPN (L3VPN) route exchange | | 30 | flowspec-gobgp | Ze, GoBGP | FlowSpec rule exchange | | 31 | multihop-ebgp-bird | Ze, BIRD | Multi-hop eBGP with outgoing-ttl | | 32 | multihop-ebgp-gobgp | Ze, GoBGP | Multi-hop eBGP with outgoing-ttl | | 33 | bfd-frr | Ze, FRR | BFD opt-in and BFD-triggered BGP teardown | | 34 | ecmp-frr | Ze, FRR, GoBGP | FRR ECMP selection for the same prefix from Ze and GoBGP | | 35 | srv6-frr | Ze, FRR | SRv6 VPNv6 route exchange and Prefix-SID handling | | 36 | remove-private-as-frr | Ze, FRR, GoBGP | remove-private-as export policy to FRR | | 37 | remove-private-as-as4path-frr | Ze, FRR, BIRD | remove-private-as handling for AS4_PATH private ASNs | <!-- source: test/interop/scenarios/ -- scenario directories --> ### Running ```bash ./le integration interop INTEROP_SCENARIO=bgp-ebgp-ipv4-frr ./le integration interop VERBOSE=1 ./le integration interop NO_BUILD=1 ./le integration interop FRR_IMAGE=quay.io/frrouting/frr:10.3 ./le integration interop BUILD_TIMEOUT=7200 ./le integration interop ``` Interop tests require Docker and are not part of the offline precommit gate. They are a separate protocol-validation action. The first run cross-compiles the lab binaries on the host, then builds the Docker images. No `Dockerfile.ze` carries a Go compiler: each one is an `alpine:3.21` base, one `apk add`, and a `COPY` of a binary the suite's preflight has already written into the build context (`internal/le/interoplab/zebuild.go`, `StageBinaries`). Each lab declares the binaries it needs beside the images it needs, and the bgp lab declares two, because its scenarios also run `ze-test` from inside the container. Measured on 2026-09-06 on a 32-core workstation: 6.1s and 4.8s for the two cross-compiles against a warm `cache/go-cache`, at a peak resident set of 1.03 GiB and 0.98 GiB, then 54.7s for the `docker build`, which is now context transfer rather than compilation. The shape before 2026-09-06 is what those numbers are read against. `Dockerfile.ze` copied the whole tree and compiled ze twice with no cache mount. One colima VM of 2 CPUs and 2 GB built it in 2m48s on 2026-09-04, and the same VM took 40m39s for it earlier that day, when the host disk was full and the guest was thrashing. On 2026-09-06 the kernel killed that build three times on an idle 31 GiB workstation, once with 23 GiB free and nothing else running, so the compiler in the container was the thing that did not fit rather than the machine being busy. A consequence: `docker build -f test/interop/Dockerfile.ze .` on a clean checkout now fails at the `COPY` until `./le integration interop` has run its preflight. Each converted Dockerfile's header names the action that writes its binary. Each build is bounded at 90 minutes, and `BUILD_TIMEOUT` sets that bound in whole seconds for a machine slower or faster than that one. The bound stops a wedged Docker daemon and is not a budget for the build, so a build that finishes returns at once and a generous bound costs nothing. A value that does not parse, or that is not positive, keeps the 90 minutes. An image that needs more than the machine bound declares its own `ImageBuild.Timeout`, and that field only ever LENGTHENS a bound. A number below the machine bound shortens it, which kills a build the machine would finish, so no suite declares one. The PPPoE suite did until 2026-09-04: 10 minutes for its ze image, 15 for accel-ppp and 10 for the client, each written when the shipped default was 10 minutes and each a cap once the default became 90. Subsequent runs with `NO_BUILD=1` skip rebuilds. Once the images exist, the full suite takes roughly 5-10 minutes depending on session establishment times. <!-- source: internal/le/interoplab/docker.go -- dockerBuildTimeoutDefault, buildTimeout --> <!-- source: internal/le/interoplab/zebuild.go -- StageBinaries, LabBinary --> ### Debugging Failures On failure, the orchestrator automatically dumps the last 20 lines of container logs. For more detail: - `VERBOSE=1` enables debug output (polling status, container commands, raw CLI output) - `SESSION_TIMEOUT=120` increases the session establishment timeout (default 90s) - Single-scenario runs isolate the problem: `INTEROP_SCENARIO=bgp-graceful-restart-frr ./le integration interop` ### Writing a New Scenario 1. Create a descriptively named directory under `test/interop/scenarios/`. 2. Add `ze.conf` and whichever peer configs the scenario needs. 3. Add the scenario's ordered assertions to `scenarioOperations` and `scenarioExtras`, or register a bespoke checker in `specialCheckers` when the control flow is non-linear. 4. For a bespoke checker, put each decision in a pure predicate in `check_rfc_predicate.go` and add its both-polarity subtest to `TestBespokeCheckerBranches`. 5. Run `INTEROP_SCENARIO=<name> ./le integration interop`. `TestCheckerPopulationMatchesProducer` compares every scenario directory with the package-local registry. `TestEveryCheckerFailsClosedWithoutPeerEvidence` rejects a checker that can pass without reading a peer. Each negative assertion must carry positive proof that its query mechanism ran. <!-- source: internal/le/interoplab/bgp/checkers.go -- checkers --> #### A scenario that reads the RIB attaches the RIB plugin `plugin { internal rib { use bgp-rib; } }` loads the plugin. It does NOT feed it. A peer delivers an event to a process only where both halves agree, and the peer's half is its attach block (`Server.PeerScopedProcs`, `internal/component/plugin/server/delivery_graph.go`). A peer with no `attach process rib` block grants nothing, so the plugin sees no peer at all and every RIB question answers empty: ``` attach process rib { receive [ update state refresh ]; } ``` The tell is `"peers": 0` from `show bgp rib status` while `show bgp peer list` reports both sessions Established. Ze also logs it at startup: *"the plugin declared events and no peer attaches it"*. #### Editing a config means recreating the container, not restarting it Ze persists its configuration, so `docker restart` on a scenario container runs the peers of the FIRST boot and ignores the edited file the mount now carries. `docker exec ... cat /etc/ze/bgp.conf` shows the new text while `show bgp peer list` shows the old peers, which reads as a config that had no effect. Remove the container and start a new one instead. Every restart-based config experiment in a hand-built lab is void, and the harness is unaffected because it creates each container once. For Ze's configuration syntax, see [docs/architecture/config/syntax.md](https://github.com/ze-software/ze/blob/main/docs/architecture/config/syntax.md). Copy an existing scenario's `ze.conf` as a starting point. ## ExaBGP Wire Compatibility (`test/exabgp-compat/`) A separate test suite validates that Ze's wire encoding matches the reviewed ExaBGP API 6.0.0 contract fixtures. The compiled Go server negotiates each BGP session and compares every received frame byte-for-byte. ### What It Tests The harness migrates each ExaBGP-derived configuration, runs Ze, and compares its wire bytes with the known-good fixture. The 42 `.ci` cases in `test/exabgp-compat/encoding/` use `option=file:`, `option=serial`, `1:cmd:`, `1:raw:`, `1:signal:`, and `1:json:` records rather than the standard `.ci` format. `option=serial` marks process-driven fixtures that must not overlap other ExaBGP harness instances; the runner executes those after the parallel batch. `<prefix>:signal:<NAME>` marks the point in a connection's script where the runner reloads Ze. It divides the script: every `raw` frame written before it must match before the reload happens, and the frames after it are matched only once it has. Each signal step consumes the NEXT `option=file:` config the case names, so a case owes one config more than it has signals, and the runner refuses a case where those two counts disagree. `test/exabgp-compat/api/api-reload.ci` is the case that drives this: its withdrawal of `2.0.0.0/24` is produced BY the reload. The fixtures name ExaBGP's reload signal, `SIGUSR1`. Ze reloads on SIGHUP, so the runner translates the name (`deliverExaBGPReloads`), writes the next migrated config over the path Ze reads, and signals Ze's own pid rather than the process group, which also holds the bridge's scripts. Coverage includes: | Category | Examples | |----------|----------| | Address families | IPv4/IPv6 unicast, VPN, FlowSpec, FlowSpec VPN, EVPN, VPLS, MPLS labeled, MUP, MVPN | | Path attributes | ORIGIN, AS_PATH, NEXT_HOP, MED, LOCAL_PREF, communities (standard, extended, large), AGGREGATOR, ORIGINATOR_ID, PREFIX_SID, SRv6 | | Capabilities | 4-byte ASN, ADD-PATH, link-local next-hop, software version, hostname | | Edge cases | Generic/unknown attributes, self-referencing routes, group limits, IPv4+IPv6 mixed configs, deferred announcement (watchdog) | ### Running ```bash ./le functional exabgp-test ``` ExaBGP compatibility is part of the offline precommit gate. <!-- source: test/exabgp-compat/encoding/ -- .ci test files for wire compatibility --> ## Test Hierarchy | Workflow | Includes Interop? | Includes ExaBGP? | Requires Docker? | |----------|-------------------|-------------------|-------------------| | Offline precommit gate | No | Yes | No | | Standard functional sweep | No | Yes | No | | `./le integration interop` | Yes | No | Yes | | `./le functional exabgp-test` | No | Yes | No | Interop tests are intentionally separate from the pre-commit gate because they require Docker and take longer to run. ExaBGP wire compatibility tests run as part of the standard verification suite. ## Current Scope Interop scenarios cover core BGP: session establishment, route exchange, withdrawal, capabilities (4-byte ASN, ADD-PATH, GR, route refresh, PATHS-LIMIT), communities, MD5 auth, route server behavior, route reflection, policy import/export, RPKI origin validation, BMP monitoring, BFD failover, ECMP, SRv6 VPNv6, remove-private-as export policy, GTSM, AS112, and non-unicast address families (EVPN, VPN, FlowSpec). The suite also includes full IS-IS and OSPFv2/OSPFv3 interop families (adjacency, flooding, SPF, dual-stack, authentication, TE, LFA/TI-LFA, graceful restart, and segment routing). ExaBGP compat covers wire encoding for all supported address families. <!-- source: test/interop/scenarios/ -- scenario directories --> Not yet covered by interop tests: - Long-Lived Graceful Restart with live peers ## Known Vendor Limitations | Vendor | Limitation | Affected Scenario | Workaround | |--------|-----------|-------------------|------------| | GoBGP 3.31 | Deduplicates Multiprotocol capabilities by AFI. When two families share the same AFI (e.g., ipv4-unicast + l3vpn-ipv4-unicast, both AFI=1), GoBGP keeps only one. | bgp-vpn-gobgp | None from Ze side. Ze's OPEN is correct per RFC 4760. Families with different AFIs (e.g., ipv4-unicast + l2vpn-evpn) work fine. | | BIRD 2.15 | Enforces next-hop reachability for IPv6 routes. On IPv4-only Docker networks, IPv6 next-hops are unreachable and BIRD rejects routes as invalid (RFC 7606 treat-as-withdraw). | bgp-ipv6-ebgp-bird | Add `multihop;` to BIRD config to disable the directly-connected next-hop check. | <!-- source: test/interop/scenarios/bgp-vpn-gobgp -- GoBGP same-AFI dedup --> <!-- source: test/interop/scenarios/bgp-ipv6-ebgp-bird -- BIRD next-hop reachability --> ## Related Documents - [`.ci` test format](../ci-format/index.md) -- Ze's standard functional test file format - [Functional test system](https://github.com/ze-software/ze/blob/main/docs/functional-tests.md) -- complete guide to the functional test system - [BGP implementation comparison](https://github.com/ze-software/ze/blob/main/docs/comparison.md) -- feature matrix comparing Ze with FRR, BIRD, GoBGP, ExaBGP, and others - [ExaBGP comparison report](https://github.com/ze-software/ze/blob/main/docs/exabgp/exabgp-comparison-report.md) -- detailed implementation differences between Ze and ExaBGP --- ### Page: VPP Deployment Reference https://ze-software.net/architecture/vpp-deployment/ # VPP Deployment Reference Source: 83 blog articles from ipng.ch (Pim van Pelt / IPng Networks, AS8298 / AS50869), VyOS VPP integration, and GoVPP documentation. Full article archive: `~/Code/site/ipng.ch/articles/` (external, not checked in) Consolidated notes: `docs/research/vpp-deployment-notes.md` Feasibility analysis: `docs/research/ze-vpp-analysis.md` ## Production Architecture (IPng AS8298) IPng Networks runs VPP as the production forwarding plane on all 12 backbone routers across Europe and US. VPP replaces Linux kernel routing, achieving 8-35x forwarding performance. | Layer | Technology | |-------|-----------| | Forwarding | VPP (custom .deb packages from source) | | Control plane | Bird2 (custom-built) | | Config tool | vppcfg (Python, YAML-based declarative config) | | OS | Debian 12 Bookworm | | Monitoring | SNMP AgentX (Python) + Prometheus exporter (C) | | Network namespace | `dataplane` (SSH, SNMP, Bird, all run inside it) | | LCP plugin | Custom `lcpng` fork with MPLS, sFlow, sub-interface improvements | ## startup.conf Reference ### Production Values (Supermicro SYS-5018D-FN8T, Xeon D1518 4C) | Section | Key | Value | Rationale | |---------|-----|-------|-----------| | unix | nodaemon | (flag) | VPP runs in foreground under process supervisor | | unix | cli-listen | /run/vpp/cli.sock | vppctl debugging socket | | unix | log | /var/log/vpp/vpp.log | Process log | | unix | full-coredump | (flag) | Crash debugging | | cpu | main-core | 0 | Core 0 for VPP main thread (HT sibling core 4 for Linux) | | cpu | corelist-workers | 1-3 | Cores 1-3 for VPP workers. `isolcpus=1,2,3,5,6,7` keeps Linux off. | | buffers | buffers-per-numa | 128000 | 128K buffers. Formula: expected_packets_in_flight * 2. Proven for full DFZ at 10G. | | buffers | default-data-size | 2048 | Standard. Use 9216 for jumbo frames (9000B MTU + headers). | | buffers | page-size | default-hugepage-size | Match hugepage size. 2M for most deployments. | | heapsize | main-heap-size | 1536M (1.5G) | Enough for ~958K IPv4 + ~198K IPv6 full DFZ FIB. | | statseg | size | 1G | Stats segment shared memory for per-interface/node counters. | | statseg | page-size | default-hugepage-size | Match hugepage size. | | dpdk | dev <pci> { name, num-rx-queues, num-tx-queues } | Per-NIC | See NIC table below. | | plugins | plugin default { disable } | (block) | Disable all, enable specific. | | plugins | plugin dpdk_plugin.so { enable } | (line) | NIC driver. Always enabled. | | plugins | plugin linux_cp_plugin.so { enable } | (line) | LCP. Enable when control-plane connectivity needed. | | plugins | plugin linux_nl_plugin.so { enable } | (line) | Netlink listener. Enable with LCP. | | linux-cp | lcp-sync | (flag) | VPP state changes propagate to Linux TAP mirrors. | | linux-cp | lcp-auto-subint | (flag) | Auto-create sub-TAPs for dot1q/QinQ sub-interfaces. | | linux-cp | default netns | dataplane | LCP TAPs created in `dataplane` netns. Routing daemons run there. | | linux-nl | rx-buffer-size | 67108864 | 64MB netlink buffer. Required for full DFZ route injection without overflow. | ### startup.conf Syntax VPP's startup.conf uses a custom format (not INI, not YAML): ``` section-name { key value key value value flag-without-value nested-section { key value } } ``` String values are unquoted. Multi-word values use spaces. Booleans are bare flags (present = true). Dev entries use PCI address as the key with a nested block for per-device options. ## System Prerequisites | Requirement | Value | How to set | |-------------|-------|-----------| | Hugepages | 3072 x 2MB (6GB) | `echo 3072 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages` | | IOMMU | Enabled | BIOS: enable VT-d/AMD-Vi. Kernel: `intel_iommu=on iommu=pt` | | CPU isolation | Worker cores | `isolcpus=<worker-cores>` kernel param (prevents Linux scheduler interference) | | Netlink buffer | 64MB | `sysctl net.core.rmem_default=67108864` | | Min hardware | 4 cores, 8GB RAM | 1 main + N workers + hugepage memory | ### Hugepage Sizing | Use case | Hugepage count (2MB) | Total memory | |----------|---------------------|-------------| | Lab/small | 1024 | 2GB | | Production 10G | 3072 | 6GB | | Full DFZ + large buffers | 4096 | 8GB | Formula: `main_heap + buffers * data_size + statseg + overhead`. Round up generously. ## NIC Support and Driver Binding ### Driver Matrix | NIC | Chip | Speed | Driver | Binding method | |-----|------|-------|--------|---------------| | Intel X710/XL710 | i40e | 10G/40G | vfio-pci | Standard DPDK bind | | Intel E810 | ice | 25G/100G | vfio-pci | Standard DPDK bind | | Intel X520/X540 | ixgbe | 10G | vfio-pci | Standard DPDK bind | | Intel i350 | igb | 1G | vfio-pci | Standard DPDK bind | | Intel i210 | igb | 1G | vfio-pci | Standard DPDK bind | | Mellanox ConnectX-5+ | mlx5 | 25G/100G | RDMA (bifurcated) | NO vfio. `create interface rdma`. | | VirtIO | virtio | Variable | VPP native | No DPDK bind needed. | ### DPDK Bind Sequence (vfio-pci) Per configured PCI address: 1. Read current driver: `/sys/bus/pci/devices/<addr>/driver` symlink basename 2. Save original driver to persistent state 3. Load kernel modules: `vfio`, `vfio_pci`, `vfio_iommu_type1` 4. Unbind from current driver: write PCI address to `/sys/bus/pci/devices/<addr>/driver/unbind` 5. Bind to vfio-pci: write `vendor:device` to `/sys/bus/pci/drivers/vfio-pci/new_id` Reverse on teardown: unbind vfio-pci, PCI rescan (`/sys/bus/pci/rescan`), rebind original driver. Source: VyOS `control_host.py` (Python, ported to Go for ze). ### Mellanox RDMA Exception mlx5 NICs use bifurcated driver mode. The kernel `mlx5_core` driver stays loaded. VPP creates the interface via `create interface rdma host-if <pci-addr>` instead of a DPDK dev entry. This means: - No vfio binding step - No driver save/restore - DPDK section in startup.conf does NOT include mlx5 devices - Interface creation uses GoVPP `RdmaCreate` API instead of DPDK auto-discovery - Performance is equivalent to DPDK for mlx5 This describes VPP's general Mellanox handling. Ze's own VPP integration does not yet implement RDMA/mlx5 interface creation (there is no `RdmaCreate` call in the codebase); this section is background for a future deployment path. ### NIC-Specific Quirks | NIC | Issue | Workaround | |-----|-------|-----------| | Intel X710 (early VPP 21.06) | DPDK driver failures with vfio-pci and igb_uio | Upgrade VPP. Fixed in later releases. | | Intel i40e (some firmware) | RSS problems with flow-director enabled | Disable flow-director in DPDK dev config. | | Any NIC with jumbo frames | Default buffer size (2048) too small | Set `default-data-size 9216` in buffers section. | ## LCP (Linux Control Plane) Plugin ### Architecture LCP creates TAP mirrors in Linux for every VPP interface. Routing daemons (ze's BGP) run in Linux and see TAP devices. VPP's netlink plugin syncs kernel route changes to VPP's FIB. ``` VPP interface (TenGigabitEthernet3/0/0) <---> Linux TAP (in dataplane netns) | ze BGP reactor (TCP bind on TAP) ``` ### lcpng vs Upstream LCP | Feature | Upstream LCP (VPP 25.02+) | lcpng (IPng fork) | |---------|--------------------------|-------------------| | Basic TAP mirroring | Yes | Yes | | lcp-sync (VPP to Linux) | Yes | Yes | | lcp-auto-subint | Yes | Yes | | MPLS interface enable | No | Yes | | MPLS route sync | No | Yes | | sFlow hooks | No | Yes | | Sub-interface fixes | Partial | Full | **Recommendation for ze:** Start with upstream LCP. Evaluate lcpng when vpp-3 (MPLS) needs LCP MPLS support. ### TAP Interface Naming LCP creates TAPs with VPP's interface name, truncated to Linux's 15-char limit. `TenGigabitEthernet3/0/0` becomes a truncated name. vppcfg uses short names. Ze's naming module (vpp-4 spec) handles bidirectional mapping. ### Network Namespace All LCP TAPs are created in the `dataplane` netns. This isolates the forwarding plane from the management plane. Ze's BGP reactor, SSH, web UI all run in this netns. ## GoVPP Connection ### Socket Transport GoVPP connects via Unix socket at `/run/vpp/api.sock`. Pure Go, no CGo. AsyncConnect pattern: - 10 connection attempts, 1 second interval (initial connect only) - Returns event channel for connection state changes (Connected, Disconnected) - Ze consumes the first state event and returns an error on any non-Connected state; there is no automatic reconnect-with-backoff loop after an established session drops (reconnection is the caller's responsibility) ### API Clients GoVPP provides typed RPC service clients generated from VPP's `.api.json` files: | Client | Purpose | Key APIs | |--------|---------|----------| | ip.RPCService | IPv4/IPv6 routes | IPRouteAddDel, IPRouteDump | | mpls.RPCService | MPLS labels | MplsRouteAddDel, MplsInterfaceEnableDisable | | interfaces.RPCService | Interface mgmt | SwInterfaceSetFlags, SwInterfaceAddDelAddress, SwInterfaceDump | | vxlan.RPCService | VXLAN tunnels | VxlanAddDelTunnelV3 | | lcp.RPCService | LCP pairs | LcpItfPairAddDelV3, LcpItfPairGet | | acl.RPCService | ACLs | AclAddReplace, AclInterfaceSetAclList | | policer.RPCService | Policers | PolicerAddDel | | sflow.RPCService | sFlow | SflowEnableDisable, SflowSamplingRateSet | ### Stats Client (separate from binary API) Stats use shared memory, not the binary API socket. Separate connection: | Stat type | Method | Data | |-----------|--------|------| | InterfaceStats | GetInterfaceStats | rx/tx packets/bytes, drops, errors per interface | | NodeStats | GetNodeStats | clocks/packet, vectors/call per graph node | | SystemStats | GetSystemStats | vector rate, input rate | Stats socket default: `/run/vpp/stats.sock` ## Performance Reference | Metric | Value | Hardware | Source | |--------|-------|----------|--------| | IPv4/IPv6 forwarding | ~35 Mpps total | Xeon D1518 4C, 3 workers, 6x10G | IPng production | | MPLS forwarding | 18-20 Mpps/thread | Same | IPng MPLS series | | L2 cross-connect | >14.88 Mpps/thread | Same | IPng VLL benchmark | | VXLAN (IPv4 underlay) | ~14 Mpps/thread | Same | IPng VLL benchmark | | Clocks/packet (1 Mpps) | ~660 | Same | IPng monitoring | | Route loading (netlink) | ~175K routes/sec | Same | IPng LCP Part 5 | | Route loading (binary API) | ~250K calls/sec | GoVPP estimated | Benchmark needed | | sFlow overhead | 11 CPU cycles/packet | VPP 25.02 | IPng sFlow Part 2 | | Power consumption | ~45W fully loaded | Supermicro SYS-5018D-FN8T | IPng hardware review | ### Efficiency Under Load VPP gets more efficient as load increases due to instruction/data cache reuse in the vector processing model. `vectors_per_call` metric shows this: 1.00 = idle (one packet at a time), 256 = saturated (processing full vectors). Production typically 4-16. ## Hardware Reference | Device | CPU | NICs | VPP Perf (64B) | Power | Price | |--------|-----|------|----------------|-------|-------| | Supermicro 5018D-FN8T | Xeon D1518 4C 2.2GHz | 2x10G + 6x1G + add-in | ~35 Mpps | ~45W | ~$800 | | Netgate 6100 | Atom C-3558 4C 2.2GHz | 2x10G + 4x2.5G + 2x1G | 5 Mpps (1 worker) | 19W | $699 | | GoWin R86S | Pentium N6005 4C 2GHz | OCP + 3x2.5G | 13.4 Mpps | 17-20W | ~$314 | | Gowin 1U N305 | i3-N305 8C 3GHz | OCP 2x25G + 2x2.5G + 3x1G | 18.34 Mpps | 47.5W | - | ## What VPP Does NOT Provide - No routing protocols (BGP, OSPF, IS-IS). Ze's job. - No firewall (nftables/iptables bypassed). VPP ACLs are basic match/action. - No hierarchical QoS. VPP policers are flat token buckets, not HTB/HFSC. - No kernel features on fast path (tc, XDP, conntrack, eBPF). - DPDK/RDMA NIC drivers only (not all hardware supported). - Minimum 4 physical CPU cores, 8 GB RAM. --- ### Page: Configuration https://ze-software.net/features/bgp-configuration/ # Configuration This page walks through BGP peer configuration specifically, since it is the most-configured surface. For every other subsystem's config syntax (interfaces, firewall, L2TP, DHCP, and the rest of Ze's 38 plugin groups), see the generated [Configuration Reference](https://ze-software.net/reference/configuration/), built straight from each plugin's own YANG module. ### Peer Settings Peers are keyed by name (`peer <name> { }`) with IP and AS in nested containers: | Setting | Description | Validation | |---------|-------------|------------| | `connection/remote/ip` | Peer IP address | Required (`ze:required`) | | `session/asn/remote` | Peer AS number | Required (`ze:required`) | | `session/asn/local` | Local AS number | Required (`ze:required`, inheritable from bgp level) | | `connection/local/ip` | Local bind address | Suggested (`ze:suggest`, can be `auto`) | | `router-id` | Per-peer router ID override | Optional (or inherited) | | `name` | Peer key (must start with letter) | Required | | `timer/receive-hold-time` | Hold timer (0 or 3-65535 seconds) | 1-2 rejected | | `connection/remote/connect` | Initiate outbound connections (boolean) | Default: true | | `connection/local/accept` | Accept inbound connections (boolean) | Default: true | | `md5-password` | TCP MD5 authentication | Optional | | `outgoing-ttl` | TTL for outgoing packets | Optional | | `ttl-security` | Minimum TTL for incoming packets | Optional | | `group-updates` | Enable/disable UPDATE grouping | Default: enabled | <!-- source: internal/component/bgp/config/resolve.go -- ResolveBGPTree config resolution --> <!-- source: internal/component/bgp/config/peers.go -- peer config parsing --> <!-- source: internal/component/bgp/yang/ze-bgp-conf.yang -- BGP config YANG schema --> ### Required Field Validation The `ze:required` and `ze:suggest` YANG extensions declare which fields must be present in a peer after config inheritance resolution (bgp -> group -> peer merge). | Extension | Behavior | |-----------|----------| | `ze:required "path"` | Descendant field must have a value in each list entry after inheritance. Validated at `ze config validate`, editor commit, and daemon startup for any list (generic, not BGP-only). | | `ze:suggest` | Field shown in the web creation form with inherited defaults but not mandatory. | The registered `ze:validate` value validators run on the same bytes at `ze config validate`, at daemon start, and at SIGHUP reload. One walk serves the offline check and the daemon, so they cannot reach different verdicts. A value a validator refuses stops the daemon and fails a reload, with no override and no force flag, and the message names the section, the leaf, and the rule. A refusal declines rollback recovery, so an operator's typo cannot start the daemon on a config they never wrote. A missing mandatory field is graded a warning on both surfaces rather than a refusal. <!-- source: internal/component/config/validate_sections.go -- ValidateCustomSections, ErrCustomValidation --> <!-- source: internal/component/config/loader.go -- LoadConfig calls ValidateCustomSections --> <!-- source: cmd/ze/hub/main.go -- recoverableLoadError declines recovery for ErrCustomValidation --> Peer required fields: `connection/remote/ip`, `session/asn/local`, `session/asn/remote`. Suggested: `connection/local/ip`. Fields can be satisfied by inheritance: `session/asn/local` set at bgp level satisfies the requirement for all peers; `session/asn/remote` set at group level satisfies it for group members. <!-- source: internal/component/config/yang/modules/ze-extensions.yang -- ze:required, ze:suggest extensions --> <!-- source: internal/component/config/required.go -- CheckRequired (generic, any list) --> <!-- source: internal/component/bgp/config/resolve.go -- CheckRequiredFields (BGP-specific) --> ### Prefix Limits (RFC 4486) Per-peer per-family prefix maximum enforcement. Mandatory for every negotiated family. | Setting | Scope | Description | |---------|-------|-------------| | `prefix { maximum N; }` | Per family | Hard maximum prefix count. Mandatory. | | `prefix { warning N; }` | Per family | Warning threshold. Default: 90% of maximum. | | `prefix { teardown true/false; }` | Per family | Tear down on exceed (default: true) or warn-only. | | `prefix { idle-timeout N; }` | Per family | Seconds to wait before reconnect after this family caused a teardown. Default 0, which keeps the peer down. | | `prefix { reconnect never\|backoff\|timer; }` | Per family | What the peer does after this family stopped the session. No value means `timer` when `idle-timeout` is above 0, and `never` when it is 0. | | `prefix { count offered\|installed; }` | Per family | Which prefixes the count compared against `maximum` holds. Default `offered`. | When a family exceeds its maximum: NOTIFICATION Cease/MaxPrefixes (subcode 1) is sent and the session is torn down. With `teardown false` on that family, the session stays up and the UPDATE that crossed the maximum is dropped. The drop is per UPDATE, not per NLRI: Ze consumes the whole message and delivers none of it, so routes of other families in that same UPDATE are dropped with it. Each family reads its own `teardown` value, so one family can warn while another stops the session. <!-- source: internal/component/bgp/reactor/session_read.go -- processMessage returns before plugin delivery when prefixDrop is set --> `count` states which prefixes the number compared against `maximum` holds, and it changes what happens after `teardown false` drops an UPDATE. RFC 4271 Section 6.7 does not say whether a prefix limit governs what the peer offered or what the receiver kept, so the operator chooses. `offered`, the default, keeps a dropped UPDATE's prefixes in the count: the count stays above the maximum, and Ze drops every later announce of that family until the peer withdraws them. `installed` leaves the count where it was, so the family accepts the next announce that fits. Neither value is the size of the RIB, because import policy can reject a counted prefix. The choice never changes enforcement: both values drop the same UPDATE and send the same NOTIFICATION. <!-- source: internal/component/bgp/reactor/session_prefix.go -- applyInstalledPrefixDeltas settles an installed family before the count moves --> A peer stopped by a prefix limit STAYS DOWN by default. Its state reads `idle-hold`, `ze show warnings` carries a `prefix-hold` warning that names the family, and the log line says `peer held down`. The peer comes back when an operator recreates it: change that peer's config and commit, or delete and add the peer. This is what Cisco and Juniper do for the same event. `reconnect backoff` asks for the opposite: the peer comes back on its usual connect backoff, 5 to 60 seconds. `reconnect timer`, or an `idle-timeout` above 0, waits idle-timeout x 2^(N-1), capped at 1 hour. The wait comes from the family that exceeded its maximum, and resets on a stable session. A `reconnect` value that contradicts `idle-timeout` in the same block is a config error. **PeeringDB integration:** `resolve peeringdb max-prefix <asn>` queries PeeringDB for a peer's ASN and updates prefix maximums automatically. A configurable margin (default 10%) is added to PeeringDB values. The PeeringDB URL is configurable under `system { peeringdb { url; margin; } }` for private mirrors. Staleness warnings appear when prefix data is older than 6 months. **Prometheus metrics:** `ze_bgp_prefix_count`, `ze_bgp_prefix_maximum`, `ze_bgp_prefix_warning`, `ze_bgp_prefix_warning_exceeded`, `ze_bgp_prefix_ratio`, `ze_bgp_prefix_maximum_exceeded_total`, `ze_bgp_prefix_teardown_total`, `ze_bgp_prefix_stale`. <!-- source: internal/component/bgp/reactor/session_prefix.go -- prefix limit enforcement --> <!-- source: internal/component/bgp/reactor/peer_run.go -- prefixReconnectDecision and holdDownAfterPrefixTeardown, per-family reconnect --> <!-- source: internal/component/resolve/cmd/resolve.go -- handlePeeringDBMaxPrefix --> ### Cross-Peer Update Groups Peers with identical outbound encoding contexts (same ContextID, same policy) are automatically grouped. The reactor builds each UPDATE once per group and fans out the wire bytes to all members, eliminating redundant per-peer UPDATE construction. GroupKey combines the peer's `sendCtxID` (which encodes ASN4, ADD-PATH, Extended Message, Extended Next Hop, iBGP/eBGP, and ASN values) with a policy key (uniform today, extensible for per-peer export policy). Groups are maintained by the reactor: peers are added on session establishment and removed on session close. When disabled or when all peers have unique contexts, behavior is identical to per-peer building with negligible overhead (one map lookup per peer lifecycle event). Default enabled. Configurable via `ze.bgp.reactor.update-groups` (boolean, default true). ExaBGP migrated configs automatically set `update-groups false` to preserve per-peer UPDATE behavior. <!-- source: internal/component/bgp/reactor/update_group.go -- UpdateGroupIndex, GroupKey, Add, Remove, GroupsForPeers --> <!-- source: internal/component/bgp/reactor/reactor_notify.go -- updateGroups.Add on established, Remove on closed --> <!-- source: internal/component/bgp/reactor/reactor_api_batch.go -- group-aware AnnounceNLRIBatch --> <!-- source: internal/component/bgp/reactor/reactor_api_forward.go -- group-aware ForwardUpdate with fwdBodyCache --> <!-- source: internal/component/config/environment.go -- ze.bgp.reactor.update-groups env var registration --> <!-- source: internal/exabgp/migration/migrate.go -- injectUpdateGroupsDisabled --> ### Session Resilience | Feature | Description | |---------|-------------| | TCP_NODELAY | Disables Nagle's algorithm. BGP messages are application-framed; Nagle only adds latency. | | DSCP CS6 (RFC 4271 S5.1) | Sets IP_TOS/IPV6_TCLASS to 0xC0 so network QoS policies prioritize BGP traffic. | | Graceful TCP close | Half-close (CloseWrite) before Close sends FIN instead of RST, ensuring remote peers read pending NOTIFICATIONs. | | Send Hold Timer (RFC 9687) | Detects when local side cannot write to peer. Duration: max(8min, 2x hold-time), or the per-peer `send-hold-time`, which must exceed the hold time. Sends NOTIFICATION code 8 on expiry. Stopped when the negotiated hold time is zero, since such a session sends nothing. | | Hold timer expiry (RFC 4271 Section 8.2.2, Event 10) | Every expiry sends NOTIFICATION code 4 (Hold Timer Expired) and stops the session. Ze grants no reprieve for CPU congestion. | | Write deadline | Forward pool batch writes use a 30s TCP write deadline (configurable via `ze.fwd.write.deadline`) to prevent stuck peers from blocking workers. | | Bounded overflow pool | Two-tier pool: per-peer pools (64 slots) absorb steady-state traffic, shared MixedBufMux overflow pool (auto-sized from peer prefix maximums, overridable via `ze.fwd.pool.size` byte budget) bounds overflow memory. | | Congestion backpressure | Two-threshold enforcement: pool > 80% denies buffers to the worst destination peer (natural TCP backpressure). Pool > 95% with peer > 2x weight share for 5s triggers forced teardown. | | GR-aware congestion teardown | Forced teardown is GR-aware: GR peers get TCP close (route retention), non-GR peers get Cease/OutOfResources NOTIFICATION. | | Pool headroom | `ze.fwd.pool.headroom` adds extra memory beyond auto-sized baseline, trading memory for delayed teardown decisions. | **Prometheus metrics:** `ze_bgp_pool_used_ratio`, `ze_bgp_overflow_items{peer}`, `ze_bgp_overflow_ratio{source}`, `ze_forward_buffer_denied_total`, `ze_forward_congestion_teardown_total`. <!-- source: internal/component/bgp/reactor/session_connection.go -- TCP_NODELAY, IP_TOS, closeConn --> <!-- source: internal/component/bgp/reactor/session_write.go -- Send Hold Timer --> <!-- source: internal/component/bgp/reactor/session.go -- OnHoldTimerExpires callback --> <!-- source: internal/component/bgp/reactor/forward_pool.go -- write deadline, overflow pool --> <!-- source: internal/component/bgp/reactor/forward_pool_congestion.go -- two-threshold enforcement --> ### Route Loop Detection | Check | RFC | Scope | What it detects | |-------|-----|-------|-----------------| | AS loop | RFC 4271 Section 9 | All sessions | Local ASN in received AS_PATH (AS_SEQUENCE or AS_SET) | | ORIGINATOR_ID loop | RFC 4456 Section 8 | iBGP only | ORIGINATOR_ID matches local Router ID | | CLUSTER_LIST loop | RFC 4456 Section 8 | iBGP only | Local Router ID found in CLUSTER_LIST | All three checks run in one ingress filter at `FilterStageProtocol`, so they come after RFC 7606 structural validation and after prefix limit counting, which both run on the session read path before the UPDATE reaches the filter pipeline. A route failing any check is dropped silently (no NOTIFICATION, session stays up). Cluster ID defaults to Router ID per RFC 4456 Section 7. <!-- source: internal/component/bgp/reactor/filter/loop.go -- LoopIngress --> <!-- source: internal/component/bgp/filterapi/filterapi.go -- FilterStageProtocol --> <!-- source: internal/component/bgp/reactor/session_validation.go -- enforceRFC7606; internal/component/bgp/reactor/session_prefix.go -- checkPrefixLimits --> ### Capabilities Configuration | Capability | Config Key | Values | |------------|-----------|--------| | 4-byte ASN | `asn4` | true / false | | Route Refresh | `route-refresh` | true / false | | ADD-PATH | `add-path` | Per-family send/receive/both | | Extended Message | `extended-message` | true / false | | Extended Next Hop | `nexthop` | Per-family AFI mapping | | Graceful Restart | `graceful-restart` | restart-time (0-4095s), long-lived-stale-time (0-16777215s) | | Role | `role` | provider / rs / rs-client / customer / peer | | Role Strict | `role/strict` | true / false | ### Route Configuration Static routes configured per-peer with full attribute control: - Per-family NLRI with add/del/eor operations - All standard path attributes - Watchdog-controlled deferred announcement - MPLS labels (single and multi-label) - Route Distinguisher for VPN routes - Prefix-SID and SRv6 attributes ### Process Bindings External processes receive BGP events and send commands: - JSON event encoding (peer-up, peer-down, route updates) - Text command protocol (route announce/withdraw) - Configurable message filtering (receive-update, receive-open, etc.) - Neighbor change notifications ## Dependency Graph `ze config graph <file>` exposes configuration groups, peers, plugin dependencies, and their relationships as machine-readable nodes and edges. Use it to identify which peers inherit a shared value before changing that group. Plugin dependency expansion keys on the REGISTERED plugin name, not on the operator's list label. `plugin { internal rs { use bgp-rs } }` names the instance `rs` and runs the registered plugin `bgp-rs`, and the dependencies `bgp-rs` declares are pulled in. Keying on the label made the resolver treat the plugin as external and skip expansion, so both hard and optional dependencies were dropped with no error: a route server configured that way ran with no `bgp-adj-rib-in` and therefore no peer-up Adj-RIB-In replay. <!-- source: internal/component/config/loader.go -- ExpandDependencies --> <!-- source: internal/component/plugin/resolve.go -- RegistryName --> A config root that no plugin and no hub handler claims is stored and delivered to nobody, so it has no effect. `ze doctor` reports it as `doctor-config-root-unclaimed`, naming the path and the two causes: the owning plugin is not in this binary or did not load, or its config root is missing. The check fails closed and reports `doctor-config-claims-unavailable` when no plugin in the build declares a config root at all. <!-- source: internal/component/doctor/checks_config_claims.go -- checkConfigClaims, configClaimDiagnostics --> ### Demo: Find every peer affected by a group change Inspect and validate a BGP group, then use Ze's dependency graph to prove which peers inherit the value before scheduling maintenance. [Download the asciicast recording](../../assets/demos/config-graph.cast?v=1ea1811466) · [Plain-text transcript](../../assets/demos/config-graph.txt?v=7a64ac5a0c) Recorded with Ze 26.08.31 on macOS and Linux using Ze recorder. Duration: 1 minute 10 seconds. ```console An operator needs to change the transit group's remote ASN and identify every peer that inherits it before scheduling maintenance. $ ze config show router.conf bgp group transit The scoped configuration shows `upstream-a` and `upstream-b` inside the transit group. $ ze config validate router.conf configuration valid $ ze config graph router.conf | ze pipe text | ze pipe match peer/upstream $ ze config graph router.conf | ze pipe text | ze pipe match group/transit $ ze config graph router.conf | ze pipe text | ze pipe match inherits The graph answer holds two lists, `nodes` and `edges`. A row operator such as `match` has no single set of rows there, so Ze refuses it by name instead of picking one list. `| text` renders both lists as aligned rows, one relationship to a line. `| match` then keeps the lines that name the two peers, the group they share, and the two `inherits` relationships. No reporting helper creates the displayed relationships. The command filters Ze's graph output directly through Ze's format pipeline. ``` --- ### Page: CLI Commands https://ze-software.net/features/cli-commands/ # CLI Commands ### Every command starts with its verb `show`, `clear`, `monitor`, `request`, `set`, and `delete` are the six verbs. The words after the verb are the YANG path. A bare form with no verb is not in the command tree, and the dispatcher answers `unknown command` for it. `daemon reload` is now `request reload`, `daemon status` is `show status`, `daemon quit` is `request halt`, `daemon shutdown` is `request shutdown`, and `bgp summary` is `show bgp`. `stop`, `restart`, and `reboot` are the exception, and they keep their bare spelling. The SSH exec middleware intercepts those three lifecycle verbs before the dispatcher, which registers no key for them. <!-- source: internal/plugins/signal/main.go -- Commands table ExecCommand column --> <!-- source: internal/component/ssh/ssh.go -- execMiddleware lifecycle interception --> Every subcommand of `ze show`, `ze clear`, `ze monitor`, `ze request`, `ze set`, and `ze delete` resolves against the daemon's own registrations. The verb-relative tree is built from the same registration set the dispatcher is keyed on, so the client and the daemon cannot disagree about which words are a command. <!-- source: internal/component/cli/client/verb_tree.go -- BuildVerbCommandTree, AbsoluteVerbPath --> A local in-process handler is refused for any argv that reaches a declared command below it. `show interface` is registered at two words and takes an interface name, so `brief`, `scan`, `type`, `errors`, `rate`, and the two `name <name> ...` forms all go to the daemon rather than being read as interface names. <!-- source: internal/component/command/registry/registry.go -- LookupLocal prefix refusal --> ### Peer selectors on a destructive command One resolver answers the peer selector for every peer-scoped command. It accepts a name, an address, an ASN (`as65001`), or a prefix, so a selector that works on one verb works on all of them. A command that acts on ONE peer refuses a wildcard (`*` or empty), refuses an exclusion selector (`!edge1`, `!as65001`, `!10.0.0.0/24`), and refuses a selector that matches more than one peer. It never guesses which peer was meant. An unresolvable selector is an error with the selector quoted, rather than a no-op reported as success. A `show` command that narrows a list still accepts an exclusion, because a complement is a good answer when the command filters rather than acts. <!-- source: internal/component/plugin/server/command.go -- ResolveSinglePeer, errExcludePeerSelector --> <!-- source: internal/component/bgp/plugins/cmd/peer/peer.go -- handleTeardown, handleBgpPeerFlush --> ### Positional arguments Every declared argument kind takes part in positional matching, typed kinds included, and mandatory definitions are offered a token before optional ones. A `uint16` port no longer skips its token and then fails with `required argument missing`, and an optional string can no longer starve a required argument of its value. One spare token becomes the peer selector when the command declares that it requires one, no selector arrived out of band, and exactly one token is spare, which is what makes `delete bgp peer 127.0.0.1` work, and `create bgp peer 127.0.0.1 asn 65001` with it: the keyword-value pairs are consumed first, so the address is the one token left over. A SECOND fault in the tail leaves more than one spare token, so the selector is never bound and the answer says the command "requires a selector" over a line that gave one. The row is in `plan/journal/earlier-guard-hides-the-better-error.md`. A value that sits BETWEEN two keywords reaches the leaf the model anchored to the first of them. A leaf declared on a grouping container carries that container's name. That name is the word the operator types the value after, so `send bgp <selector> unicast <prefix>` binds the selector and leaves the prefix to the handler. The shape-based fallback stays for a command that anchors nothing, and both refuse ambiguity: two candidates name none. <!-- source: internal/component/plugin/server/command.go -- positionalDef, matchCommandTokens, anchoredDef, implicitSelectorDef --> <!-- source: internal/component/config/yang/command.go -- appendAnchored --> ### A command declares what its answer holds A command declares whether its answer holds rows or one document, and which of its fields hold an IP address. The CLI refuses an operator that declaration cannot support, by name, before the command runs: ``` show bgp rib status | count count cannot apply here: this command answers one document, and count acts on rows ``` `ze help command "<path>" --json` lists the operators a declared command supports, and it reads the same declaration, so the published list and the runtime cannot disagree. `| resolve` and `| origin` are listed only where the command declares a field that holds an address. Every `show bgp` command declares one. Go compiled into the daemon declares twenty paths. A plugin process declares the other eleven in its startup message: six under `show bgp rpki`, two under `show bgp rs`, two under `show bgp adj-rib-in`, and `show bgp healthcheck`. An undeclared command still refuses what it cannot support, from the answer it has in hand, after it runs. <!-- source: internal/component/command/pipe.go -- validateDeclaredShape --> <!-- source: internal/component/command/answer_shape.go -- RegisterShape, RegisterAddressFields, RegisterPluginShapes --> <!-- source: internal/component/bgp/plugins/cmd/peer/peer.go -- registerShapes --> <!-- source: internal/component/plugin/server/startup.go -- registerPluginShapes --> ### Protocol Tools | Command | Description | |---------|-------------| | `ze bgp decode` | Decode BGP message from hex to JSON | | `ze bgp encode` | Encode text route command to BGP wire hex | <!-- source: internal/component/bgp/cli/main.go -- bgp decode/encode dispatch --> ### Configuration Management | Command | Description | |---------|-------------| | `ze config validate <file>` | Validate configuration file | | `ze config edit` | Interactive configuration editor | | `ze config migrate` | Convert an older ze config to the current format | | `ze config fmt` | Format and normalize config file | | `ze config dump` | Dump parsed configuration tree | | `ze config diff <a> <b>` | Compare two configuration files | | `ze config set` | Set a configuration value programmatically | | `ze config import` | Import a configuration file into ze | | `ze config rename` | Rename a configuration element | | `ze config archive <name>` | Archive config to a named destination ([guide](../../guides/config-archive/index.md)) | | `ze config history` | List rollback revisions | | `ze config rollback <N>` | Restore revision N | <!-- source: internal/component/config/cli/main.go -- config subcommand dispatch --> <!-- source: internal/component/config/cli/cmd_archive.go -- archive subcommand --> <!-- source: internal/component/config/cli/cmd_validate.go -- validate command --> <!-- source: internal/component/config/cli/cmd_migrate.go -- migrate command --> <!-- source: internal/component/config/cli/cmd_dump.go -- dump command --> <!-- source: internal/component/config/cli/cmd_diff.go -- diff command --> ### Schema Discovery | Command | Description | |---------|-------------| | `ze schema list` | List all registered YANG schemas | | `ze schema show <module>` | Show YANG content for a module | | `ze schema handlers` | List handler→module mapping | | `ze schema methods [module]` | List RPCs from YANG modules | | `ze schema events` | List notifications from YANG | | `ze schema protocol` | Show protocol version and format info | <!-- source: internal/component/config/yang/cli/main.go -- schema subcommand dispatch --> ### Daemon Control | Command | Description | |---------|-------------| | `ze <config-file>` | Start daemon with configuration | | `ze signal reload` | Send SIGHUP and reload configuration | | `ze signal stop` | Graceful shutdown (no GR marker) | | `ze signal restart` | Graceful restart (writes GR marker, then shuts down) | | `ze signal status` | Dump process status (SIGUSR1 equivalent) | | `ze signal quit` | Send SIGQUIT, dump goroutines, and halt | | `ze status` | Check if daemon is running | Each `ze signal` subcommand sends one SSH exec command to the daemon. `reload` sends `request reload`, `status` sends `show status`, and `quit` sends `request halt`. `stop`, `restart`, and `reboot` keep their bare spelling because the SSH exec middleware intercepts them before the dispatcher. <!-- source: internal/plugins/signal/main.go -- Commands registry, ExecCommand column --> ### Runtime Interaction | Command | Description | |---------|-------------| | `ze cli` | Interactive CLI (with `-c <cmd>` for single command) | | `ze show <command>` | Read-only daemon commands | **Ping and traceroute:** `show ping` and `show traceroute` run one-shot ICMP checks from the router itself using ze's internal engine -- no daemon required, they work as local handlers. `monitor ping` and `monitor traceroute` open a live, continuously-updating view (Ctrl-C to stop); pipe either through `| log` for scrollback, or add `| resolve` / `| origin` to enrich traceroute hops with reverse DNS or ASN info. `size` sets the ICMP payload length (1-65507), not the total packet: unlike `ping(8)`'s "64 bytes" (56 payload + 8 header), `size 1400` sends 1400 payload bytes on top of the ICMP and IP headers. Both `show ping` and `monitor ping` take `count` (1-100) and `size`; omitting `count` on `monitor ping` is what makes it stream until Ctrl-C, and it additionally takes `interval` (100ms-30s). ``` ze show ping 8.8.8.8 count 5 timeout 3s ze show ping 8.8.8.8 size 1400 ze show traceroute 8.8.8.8 max-hops 10 probes 1 ze cli -c "monitor ping 8.8.8.8 interval 500ms" ze cli -c "monitor ping 8.8.8.8 count 5 size 1400" ze cli -c "monitor traceroute 8.8.8.8 | log | resolve" ``` <!-- source: internal/component/ping/cmd/register.go -- showPingLocal, monitorPingLocal --> <!-- source: internal/component/traceroute/cmd/register.go -- showTracerouteLocal --> <!-- source: internal/component/cli/model_ping_test.go -- parsePingMonitorArgs --> <!-- source: internal/component/cli/model_traceroute_test.go -- parseTracerouteMonitorArgs --> **Live peer dashboard:** `monitor bgp` in the interactive CLI opens a live dashboard showing router identity, a sortable colour-coded peer table with update rates, and a drill-down detail view. It refreshes every 2 seconds. Use j/k to move, s/S to sort, Enter for detail, and Esc to exit. The state column renders green for `established`, yellow for the transitional states, and red for `stopped`, `idle`, and `idle-hold`. `idle-hold` is the state a prefix limit leaves a peer in when the family that overflowed asked for no reconnect, so it needs an operator and is coloured like the other down states. <!-- source: internal/component/cli/model_dashboard.go -- isDashboardCommand --> <!-- source: internal/component/cli/model_dashboard_render.go -- stateStyled --> ### Demo: Operate BGP from the live dashboard Connect to Ze over SSH, open the live BGP dashboard, sort peers, and inspect one session. [Download the asciicast recording](../../assets/demos/cli-dashboard.cast?v=b5aa861b35) · [Plain-text transcript](../../assets/demos/cli-dashboard.txt?v=86542601eb) Recorded with Ze 26.09.01 on macOS and Linux using Ze recorder. Duration: 44 seconds. ```console $ ssh ze-demo ze# exit ze> monitor bgp The dashboard polls three local BGP sessions. Press "s" to sort by the next column, use the arrow keys to select a peer, and press Enter for live session details. Press Escape to return and "q" to leave the dashboard. ``` **Commit confirmed:** The editor supports `commit confirmed <seconds>` for safe remote changes. The config is applied immediately but auto-reverts if `confirm` is not issued within the timeout window (1-3600 seconds). Use `confirm abort` to revert manually. Modeled after Junos commit confirmed. <!-- source: internal/component/cli/model_load.go -- cmdCommitConfirmed --> **Command history persistence:** Both `ze config edit` and `ze cli` persist command history to the zefs blob store. History survives application restarts, is stored per-mode (edit vs command) and per-user, with consecutive dedup and a configurable rolling window (default 100, max 10000). Graceful degradation when no blob store is available (in-memory only). <!-- source: internal/component/cli/history.go -- History type --> **Login warnings:** When an operator connects via SSH, ze checks for conditions requiring attention and displays warnings in the welcome area. Each warning includes a message and an actionable command. Currently checks for stale prefix data (peers with `prefix-updated` older than 6 months); run `update bgp peer * prefix` to refresh from PeeringDB. <!-- source: internal/component/ssh/session.go -- createSessionModel login warning collection --> **Plugin debug shell:** `ze bgp plugin cli` connects to the daemon via SSH, runs the 5-stage plugin handshake, and enters interactive command mode. Developers can test plugin protocol interactions by hand -- sending dispatch-command, subscribe-events, decode-nlri, etc. Accepts defaults (Enter through Q&A) or custom registration parameters (families, plugin name). <!-- source: internal/component/bgp/cli/cmd_plugin.go -- cmdPluginCLI --> ### Other | Command | Description | |---------|-------------| | `ze plugin <name>` | Run a registered plugin | | `ze exabgp plugin` | Run ExaBGP plugin with ze bridge | | `ze exabgp migrate` | Convert ExaBGP config to ze | | `ze completion bash/zsh/fish/nushell` | Generate shell completion scripts | | `ze show plugin list` | List the plugins compiled into this binary | <!-- source: internal/plugins/completion/main.go -- completion subcommand --> <!-- source: internal/component/plugin/cli/main.go -- plugin subcommand --> <!-- source: internal/plugins/exabgp/main.go -- exabgp subcommand --> ## Section: Evaluate --- ### Page: Every feature Ze ships. https://ze-software.net/features/ # Every feature Ze ships. 52 shipped features plus the planned roadmap. Each card's category shows where the feature fits: operate, routing, services, automate, observe, secure, or platform. Everything shipped runs in both daemon and appliance modes unless a card says otherwise. ## Built for demanding operators. Ze starts with a configuration and protocol engine. The shipped network operating system adds BGP, interface management, FIB programming, plugins, operator tools, a minimal appliance runtime, and diagnostics as one product. ### AI Tool Interfaces *automate* -- `MCP` `Generated` `AI tools` - **MCP** exposes CLI/API commands - AI tools read **structured output** - Plugins expose **discoverable tools** [Learn more](https://ze-software.net/features/ai-first/) ### SSH CLI *operate* -- `Built-in SSH` `RBAC` - Manage Ze without **OS shell** accounts - **Profiles**, audit, and accounting - **commit**, rollback, diff, completion [Learn more](https://ze-software.net/features/cli-commands/) ### YANG Configuration *operate* -- `YANG` `ExaBGP` - Schema-driven **validation** - **One model** feeds every surface - **Plugin** defined config and commands [Learn more](https://ze-software.net/features/bgp-configuration/) ### Output Formatting *operate* -- `Shell-like pipes` `Offline` - **table**, **json**, **yaml**, **ndjson** - **match**, **count**, **first**/**last** - Offline via **ze format** - **Every command**, no rendering flags [Learn more](https://ze-software.net/features/formatting/) ### Web Workbench *operate* -- `HTMX` `SSE` - YANG-driven **config tree** - Same **CLI grammar** in browser - **Live updates** via SSE [Learn more](https://ze-software.net/features/web-interface/) ### Looking Glass *operate* -- `Routes` `Topology` `Birdwatcher` - Peer and **route viewer** - **Topology** graph - SSE streaming for **live state** [Learn more](https://ze-software.net/features/looking-glass/) ### System Readiness *operate* -- `ze doctor` `ze explain` - Offline **pre-start checks** - Health, warnings, and **errors** - Structured **remediation** with `ze explain` [Learn more](https://ze-software.net/guides/production-diagnostics/) ### Native BGP Engine *routing* -- `BGP` `IPv4/IPv6` `FlowSpec` - Full implementation in **Go** - **Lazy parsing**, buffer-first encoding - Negotiated **capabilities** [Learn more](https://ze-software.net/features/bgp-protocol/) ### Static Routes *routing* -- `ECMP` `BFD` `PBR` - Named tables, **policy routing** - **BFD**-tracked failover - Multi-path **ECMP** groups [Learn more](https://ze-software.net/guides/static-routes/) ### BFD *routing* -- `RFC 5880` `Auth` - **Single-hop** and **multi-hop** - GTSM, jitter, **BGP** integration - SHA1/MD5 **auth**, echo mode [Learn more](https://ze-software.net/features/bgp-protocol/) ### MRT Recording *routing* -- `RFC 6396` `Analysis` - Updates, messages, **RIB snapshots** - **Strftime** file rotation - Show, inject, replay, **filter** [Learn more](https://ze-software.net/guides/mrt-analysis/) ### DNS Resolver *services* -- `Cache` `Pipes` - Built-in **cached** resolver - **| resolve** and **| origin** pipe operators - No external **daemon** needed [Learn more](https://ze-software.net/features/dns-resolver/) ### Plugin System *automate* -- `ExaBGP` `RPKI` `Policy` - Plugins add **commands**, RPCs, events - YANG roots join **CLI** and web - Independent, **composable** [Learn more](https://ze-software.net/reference/plugins/) ### Programmable *automate* -- `REST` `gRPC` `gNMI` - **REST API**, **gRPC**, **gNMI** - Shared engine for **identical output** - Automate from **any language** [Learn more](https://ze-software.net/features/api-commands/) ### AI-First Design *automate* -- `Self-describing` `Skills` - **Self-describing** command catalogue from the live binary - Every command is an **automation** surface - Structured **diagnostics** and repair plans [Learn more](https://ze-software.net/features/ai-first/) ### MCP Integration *automate* -- `MCP` `OAuth 2.1` - **Streamable HTTP** transport, OAuth 2.1 resource server - Server-initiated **elicitation**, task-augmented tool calls - **MCP Apps UI** with embedded panels [Learn more](https://ze-software.net/features/mcp-integration/) ### ExaBGP Compatibility *automate* -- `Migration` `Bridge` - Automatic config **migration** - **Plugin bridge** for existing workflows - Migration path for existing scripts [Learn more](https://ze-software.net/features/exabgp-compatibility/) ### Evidence Over Claims *observe* -- `Fuzz` `Interop` `Docker` - Unit, functional, **fuzz**, chaos - Performance **benchmarks** - **Interop** vs FRR, BIRD, GoBGP [Learn more](https://ze-software.net/features/interoperability-testing/) ### Development Activity *observe* -- `Heatmap` `Live data` - A year of **commits** and added lines, at a glance - Built from git history each time - Current Go code composition [Learn more](https://ze-software.net/project/activity/) ### Prometheus Telemetry *observe* -- `Netdata` `Prometheus` - **138 metrics** from /proc and /sys - **Netdata** naming, drop-in replacement - Existing **Grafana** dashboards keep working [Learn more](https://ze-software.net/guides/monitoring/) ### Health Registry *observe* -- `HTTP` `503` - **/health** HTTP endpoint - Per-component **status** checks - BGP, FIB, IPsec, L2TP, **VPP** [Learn more](https://ze-software.net/features/) ### Host Inventory *observe* -- `CPU` `NIC` `SMART` - **CPU**, NIC, DMI, memory, thermal - **SMART** disk health and self-tests - **JSON** output for pipelines [Learn more](https://ze-software.net/features/) ### Crash Capture *observe* -- `Panic` `Syslog` - Automatic **panic** stack traces - Ring buffer **context** (last 64 entries) - **show crashes** CLI command [Learn more](https://ze-software.net/features/) ### Tech-Support Bundle *observe* -- `Offline` `JSON` - **20 modules**, pure Go, no shell-outs - Structured **JSON** per module - Privacy-by-default, **gokrazy**-safe [Learn more](https://ze-software.net/features/) ### Production Diagnostics *observe* -- `CLI` `MCP` - 11 built-in tools replacing **ss, dmesg, lsof** - **tcpdump**, traceroute, ping, mtr - All exposed via **MCP** for AI debugging [Learn more](https://ze-software.net/guides/production-diagnostics/) ### Secure by Default *secure* -- `SSH` `RBAC` `RPKI` `ASPA` - **SSH** access to the CLI - **RPKI** route origin validation - No **other daemons** needed [Learn more](https://ze-software.net/reference/plugins/) ### TACACS+ AAA *secure* -- `RFC 8907` `Accounting` - SSH login via **TACACS+** - Command **accounting** START/STOP - Server failover, **local** fallback [Learn more](https://ze-software.net/guides/tacacs/) ### Audit Trail *secure* -- `Commits` `Auth` - Config **commit**, discard, and reload - Failed **auth** on every interface - Filter by action, actor, and **time** [Learn more](https://ze-software.net/guides/audit/) ### PKI Store *secure* -- `X.509` `TLS` - YANG-modelled **certificate** management - Chain validation, **expiry** checks - Shared by IPsec, **TLS**, mutual auth - **Local CA** issues 24-hour certs to Ze's components [Learn more](https://ze-software.net/features/) ### Minimal Appliance Mode *platform* -- `Appliance` `Server` - **Kernel, init, Ze** runtime - No **package manager** or general shell - **ISO/PXE** bare-metal install - Linux server with **systemd** [Learn more](https://ze-software.net/guides/appliance/) ### Runs Itself *platform* -- `Update` `Systemd` - Binary **self-update** - Built-in **readiness** checks - No **orchestrator** needed [Learn more](https://ze-software.net/features/introspection/) ### Docker Support *platform* -- `Daemon only` `Scratch` `Compose` - **Static binary** on scratch base - **Compose** support included - Optional **build tags** [Learn more](https://ze-software.net/features/) ### Feature Gates *platform* -- `36 subsystems` `Default on` - Compile out **whole subsystems**, BGP included - Smaller binary, smaller **attack surface** - Config **fails closed** on blocks the build lacks [Learn more](https://ze-software.net/guides/quickstart/) ## Experimental and growing. Implemented and tested, still waiting for production evidence. > These still need deployment evidence or hardening before production claims. Configuration may change. ### IPsec VPN *services / Experimental* -- `IKEv2` `X.509` `EAP` - Full **IKEv2** engine, rekeying, DPD - **NAT-T**, keepalive, XFRM interfaces - EAP-MSCHAPv2, **EAP-TLS**, road warrior [Learn more](https://ze-software.net/features/) ### L2TPv2 BNG *services / Experimental* -- `PPP` `RADIUS` `CQM` - RFC 2661 **LNS and LAC** with PPP - **RADIUS** auth, accounting, CoA - CQM monitoring, **shaping**, web UI [Learn more](https://ze-software.net/guides/l2tp/) ### PPPoE Access *services / Experimental* -- `RFC 2516` `PPP` - **Access concentrator** with discovery FSM - Shared **PPP driver** with L2TP - **PAP**, **CHAP-MD5**, and **MS-CHAPv2** authentication [Learn more](https://ze-software.net/guides/pppoe/) ### Interface Management *services / Experimental* -- `Netlink` `DHCP` - Ethernet, VLAN, bridge, **WireGuard** - 8 tunnel kinds, **DHCP** client - NTP sync, **offload** tuning, mirroring [Learn more](https://ze-software.net/features/interfaces/) ### Firewall *services / Experimental* -- `nftables` `NAT` - **15 match** types, 19 actions - SNAT, DNAT, **masquerade** - FlowSpec-to-firewall **bridge** - **DNS-sourced** address groups, TTL-tracked [Learn more](https://ze-software.net/guides/firewall/) ### Policy Routing *services / Experimental* -- `nftables` `PBR` - **L3/L4 match** criteria - Table steering, **next-hop** actions - TCP-MSS clamping, **interface** wildcards [Learn more](https://ze-software.net/guides/policy-routing/) ### VPP Data Plane *services / Experimental* -- `DPDK` `GoVPP` - **FIB** programming via GoVPP - MPLS **label** operations - Per-interface **Prometheus** metrics [Learn more](https://ze-software.net/guides/vpp/) ### MPLS / LDP / RSVP-TE *routing / Experimental* -- `Labels` `Signaling` - Kernel MPLS FIB, **push/swap/pop** - LDP **discovery** and sessions - RSVP-TE **ERO**, bandwidth admission [Learn more](https://ze-software.net/features/) ### OSPFv2 / OSPFv3 *routing / Experimental* -- `RFC 2328` `RFC 5340` `ECMP` - One **ospf** engine, IPv4 and IPv6 address families - SPF/ABR, **NSSA**, virtual links, NBMA/P2MP - Redistribution, **SR**, BFD, graceful restart [Learn more](https://ze-software.net/guides/ospf/) ### IS-IS *routing / Experimental* -- `ISO 10589` `Dual-stack` - **L1/L2** link-state IGP over Layer 2 - RFC 5304/5310 **authentication**, key chains - Dual-stack **IPv6**, redistributes with BGP [Learn more](https://ze-software.net/guides/isis/) ### VRRP *routing / Experimental* -- `RFC 9568` `RFC 3768` `Virtual MAC` - First-hop **gateway redundancy**, IPv4 and IPv6 - Per-group **virtual-MAC** macvlan for L2 failover - **keepalived** interop, compile-out [Learn more](https://ze-software.net/guides/vrrp/) ### Flow Export *observe / Experimental* -- `sFlow` `NetFlow` `IPFIX` - **sFlow v5**, NetFlow v9, IPFIX - Packet sampling, **conntrack** flows - BGP **next-hop** enrichment [Learn more](https://ze-software.net/guides/flow-export/) ### DDoS and Anomaly Detection *secure / Experimental* -- `DDoS` `Anomaly` `FlowSpec` - **Volumetric** and behavioral detection - Bounded incident **history** with durations - Local and upstream **auto-mitigation** [Learn more](https://ze-software.net/guides/anomaly/) ### ISO and PXE Install *platform / Experimental* -- `PXE` `ISO` - **PXE** bare-metal provisioning - Installer **ISO** media - Local **systemd** install and uninstall [Learn more](https://ze-software.net/guides/ze-install/) ### Kernel Tunables *platform / Experimental* -- `Sysctl` `Profiles` - Three-layer **precedence** - Named **profiles** (DSR, router, hardened) - Originals **restored** on stop [Learn more](https://ze-software.net/features/) ### AS112 Anycast DNS *services / Experimental* -- `AS112` `Anycast` - Authoritative **sink zones** on four fixed anycast addresses (RFC 7534/7535) - Conditional **BGP origination** via healthcheck-gated watchdog - Anycast IPs bound on **lo** automatically, never operator-typed [Learn more](https://ze-software.net/guides/as112/) ### Segment Routing *routing / Experimental* -- `SAFI 73` `SRv6` - **SR-Policy** NLRI (RFC 9830), SAFI 73 - MPLS and **SRv6** binding SID, tunnel encap - **ExaBGP bridge** for SR-Policy migration [Learn more](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/srpolicy) ### Fleet Management *automate / Experimental* -- `Managed config` `TLS hub` - Per-client **configuration** and **pinned** hub certificate - Cached config with **reconnect** and heartbeat - Version hashing and **two-phase** fetch [Learn more](https://ze-software.net/guides/fleet-config/) ### IRR Route Filtering *secure / Experimental* -- `IRR` `as-set` - **Prefix-lists** from IRR data, live in the engine - Sourced from **PeeringDB** and **RADB** - Opt-in per peer, group or **global filter chain** [Learn more](https://ze-software.net/guides/irr-filtering/) ## Specified, not built. Aspirations with written, reviewed specs. Nothing here is usable today. > Every card links to a pending spec in the main repo's `plan/` directory. Captured intent moves from skeleton, to design, to ready, to in progress. A spec is deleted only when the work ships. ### OSPF L3VPN PE-CE *routing / Spec'd* -- `RFC 4576` `RFC 4577` `L3VPN` - PE-CE **DN bit** loop prevention - Domain ID, route type, **VPN route tag** - Blocked on **VRF/MPLS L3VPN** infrastructure [Learn more](https://github.com/ze-software/ze/blob/main/plan/spec-ospf-ext-13-l3vpn-dn-bit.md) ### VRF *routing / Spec'd* -- `VRF` `L3VPN` - VRF as a **first-class** concept - Per-VRF **BGP stacks**, YANG config - Kernel **VRF devices**, table binding [Learn more](https://github.com/ze-software/ze/blob/main/plan/spec-vrf-0-umbrella.md) ### Kernel Lockdown *secure / Spec'd* -- `Lockdown` `Integrity` - Kernel **lockdown** integrity mode - Blocks unsigned **modules**, kexec, /dev/mem - Design **reviewed**, waiting for schedule [Learn more](https://github.com/ze-software/ze/blob/main/plan/spec-kernel-lockdown-hardening.md) ### Cloud-Init Provisioning *platform / Spec'd* -- `Cloud-init` `User-data` - Appliance identity from **cloud metadata** - SSH keys and config via **user-data** - No **pre-baked** seed image needed [Learn more](https://github.com/ze-software/ze/blob/main/plan/spec-install-9-cloud-init.md) --- ### Page: AI-First Design https://ze-software.net/features/ai-first/ # AI-First Design <!-- source: internal/component/mcp/tools.go -- MCP tool dispatch primitives --> <!-- source: cmd/ze/help_ai.go -- ze help ai machine-readable reference --> <!-- source: internal/test/cli/cmd_mcp.go -- MCP test client --> <!-- source: ai/rules/repo-maintenance.md -- Current Discovery Surfaces --> <!-- source: internal/le/inventory/register.go -- inventory command registration --> Ze is built around a single command and discovery surface. Commands, configuration nodes, RPCs, events, and plugin metadata are registered once, then exposed through MCP and the operator interfaces that need them. AI tools can discover the same command catalog, run the same actions, and read structured output while helping an operator use or debug the system. ## Register Once, Expose Everywhere The core design point is broader than AI. A feature added to Ze should avoid separate hand-written glue for every surface. The same registration path can feed the CLI, SSH sessions, the web workbench, REST/gRPC, MCP, generated references, completion, authorization, audit, and diagnostics. ## CLI commands as the API surface Every command available through `ze cli` (interactive or `ze cli -c` for one-shot) is exposed programmatically through MCP, and command output is shared with the API engine where those commands are surfaced. This means: - Features do not become CLI-only by accident - Human and machine interfaces use the same command grammar - New commands become automation surfaces when they register with the command catalog ## Self-Describing Command Reference ``` ze help command # full command catalog, filterable ze help command bgp # filter to BGP-related commands ze help command --json # machine-readable JSON (for tooling, wiki generation) ze help ai # AI-oriented summary with recipes and context ze help ai --json # machine-readable JSON reference ze help ai api # daemon API endpoints (ze-show:*, ze-set:*, ...) ``` Generates a command reference from the live binary. The output is assembled from the plugin registry, YANG schemas, and RPC registrations -- it cannot go stale because it is generated from code, not written by hand. `ze help command` is the human-facing catalog. The native `./le wiki-catalog update file <destination>` action renders the corresponding Markdown from the live registries. ## Structured Diagnostics ``` ze cli -c "validate config <file> | json" ze explain [--json] <diagnostic-code> ze config fix --plan <file> ``` The validation verdict is a record the pipe layer renders, so `| json`, `| yaml` and `| table` are three views of one payload and no command carries a rendering flag of its own. The fix plan prints for a reader. Config validation emits structured diagnostic records with stable codes, source spans, expected/actual facts, and repair metadata. Agents parse JSON diagnostics instead of scraping terminal prose. Each diagnostic carries a stable code (e.g., `config-parse`, `config-yang-type`, `config-listener-conflict`). Use `ze explain <code>` to get an explanation. `ze config fix --plan` reports candidate repairs without editing files. Repair plans carry safety labels (`format-only`, `section-local`, `behavior-preserving`, `requires-human-review`) and stable repair IDs. ## Version-Matched Skills ``` ze skills list ze skills get ze-diagnostics ze skills get ze --full ``` The installed binary serves agent workflow guides matched to its exact version. Skills cover diagnostics, config, commands, and agent edit loops. Agents load only the skill relevant to the current task. ## Development-Time Discovery Feature, tooling, self-check, verification, and test-infrastructure changes must update their discovery path in the same work. The standard path is `ai/rules/repo-maintenance.md` for policy, `ai/INDEX.md` for keyword lookup, `ai/NAVIGATION.md` for task routing, and the relevant native action or docs page for verification and usage. <!-- source: ai/rules/repo-maintenance.md -- Required Discovery Artifacts --> Agents should use the existing inventory and verification surfaces: `./le inventory`, `./le command list`, `./le doc check verify`, `./le docs-to-code update`, and `./le doc wiring`. Commit preparation uses `internal/le/commit.Answer`: agents pass the vetted subject, body, and explicit file list, and the helper creates the session ID, message file, executable user-run script, ignored-path checks, and `git commit -F` flow. <!-- source: internal/le/commit/actions.go -- Answer --> <!-- source: internal/le/doc/check/actions.go -- Actions --> ## MCP Transport The MCP (Model Context Protocol) server wraps the CLI command surface for AI consumption: | Tool | Description | |------|-------------| | `ze_execute` | Run **any** CLI command -- full daemon control | | `ze_reference` | Machine-readable reference for this daemon (commands, endpoints, dispatch keys, plugins, families); same JSON as `ze help ai --json` | | `ze_announce` | Announce routes with typed parameters (origin, next-hop, communities, prefixes) | | `ze_withdraw` | Withdraw routes | | `ze_show_bgp` | BGP peer state, ASN, uptime, and summary views (auto-generated from `show bgp ...`) | | `ze_request_peer` | Peer lifecycle: teardown, pause, resume, flush (auto-generated from `request peer ...`) | Additional tools are auto-generated from the command registry. Every YANG command and plugin command becomes a typed MCP tool automatically, so an AI agent can discover features, execute the same commands as an operator, and inspect structured outputs while debugging. The `ze_execute` tool is the bridge: anything a human can type, an AI can execute. Route management, RIB queries, peer lifecycle, configuration changes, event subscription, schema discovery, doctor output, warnings, and health checks all go through one interface. Start with `ze start --mcp <port>` or configure via YANG (`environment/mcp`). ## What Makes This Different Other software adds an API endpoint and expects operators to build separate wrappers, UI, documentation, and automation around it. Ze exposes its command surface through a self-describing interface that tools can discover at runtime. `ze help command` lists every command with its description. `ze help ai` adds context (recipes, families, update syntax). MCP tools have typed parameters. The command list is queryable at runtime (`show command list`, `show command help <name>`). The useful property is the shared surface: when a plugin or subsystem registers commands, YANG, RPCs, or events, the same metadata can feed CLI, web, generated references, automation, authorization, audit, and diagnostics. See [MCP Guide](../../guides/mcp/overview/index.md) for configuration and [MCP Remote Access](../../guides/mcp/remote-access/index.md) for tunneling. --- ### Page: API Commands https://ze-software.net/features/api-commands/ # API Commands Commands sent through `ze cli`, `ze cli -c`, `ze show`, or process stdin. ### Peer Management | Command | Description | |---------|-------------| | `show bgp peer list` | List peers (brief) | | `show bgp peer <sel> detail` | Show peer details and statistics | | `request peer <addr> teardown <code>` | Graceful session closure with NOTIFICATION | | `create bgp peer <addr> asn <asn>` | Add a peer to the running daemon | | `delete bgp peer <name>` | Remove peer | | `request peer <addr> pause` | Pause reading from peer (flow control) | | `request peer <addr> resume` | Resume reading from peer | | `show bgp peer <addr> capabilities` | Show negotiated capabilities | | `show bgp` | BGP summary table with statistics | Peer selector supports: `*` (all), exact IP, peer name, ASN (`as65001`), glob patterns (`192.168.*.*`), exclusion (`!addr`, `!as65001`). Tab completion for peer selectors in `ze show` and `ze cli` when daemon is running. <!-- source: internal/component/bgp/plugins/cmd/peer/peer.go -- peer management RPC handlers --> ### Route Updates | Command | Description | |---------|-------------| | `send bgp * update text <attrs> nlri <family> <op> <prefix>` | Text-format UPDATE | | `send bgp * update hex <hex>` | Hex-format UPDATE | Text attribute syntax: `origin set igp`, `nhop set 1.1.1.1`, `local-preference set 100`, `med set 50`, `as-path set [65000 65001]`, `community set [no-export]`, `large-community set [65000:1:1]`. NLRI operations: `add`, `del`, `eor` per address family. <!-- source: internal/component/bgp/plugins/cmd/update/update_text_test.go -- text update parsing --> <!-- source: internal/core/bgp/attribute/builder_parse.go -- text attribute parsing --> ### RIB Operations | Command | Description | |---------|-------------| | `show bgp rib received [peer] [family]` | Show Adj-RIB-In | | `show bgp rib sent [peer] [family]` | Show Adj-RIB-Out | | `clear bgp rib in [peer] [family]` | Clear Adj-RIB-In | | `clear bgp rib out [peer] [family]` | Clear Adj-RIB-Out | | `request bgp rib inject <peer> <family> <prefix> [attrs...]` | Insert route into Adj-RIB-In (no live session needed) | | `request bgp rib withdraw <peer> <family> <prefix>` | Remove route from Adj-RIB-In | Inject attributes: `origin <igp|egp|incomplete>`, `nhop|nexthop <ip>`, `aspath <asn,asn,...>`, `localpref <n>`, `med <n>`. Peer address is a label (valid IP, no session required). Only simple prefix families (IPv4/IPv6 unicast/multicast). <!-- source: internal/component/bgp/plugins/rib/rib_commands.go -- injectRoute, withdrawRoute --> <!-- source: internal/component/bgp/plugins/cmd/rib/ -- RIB command handlers --> ### Cache Management | Command | Description | |---------|-------------| | `show cache` | List cached messages | | `request cache retain` | Retain message in cache | | `request cache release` | Release from cache | | `request cache expire` | Set cache expiration | | `send bgp <sel> cached <id>` | Forward cached message to peer(s) | ### Event Subscription | Command | Description | |---------|-------------| | `request subscribe <filter>` | Subscribe to BGP events | | `request unsubscribe <filter>` | Unsubscribe from events | ### Commit Workflow Named update windows for atomic route changes: | Command | Description | |---------|-------------| | `request commit start <name>` | Begin named update window | | `request commit end <name>` | End window and send updates | | `request commit eor <name>` | Send End-of-RIB for window | | `request commit rollback <name>` | Discard changes | | `request commit show <name>` | Show commit status | | `request commit withdraw <name>` | Withdraw all routes in window | | `request commit list` | List named commits | <!-- source: internal/component/bgp/plugins/cmd/commit/commit.go -- commit workflow handlers --> ### Raw & Introspection | Command | Description | |---------|-------------| | `send bgp * raw hex <data>` | Send raw BGP message bytes | | `route-refresh <family>` | Send route refresh request | | `help` | Show available commands | | `command-list` | List all commands with descriptions | | `command-help <name>` | Detailed help for command | <!-- source: internal/component/bgp/plugins/cmd/raw/ -- raw BGP message handler --> --- ### Page: BGP Protocol https://ze-software.net/features/bgp-protocol/ # BGP Protocol ### Address Families | Family | Config Name | AFI/SAFI | Encode | Decode | Route Config | |--------|-------------|----------|--------|--------|--------------| | IPv4 Unicast | `ipv4/unicast` | 1/1 | Yes | Yes | Yes | | IPv6 Unicast | `ipv6/unicast` | 2/1 | Yes | Yes | Yes | | IPv4 Multicast | `ipv4/multicast` | 1/2 | Yes | Yes | Yes | | IPv6 Multicast | `ipv6/multicast` | 2/2 | Yes | Yes | Yes | | IPv4 VPN | `ipv4/mpls-vpn` | 1/128 | Yes | Yes | Yes | | IPv6 VPN | `ipv6/mpls-vpn` | 2/128 | Yes | Yes | Yes | | IPv4 FlowSpec | `ipv4/flow` | 1/133 | Yes | Yes | Yes | | IPv6 FlowSpec | `ipv6/flow` | 2/133 | Yes | Yes | Yes | | IPv4 FlowSpec VPN | `ipv4/flow-vpn` | 1/134 | Yes | Yes | Yes | | IPv6 FlowSpec VPN | `ipv6/flow-vpn` | 2/134 | Yes | Yes | Yes | | IPv4 MPLS Label | `ipv4/mpls-label` | 1/4 | Yes | Yes | Yes | | IPv6 MPLS Label | `ipv6/mpls-label` | 2/4 | Yes | Yes | Yes | | L2VPN EVPN | `l2vpn/evpn` | 25/70 | Yes | Yes | Yes | | L2VPN VPLS | `l2vpn/vpls` | 25/65 | Yes | Yes | Yes | | BGP-LS | `bgp-ls/bgp-ls` | 16388/71 | No | Yes | No | | BGP-LS VPN | `bgp-ls/bgp-ls-vpn` | 16388/72 | No | Yes | No | | IPv4 MVPN | `ipv4/mvpn` | 1/5 | Yes | Yes | Partial | | IPv6 MVPN | `ipv6/mvpn` | 2/5 | Yes | Yes | Partial | | IPv4 RTC | `ipv4/rtc` | 1/132 | No | Yes | No | | IPv4 MUP | `ipv4/mup` | 1/85 | Yes | Yes | Yes | | IPv6 MUP | `ipv6/mup` | 2/85 | Yes | Yes | Yes | | IPv4 SR-Policy | `ipv4/sr-policy` | 1/73 | Yes | Yes | Yes | | IPv6 SR-Policy | `ipv6/sr-policy` | 2/73 | Yes | Yes | Yes | <!-- source: internal/component/bgp/plugins/nlri/evpn/register.go -- EVPN family registration --> <!-- source: internal/component/bgp/plugins/nlri/srpolicy/register.go -- SR-Policy family registration --> <!-- source: internal/component/bgp/plugins/nlri/flowspec/register.go -- FlowSpec family registration --> <!-- source: internal/component/bgp/plugins/nlri/vpn/register.go -- VPN family registration --> <!-- source: internal/component/bgp/plugins/nlri/mup/register.go -- MUP family registration --> <!-- source: internal/component/bgp/plugins/nlri/ls/register.go -- BGP-LS family registration --> <!-- source: internal/component/bgp/plugins/nlri/labeled/register.go -- MPLS label family registration --> <!-- source: internal/component/bgp/plugins/nlri/vpls/register.go -- VPLS family registration --> <!-- source: internal/component/bgp/plugins/nlri/mvpn/register.go -- MVPN family registration --> <!-- source: internal/component/bgp/plugins/nlri/rtc/register.go -- RTC family registration --> ### Capabilities | Capability | Code | RFC | Description | |------------|------|-----|-------------| | Multiprotocol Extensions | 1 | RFC 4760 | Multi-protocol BGP (AFI/SAFI negotiation) | | 4-byte ASN | 65 | RFC 6793 | 32-bit AS numbers | | Route Refresh | 2 | RFC 2918 | Request full route re-advertisement | | Enhanced Route Refresh | 70 | RFC 7313 | Bounded clear and re-send | | ADD-PATH | 69 | RFC 7911 | Multiple paths per prefix | | Extended Message | 6 | RFC 8654 | 65535-byte messages | | Extended Next Hop | 5 | RFC 8950 | IPv6 next-hop for IPv4 NLRI | | Graceful Restart | 64 | RFC 4724 | Session preservation across restarts (Restarting Speaker: R-bit via zefs marker on `ze signal restart`) | | Long-Lived GR | 71 | RFC 9494 | Extended stale route retention with LLGR_STALE community and depreference | | BGP Role | 9 | RFC 9234 | Peer relationship role | | Hostname | 73 | RFC 8516 | FQDN capability | | Software Version | 75 | draft | Software version advertisement | | Link-Local Next Hop | 77 | RFC 2545 + draft | IPv6 link-local as next-hop | | PATHS-LIMIT | 76 | draft-abraitis-idr-addpath-paths-limit | Per-family path count limit for ADD-PATH | <!-- source: internal/core/bgp/capability/capability.go -- capability code constants --> <!-- source: internal/core/bgp/capability/encoding.go -- EncodingCaps fields ASN4, ExtendedMessage, AddPathMode, ExtendedNextHop, PathsLimitSend, PathsLimitRecv --> <!-- source: internal/core/bgp/capability/session.go -- SessionCaps fields RouteRefresh, EnhancedRouteRefresh, GracefulRestart --> <!-- source: internal/component/bgp/plugins/role/register.go -- BGP Role capability plugin --> <!-- source: internal/component/bgp/plugins/hostname/register.go -- Hostname capability plugin --> <!-- source: internal/component/bgp/plugins/softver/register.go -- Software Version capability plugin --> <!-- source: internal/component/bgp/plugins/llnh/register.go -- Link-Local NH capability plugin --> ### AS number notation An AS number is one 4-octet number on the wire (RFC 6793). RFC 5396 Section 2 names three ways to write that number in text. Ze READS all three wherever an AS number is configured or typed at a command. That includes a route distinguisher in every form Ze parses one, and the administrator of an extended community such as `target:1.10:5`. It also includes the `as-path` of `show bgp encode route`, the MVPN `source-as`, the `show bgp rib path` filter, the `AS<n>` peer selector, and `rpki validate`. The `ze-analyze` binary reads them on `--peer-asn` and `--local-as`. One reader answers each of those forms. `selector.ParseASNSelector` is the `AS<n>` selector, for every peer command and for the policy filter. `attribute.ParseExtCommunityAdmin` is the extended-community administrator, for all four of its parsers. A private copy of either is what `./le repository check` now reports. The `L` suffix of an extended community forces the four-octet encoding, and it is orthogonal to the spelling: `65000L`, `0.100L` and `1.10L` each say it. For a value of more than 65535 the value forces that encoding by itself, so the suffix is redundant there and is accepted. AS 65546 is written `65546` in asplain, and `1.10` in both asdot and asdot+. Each field of the dotted form is a 16-bit value, so `0.65546` is not a spelling of it. The tree stores the decimal form, so the notation an operator types changes nothing downstream. These configured fields hold an AS number and still read decimal only: - the AS half of a standard community. RFC 1997 page 3 encodes it in the first two octets. Two octets hold no four-byte AS number. The only dotted spelling that fits is `0.Y`, and that names the AS number `Y` already names. - the Global Administrator of a large community. RFC 8092 Section 5 pins the canonical representation to three decimal integers. - the 2-octet administrator of a route origin (`origin:ASN:IP`) and of a filter's extended-community match. Two octets again, as above. `bgp { as-notation }` selects the notation Ze WRITES. `asplain` is the default, and RFC 5396 Section 3 recommends it. `asdot` writes an AS number of 65536 or more as `X.Y`, and leaves a lower one as a decimal integer. `asdot+` writes every AS number as `X.Y`. The leaf reaches every surface an operator reads an AS number on: - the route rows of `show bgp rib` - the peer rows of `show bgp` and `show bgp peer detail` - the ROA and ASPA rows of `show bgp rpki` - the `show bgp irr` rows - the CLI dashboard - the looking glass tables and topology graph - the BGP pages of the web interface Under a dotted notation those AS numbers are JSON STRINGS rather than JSON numbers, because `1.10` is not a JSON number. Every Ze reader of such a payload accepts both forms (`asn.Number`, `asn.FromJSON`), so a client renders what the producer rendered. Five surfaces stay asplain whatever the leaf says, because their text is a contract rather than a display: - the AS path of the plugin event stream - the `update text` command Ze replays to itself - the filter text a filter plugin matches - the text form of the plugin process protocol - the `as_path` of the birdwatcher-compatible looking glass API A dotted AS number there would stop a configured filter matching, or fail a client that parses an integer. The ExaBGP bridge writes ExaBGP's own text format, and that program fixes the spelling. <!-- source: internal/core/bgp/asn/asn.go -- Parse, Append, Number, Configured --> <!-- source: internal/core/bgp/attribute/text.go -- ParseASPathText, ParseCommunity, ParseLargeCommunity --> <!-- source: internal/component/bgp/config/routeattr.go -- ParseRouteDistinguisher, ParseASPath, ParseAggregator --> <!-- source: internal/core/bgp/nlri/rd.go -- ParseRDString, the RD reader every command word reaches --> <!-- source: internal/core/bgp/attribute/text.go -- ParseExtCommunityAdmin, the one extended-community administrator reader --> <!-- source: internal/component/bgp/yang/ze-bgp-conf.yang -- as-notation leaf --> <!-- source: internal/component/bgp/plugins/rib/rib_attr_format.go -- asPathList --> <!-- source: internal/core/bgp/attribute/text_append.go -- AppendText, which stays asplain --> ### Multipath installation When several BGP candidates tie under multipath selection, Ze carries the winner and its equal-cost sibling next hops into the shared Loc-RIB. The system RIB then emits one ECMP group to the active FIB backend. Membership-only changes, such as one equal-cost peer disappearing, update the installed group without requiring the winning peer to change. `show rib` reports the primary `next-hop` and the additional `ecmp-paths`. Together they are the complete installed next-hop set. <!-- source: internal/component/bgp/plugins/rib/rib_bestchange.go -- SelectMultipath, mirrorToLocRIB --> <!-- source: internal/core/rib/locrib/candidate.go -- Path.ECMP --> <!-- source: internal/core/rib/locrib/manager.go -- siblingNextHops --> ### Startup convergence hold (`update-delay`) `bgp update-delay max-delay <seconds>` holds the first advertisement of a speaker that has just started, so a neighbor receives one settled route set instead of an initial set followed by corrections. The optional `establish-wait <seconds>` ends the hold early, on the peers that have reached Established by then. It must not be more than `max-delay`, and Ze refuses a configuration where it is, at `ze config validate` as well as at startup. While Ze holds, sessions negotiate, reach Established and receive UPDATE messages as usual. Ze sends no UPDATE and no End-of-RIB marker, because the initial routing update has not completed and RFC 4724 Section 4.1 makes the marker a claim that it has. The hold ends on the first of three conditions: every configured peer sends its own End-of-RIB marker, `establish-wait` expires with one or more peers established, or `max-delay` expires. A peer that never comes up cannot extend the wait past `max-delay`. Ze waits for the marker rather than for Established, because Established says only that the neighbor answered. RFC 4724 Section 4.1 excludes two kinds of peer from that wait by name, and Ze excludes the same two: a peer that advertised no graceful-restart capability, which has promised no marker, and a peer whose capability carries the Restart State bit, which is deferring its own initial update. A dynamic-group member is held like every other peer and is never counted toward the release. `show bgp update-delay` reports the hold: whether it is running, which condition ended it, and how many expected peers have converged. The hold applies at startup alone. A reload validates a changed `update-delay` and Ze keeps the hold it already armed, or none: re-arming would withhold routes from a neighbor that already has them. An absent `max-delay`, or `max-delay 0`, is the default and holds nothing. <!-- source: internal/component/bgp/reactor/update_delay.go -- updateDelayHold, Peer.startInitialRoutes --> <!-- source: internal/component/bgp/config/update_delay.go -- ParseUpdateDelay --> ### Long-Lived Graceful Restart readvertisement RFC 9494 stale-route handling is applied per destination on both normal forwarding and RIB readvertisement. An LLGR-capable peer receives the stale route unchanged. A non-LLGR eBGP peer receives a withdrawal. A non-LLGR iBGP peer receives the route with `NO_EXPORT` and `LOCAL_PREF=0`, allowing partial deployments to retain reachability without preferring stale information. Only the LLGR filter runs during stale readvertisement. Other export policy has already run on the original announcement and is not applied twice. <!-- source: internal/component/bgp/plugins/gr/gr_egress.go -- LLGREgressFilter --> <!-- source: internal/component/bgp/reactor/reactor_api_batch.go -- sendStaleReadvertise --> ### Egress attribute rules Several rails write an UPDATE: the announce rails that encode a route Ze produced, the forward rail that relays another speaker's UPDATE, and the route-server rail. Each rule below is answered at one site that every rail asks, rather than re-derived per rail. The rails that re-derived a rule disagreed about it: the announce rail stripped LOCAL_PREF toward an external peer and the forward rail did not. | Rule | RFC | Behavior | |------|-----|----------| | LOCAL_PREF off an external session | 4271 Section 5.1.5 | Removed from every UPDATE toward an external peer, relayed UPDATEs included. Recorded after the export filter pass, so a filter that sets LOCAL_PREF does not override the prohibition. A session is internal when `LocalAS == PeerAS`. Ze names no confederation member-AS, so the RFC 3065 exception is never active. | | A received MULTI_EXIT_DISC is not relayed | 4271 Section 5.1.4 | Removed when the value about to be written is the value that arrived. A metric an announce rail or an export filter sets is kept, because that is Ze originating one. A route-server client keeps the received metric (RFC 7947 Section 2.2.3). | | Configured MULTI_EXIT_DISC removal | 4271 Section 5.1.4 | `bgp/policy/modify/<name>/del/med`, on an import chain only. An export chain refuses the directive and logs why (Section 9.1.2.2). | | No peer receives its own address as next hop | 4271 Section 5.1.3 | Asked of the built body, after the export chain, for a relayed route and for an originated one. The route is withheld from that peer and logged; it is never rewritten, which keeps RFC 7947 Section 2.2.2 transparency. Withdrawals in the same UPDATE still reach the peer. | | A route naming this speaker is not installed | 4271 Section 5.1.3 | Excluded from best-path candidacy rather than refused at install, so a sound alternative wins. "Itself" is the set of session-local addresses. | | Partial bit on an unrecognized transitive optional attribute | 4271 Sections 5 and 9 | Stamped at ingest. Recognition comes from Ze's own attribute registry, so removing a plugin makes that plugin's attribute unrecognized again. Session-reset and treat-as-withdraw skip the stamp, since they propagate nothing. | | Withdrawals before announces | 4271 Section 4.3 | All withdrawals run before all announces, in the legacy sections and the multiprotocol ones, so one UPDATE naming a prefix in both leaves it reachable. | | A relayed withdrawal carries no path attributes | 4271 Sections 4.3 and 6.3 | An UPDATE advertising no reachable NLRI gets no attribute created on it: no next-hop rewrite, no RFC 4456 reflection stamp, no community tag, no policy delta, no AS_PATH prepend. Rewriting an attribute the source already carries stays allowed, which keeps the RFC 6793 Section 4.2.2 width transcode. | | One attribute order for every builder | 4271 Section 5 | Path attributes are inserted by type code in ascending order on every rail. MP_UNREACH_NLRI stays first, out of type-code order, so a withdrawal precedes an announcement in one message. | | The next-hop wire form matches its length octet | 4760, 2545 Section 3 | The MP_REACH Next Hop length and the bytes written derive from one value. The IPv6 link-local address is appended after the global one only when this speaker shares a locally connected subnet with the peer and with the entity the global next hop names. The `link-local` leaf supplies the address; it does not decide that the address is sent. | | A modification that cannot be applied suppresses the route | none | The route is withheld from that destination rather than forwarded unmodified. Counted on `ze_bgp_update_modify_failed_total{reason}`. | <!-- source: internal/component/bgp/reactor/forward_local_pref.go -- localPrefAllowedTo, applyFactsLocalPref --> <!-- source: internal/component/bgp/reactor/forward_med.go -- medPropagationAllowedTo, applyFactsMED --> <!-- source: internal/component/bgp/reactor/forward_next_hop.go -- egressNextHopIsPeerOwn, originatedNextHopIsPeerOwn --> <!-- source: internal/component/bgp/plugins/rib/rib_self_nexthop.go -- refreshSelfNextHopsLocked, isSelfNextHop --> <!-- source: internal/component/bgp/reactor/forward_build.go -- planAttr, buildModifiedPayload --> <!-- source: internal/component/bgp/reactor/forward_modify_failure.go -- modifyFailure --> <!-- source: internal/component/bgp/plugins/filter_modify/filter_modify.go -- appendMEDRemove --> ### Unrecognized NLRI types in a typed family RFC 7606 Section 5.4 requires a speaker advertising a typed address family to discard routes carrying an NLRI type it does not implement, "unless the relevant specification for that address family specifies otherwise". The ruling is registered per family by the plugin that owns the family, so compiling that plugin out removes both the advertisement and the obligation. | Family | Ruling | |--------|--------| | `l2vpn/evpn` | Discard an unrecognized route type | | `ipv4/mvpn`, `ipv6/mvpn` | Discard an unrecognized route type (RFC 6514 Section 4 framing) | | `ipv4/mup`, `ipv6/mup` | Discard an unrecognized route type (draft-ietf-bess-mup-safi Section 3.1 framing) | | `bgp-ls/bgp-ls`, `bgp-ls/bgp-ls-vpn` | No discard. RFC 9552 Section 5.2 requires an unknown Link-State NLRI type to be preserved and propagated | | Every other family | No discard. A family nobody has ruled on discards nothing | The recognizer answers about the route TYPE only. A well-typed but malformed NLRI is a Section 5.3 concern, handled before this. When the length fields do not agree with the section the NLRI boundaries are unknowable, so no discard decision is made rather than one being guessed. <!-- source: internal/core/bgp/nlri/nlritype/nlritype.go -- Register, Retain --> <!-- source: internal/component/bgp/plugins/nlri/evpn/register.go -- EVPN recognizer --> <!-- source: internal/component/bgp/plugins/nlri/mvpn/register.go -- MCAST-VPN recognizer --> <!-- source: internal/component/bgp/plugins/nlri/mup/register.go -- MUP recognizer --> ### RFC 7911 Path Identifier regeneration RFC 7911 Section 2 requires a speaker that re-advertises a route to generate its own Path Identifier, assigned so that (prefix, identifier) uniquely names a path advertised to a neighbor. Both forward rails do that instead of relaying the identifier the source chose. The key is the path at ingress, and how much of the path it holds follows what the source framed. A source that negotiated no ADD-PATH names a path by its prefix alone and sends every one of them under identifier 0, so ze holds one identifier for that source's whole session. A source that negotiated ADD-PATH names a path by (prefix, identifier), so ze holds one entry per family, received identifier and prefix. A withdrawn route carries no path attributes, so an attribute-derived identifier could not be recomputed when the path leaves; the ingress key can. The same key makes a re-announcement with changed attributes replace rather than duplicate. An identifier of the first kind is released only when the peer is removed. One of the second kind is released when ze has relayed that pair's withdraw, at the point the recent-update cache evicts the UPDATE that carried it. Neither is released at session down, so a reconnecting peer re-announces under the identifiers its destinations already hold. Zero is minted and accepted like any other value (RFC 7911 Section 3). Regeneration runs whenever either side of the forward frames identifiers. A session where neither side negotiated ADD-PATH keeps its zero-copy forward. <!-- source: internal/component/bgp/reactor/forward_path_id.go -- fwdPathIDs, fwdPathIDTable.generatePath, fwdReleaseWithdrawnPathIDs --> ### Protocol event capture and replay A peer writes every message it receives to a bounded JSONL file, together with the config operations applied while the capture runs. `ze-test replay <file>` feeds the file back through the same read path with an injected clock. The tee sits on the complete wire message in both read paths, before message processing. RFC 7606 enforcement rewrites attributes and synthesizes withdrawals, and the family and prefix-limit checks can return before anything is dispatched, so the plugin message-observer hook cannot see what a bug capture needs. Off by default. The tee costs one nil comparison and no allocation per received message. Config payloads pass a redactor, and TCP-MD5 keys never appear on the wire, so the file holds routing data and no local secret. `ze doctor` reports whether an enabled peer's capture directory is usable (`doctor-bgp-capture-directory`). ```bash ze-test replay [--json] [--local-as N] [--peer-as N] [--router-id N] <capture-file|-> ``` <!-- source: internal/core/capture/capture.go -- the bounded JSONL writer --> <!-- source: internal/component/bgp/reactor/capture_replay.go -- the session tee and the replay driver --> <!-- source: internal/test/cli/cmd_replay.go -- cmdReplay --> <!-- source: internal/component/doctor/checks_bgp_capture.go -- capture directory readiness --> ### Path Attributes | Attribute | Code | JSON Key | Description | |-----------|------|----------|-------------| | ORIGIN | 1 | `origin` | igp / egp / incomplete | | AS_PATH | 2 | `as-path` | AS path segments | | NEXT_HOP | 3 | `next-hop` | Next hop IP address | | MED | 4 | `med` | Multi-Exit Discriminator | | LOCAL_PREF | 5 | `local-preference` | Local preference | | ATOMIC_AGGREGATE | 6 | `atomic-aggregate` | Atomic aggregate flag | | AGGREGATOR | 7 | `aggregator` | Aggregator ASN:IP | | COMMUNITY | 8 | `community` | Standard communities | | ORIGINATOR_ID | 9 | `originator-id` | Route reflector originator | | CLUSTER_LIST | 10 | `cluster-list` | Route reflector cluster list | | MP_REACH_NLRI | 14 | none | Multiprotocol reachable NLRI | | MP_UNREACH_NLRI | 15 | none | Multiprotocol unreachable NLRI | | EXTENDED_COMMUNITY | 16 | `extended-community` | Extended communities | | LARGE_COMMUNITY | 32 | `large-community` | Large communities (RFC 8092) | | PREFIX_SID | 40 | `prefix-sid` | Segment Routing prefix SID | <!-- source: internal/core/bgp/attribute/attribute.go -- attribute code constants --> <!-- source: internal/core/bgp/attribute/origin.go -- ORIGIN --> <!-- source: internal/core/bgp/attribute/aspath.go -- AS_PATH --> <!-- source: internal/core/bgp/attribute/community.go -- Communities, ExtendedCommunities, LargeCommunities --> --- ### Page: DNS Resolver https://ze-software.net/features/dns-resolver/ # DNS Resolver <!-- source: internal/component/resolve/dns/resolver.go -- miekg/dns resolver with cache --> <!-- source: internal/component/resolve/dns/cache.go -- O(1) LRU cache with TTL --> <!-- source: internal/component/config/system/yang/ze-system-conf.yang -- system DNS config --> Built-in DNS resolver component providing cached DNS queries to all Ze components. Uses `github.com/miekg/dns` (the library CoreDNS is built on). This is the client side: Ze asking somebody else. Ze also answers queries, from two authoritative plugins that share one server harness (`internal/core/dnsserver`): `as112` (see [AS112 guide](../../guides/as112/index.md#answer-policy) for the answer policy the harness enforces) and `geodns` (per-source-IP answers, `service { geodns { ... } }`). The two sides share no configuration and no cache. | Feature | Description | |---------|-------------| | Static name servers | `system { name-server [8.8.8.8 1.1.1.1]; }` sets upstream DNS servers | | resolv.conf writer | Writes configured servers to resolv-conf-path at startup | | DHCP integration | Static servers take priority over DHCP-discovered DNS | | Query types | A, AAAA, TXT, PTR, CNAME, MX, NS, SRV | | LRU cache | O(1) operations, configurable size and max TTL | | TTL-aware | Respects response TTL, caps at configured maximum, honors TTL=0 (do not cache) | | Concurrent safe | Mutex-protected cache, safe for multi-goroutine use | | System fallback | No configured servers uses `/etc/resolv.conf`; if that is missing or empty, queries fail closed with `no DNS server configured` | | Timeout control | Per-resolver configurable timeout (1-60 seconds) | | Cache management | List entries, inspect by name, selective delete by name/type, flush all, reset counters | | `\| resolve` pipe | Reverse DNS enrichment for IP addresses in any command's JSON output | | `\| origin` pipe | ASN/network enrichment for IP addresses via Team Cymru DNS queries | ## Configuration ``` system { name-server [8.8.8.8 1.1.1.1] dns { resolv-conf-path /tmp/resolv.conf timeout 5 cache-size 10000 cache-ttl 86400 } } ``` | Option | Default | Description | |--------|---------|-------------| | `name-server` | (none) | Static DNS servers. First server used by ze internal resolver. All written to resolv.conf. | | `resolv-conf-path` | `/tmp/resolv.conf` | Path for resolv.conf. Default suits gokrazy (read-only rootfs). Empty disables writing. | | `timeout` | 5 | Query timeout in seconds (1-60) | | `cache-size` | 10000 | Maximum cached entries (0 disables caching) | | `cache-ttl` | 86400 | Maximum cache TTL in seconds (0 uses response TTL only) | ## DHCP interaction When `name-server` is configured, DHCP-discovered DNS servers do not overwrite resolv.conf. When no static servers are configured, DHCP writes DNS servers to resolv-conf-path as before (last-writer-wins across interfaces). ## Cache management The DNS cache can be inspected and managed at runtime via CLI commands. **Inspection:** ``` show dns cache stats # Hit/miss/eviction counters + rates show dns cache list # All non-expired entries (sorted by TTL) show dns cache record example.com # Entries for a specific name ``` **Clearing:** ``` clear dns cache # Flush all entries and reset counters clear dns cache stats # Zero counters without removing entries clear dns cache record example.com # Delete entries for a name (all types) clear dns cache record example.com type AAAA # Delete a single name+type entry ``` ## Pipe operators Two pipe operators enrich JSON output from any command with DNS-based lookups: | Pipe | Description | |------|-------------| | `\| resolve` | Adds a `<key>-name` field with the PTR (reverse DNS) hostname for each IP address value in the JSON output. Uses the system DNS resolver with cache. 500ms timeout per lookup. | | `\| origin` | Adds `<key>-asn`, `<key>-as-name`, and `<key>-prefix` fields for each IP address value via Team Cymru DNS queries. 2s timeout. | Both pipes walk JSON values, detect IP addresses, and add sibling fields. They work on any command output, including `show traceroute`, `show bgp`, etc. In `monitor traceroute | log` mode, they enrich the hop legend. ## Reload behavior DNS resolver settings and resolv.conf are applied at startup. Changing `name-server` or `dns` settings via config reload requires a process restart to take effect. --- ### Page: ExaBGP Compatibility https://ze-software.net/features/exabgp-compatibility/ # ExaBGP Compatibility <!-- source: internal/plugins/exabgp/main.go -- ze exabgp subcommands --> <!-- source: internal/plugins/exabgp/main_sdk.go -- SDK/TLS connect-back mode --> <!-- source: internal/exabgp/migration/migrate.go -- ExaBGP config migration --> - Automatic detection and migration of ExaBGP configuration files - `ze exabgp plugin` runs ExaBGP processes with ze as the BGP engine - Two modes: standalone (stdin/stdout) for development, TLS connect-back when launched by engine - Bidirectional translation: ze JSON events to ExaBGP JSON, ExaBGP text commands to ze commands - Forward-barrier flush injected after route commands for ordering guarantees - `ze exabgp migrate` converts ExaBGP configs to ze format - `ze exabgp migrate --env` converts ExaBGP INI environment files to ze config --- ### Page: Fleet Management https://ze-software.net/features/fleet-management/ # Fleet Management Ze supports centralized configuration for multi-node deployments. A central hub serves configuration to remote ze instances over TLS. - Named hub blocks: `server <name> { ip; port; secret; }` for listeners, `client <name> { host; port; secret; ca; }` for outbound - Per-client secrets: each managed client authenticates with its own token - Hub authentication by issuer: `ca <name>` names a `pki ca` entry holding the hub's certificate authority root, which the operator exports from the hub with `show pki local-ca pem`. The client validates the hub's chain against that root and against nothing else, so a hub that reissues its certificate stays reachable with no client change. A name that resolves to nothing is an error, never a fall-through to another anchor - Config fetch with version hashing: clients only download when config changes - Two-phase config change: hub notifies, client fetches when ready - Partition resilience: clients cache config locally and start from cache when hub is unreachable - Exponential backoff reconnect with jitter (1s to 60s cap) - Heartbeat liveness detection (30s interval, 90s timeout) - CLI overrides: `--server`, `--name`, `--token` flags for troubleshooting - Managed mode toggle: `meta/instance/managed` blob flag controls hub connection <!-- source: internal/component/managed/client.go -- RunManagedClient lifecycle --> <!-- source: internal/component/managed/tls.go -- clientTLSConfig, the three trust anchors in order --> <!-- source: internal/component/plugin/server/managed.go -- hub-side config handlers --> <!-- source: internal/component/plugin/server/managed_serve.go -- managedCertificate, the leaf the hub serves --> <!-- source: pkg/fleet/ -- version hash and RPC envelope types --> --- ### Page: Output Formatting https://ze-software.net/features/formatting/ # Output Formatting A command that answers with DATA sends that answer through the pipe pipeline, whether you're poking around interactively or scripting against ze. One operator set, three ways to use it: set a persistent default, pipe it inline, or apply it offline to already-captured output. Which operators a given command owes is a property of that command, not of the language: `ze help command --json` publishes the list per command, and an operator the command's answer cannot support is refused by name rather than answered wrongly. A command that only ever prints text, with no handler on any surface answering with data, reaches no pipe layer at all and publishes no operator: `show data cat` returns the bytes of one stored file, and wrapping them in a record would corrupt its one use. <!-- source: cmd/ze/help_command.go -- operatorsFor, pathHasOnlyPlainLocalHandler --> <!-- source: internal/component/command/pipe.go -- validateDeclaredShape --> ### The simple way: set a default once `set cli format <name>` in the interactive CLI picks a default so every command already displays that way, with no piping needed at all: ``` set cli format table set cli format # shows the current default ``` | Format | Description | |--------|-------------| | `text` | Space-aligned columns, no box-drawing (default) | | `table` | Box-drawing table | | `json` | Pretty-printed JSON | | `yaml` | YAML output | | `ndjson` | One compact JSON object per line | The choice persists for the session via the `ze.cli.format` setting; it can also be set permanently through YANG config. <!-- source: internal/component/cli/model_keys.go -- handleSetCLIFormat, validCLIFormats --> <!-- source: internal/component/command/pipe.go -- ze.cli.format env registration, configuredDefault --> ### Piping inline Append `| <operator>` to a command that answers with data, shell-like: ``` show bgp peer list | table show bgp peer list | json compact show bgp rib | match established show bgp peer list | first 5 ``` A chain takes one format operator: `json`, `ndjson`, `table`, `text`, `yaml` or `raw`. Two together are rejected. Filter and display operators chain freely. The complete, current set is generated from the operator catalog: [`pipe-operators.generated.md`](https://github.com/ze-software/ze/blob/main/docs/features/pipe-operators.generated.md). It is the list to build a tool against, and `./le doc check verify` fails when it and the product disagree. That page carries every operator's name, class, argument, repetition and description, so this page does not list them again. A second copy is how five surfaces came to publish five different sets. Three of its columns are worth reading before you build against it: - **Class** says what the operator acts on. `acts on any answer` is owed by every command that reaches the pipe layer. `acts on rows` is owed only where the answer has rows, and is refused by name where it does not. `acts on a stream of updates` means something only while a command keeps answering. - **Surface** says where the operator runs. `save` is `local process only`, because a chain the daemon expands would write on the daemon's filesystem. - **Repeated** says what a second occurrence in one chain means: `applies again, in order` composes, `no effect` is idempotent, and `refused` is refused by name rather than silently answering the last one. `resolve` and `origin` decorate a field the command DECLARES to hold an IP address, and are refused over a command that declares none. <!-- source: internal/component/command/pipe_catalog.go -- pipeCatalog, RenderOperatorReference --> <!-- source: internal/component/command/pipe.go -- validateDeclaredShape, validateRepeats --> A display operator changes how an answer is shown. A data operator changes what the answer holds. The two are independent, so a display mode never suppresses a data transform: `monitor traceroute | log` and `monitor ping | log` render directly from hop and ping statistics rather than through `ApplyPipes`, and they still apply `resolve` and `origin` to their legend addresses through the shared `enrichAddr` helper. <!-- source: internal/component/cli/model_enrich.go -- enrichAddr --> ### Scripting against the daemon: `| raw` The SSH exec channel is two surfaces at once. An operator running `ssh <host> 'show bgp peer list'` gets the format `environment cli format default` names. A program that parses the answer wants the data behind that rendering, and it must not change the day an operator changes the default. `| raw` is how the second caller asks. It answers the command dispatcher's JSON byte for byte, so it is the stable contract for a script: ``` ssh ze-host 'show bgp peer list | raw' ``` `| json` is a renderer, not this. It unwraps a single-key object holding an array, so `{"commands": [...]}` reaches the caller as a bare `[...]`. JSON numbers retain their exact value through pipe decoding, row selection, metadata injection and address enrichment. `json` and `ndjson` emit integer fields as numbers, including `9007199254740993` (2^53 + 1) and `18446744073709551615` (the largest uint64), without float64 rounding or quotes. The same integers render in full decimal form in `yaml`, `table` and `text`. This guarantee applies to inline pipes, streamed records and `ze pipe` stdin; it cannot recover precision already lost by an upstream producer. Formatting still unwraps single-key array wrappers where documented. Malformed JSON or trailing content is never accepted as one complete JSON value: format operators pass it through, while line-oriented fallbacks keep their existing behaviour. <!-- source: internal/component/command/pipe.go -- decodePipeJSON, applyJSON, applyNDJSON --> <!-- source: internal/component/command/format.go -- formatNumber, writeScalar --> Ze's own tooling uses the same operator. Completion, the runtime command tree and the live dashboard each parse an exec-channel answer. Each asks for it through one helper, rather than composing the pipe itself. <!-- source: internal/core/ssh/client/client.go -- RawCommand, ExecCommandRaw --> ### The offline way: `ze pipe` Scripts and pipelines outside an interactive session apply the same operators to any captured JSON via `ze pipe`: ``` ze show host cpu | ze pipe table ze show bgp peer list | ze pipe match established ze show bgp peer list | ze pipe count ze show bgp peer list | ze pipe yaml ze show bgp peer list | ze pipe first 5 ``` `ze pipe` reads stdin (up to 256 MB), applies the pipe chain given as arguments, and writes the result to stdout. It reads the same catalog. `| log` is refused there, by name, because it needs a command that keeps answering; `| no-more` is accepted and does nothing, because paging belongs to a live session. Standalone input carries no declaration, so `| resolve` and `| origin` walk every field whose value parses as an address rather than the declared ones. `ze pipe help` lists every operator, split into the three classes below. <!-- source: cmd/ze/ze_core_pipe.go -- runPipe, pipeUsage --> ### Asking the product: `ze pipe help --json` Do not hand-copy the operator list into a tool. `ze pipe help --json` answers the whole language, and it is generated from the same table the parser reads, so it cannot fall behind: ``` ze pipe help --json ``` Each entry carries the operator's `name`, its `class`, the `shapes` of answer it acts on, whether it takes an `arg`, what a second occurrence in one chain means (`repeat`), and a one-line `description`. The three classes are the contract: | Class | Meaning | |-------|---------| | `global` | acts on the answer whatever it holds. Every command that reaches the pipe layer owes these | | `data` | acts on rows or fields, so a command owes it only where its answer has them | | `stream` | acts on a SEQUENCE of answers, so it means something only where a command keeps answering. `log` is the one operator in it, and a one-shot command refuses it by name | `shapes` names the answer shapes the operator applies to, using the same words the answer head uses on the wire: `doc` for one document or one value, `map` for rows that describe themselves, `tab` for rows read against declared column names. <!-- source: cmd/ze/ze_core_pipe.go -- printPipeCatalogJSON --> <!-- source: internal/component/command/pipe_catalog.go -- pipeCatalog --> ### Saving an answer: `| save <path>` `| save` writes the answer to a file and passes it through, so the terminal still shows it: ``` show bgp | json | save /tmp/peers.json ``` It writes the answer you are looking at, wherever `| save` sits in the chain. A chain that names no format has the configured default appended to its end, so a save applied in place would write the dispatcher's JSON rather than what the terminal showed. The file is created readable by its owner alone (`0600`), because an answer can carry peer addresses, keys and topology. The write is atomic: a failure leaves the destination as it was rather than truncated. **`| save` works where your own process runs the chain**: an interactive session, the monitors, and `ze pipe`. An interactive session is the one surface a shell redirect cannot reach, because the answer is drawn to a terminal and never reaches a pipe. **It is refused over `ze cli -c`, an SSH exec channel and the web**, by name. The daemon expands the chain on those surfaces, so the file would be written on the daemon's filesystem, with the daemon's privileges, at a path the caller chose. Redirect the output from your shell there; that already works. <!-- source: internal/component/command/pipe_save.go --> ### Configuration presentation Configuration uses the same separation between data and presentation. Operators can inspect compact hierarchical blocks, while automation can consume one complete `set` path per line: ```bash ze config show router.conf bgp peer transit-a ze config migrate format set router.conf ``` `ze config migrate format hierarchical` converts set syntax back to blocks. Rendering both forms back to canonical set syntax provides a presentation-neutral comparison. <!-- source: internal/component/config/cli/cmd_show.go -- path-scoped hierarchical view --> <!-- source: internal/component/config/cli/cmd_migrate.go -- set and hierarchical rendering --> ### Demo: Render one configuration for humans and automation Show one BGP peer as hierarchical blocks and set commands, round-trip between both with identical canonical output, then compose match and count over Ze's plugin registry. [Download the asciicast recording](../../assets/demos/config-views.cast?v=3698ec29e0) · [Plain-text transcript](../../assets/demos/config-views.txt?v=f4f89fbe3c) Recorded with Ze 26.08.31 on macOS and Linux using Ze recorder. Duration: 1 minute 21 seconds. ```console $ ze config show router.conf bgp peer transit-a connection { local ip 192.0.2.1 remote ip 192.0.2.2 } session { asn { local 65000; remote 65001; } family ipv4/unicast { prefix maximum 1000000; } } $ ze config migrate format set router.conf 2>/dev/null | ze pipe match 'bgp peer transit-a' set bgp peer transit-a connection local ip 192.0.2.1 set bgp peer transit-a connection remote ip 192.0.2.2 set bgp peer transit-a session asn local 65000 set bgp peer transit-a session asn remote 65001 ... $ cmp -s router.set roundtrip.set && echo 'canonical output: identical' canonical output: identical $ ze show plugins | ze pipe match flowspec bgp-nlri-flowspec flowspec-firewall ... $ ze show plugins | ze pipe match flowspec | ze pipe count {"count":3,"pipe":{"count":true}} Hierarchical and set syntax are alternate presentations of the same parsed configuration. Converting to set syntax and back produces identical canonical set commands. The standalone formatter composes the same match and count operators for shell pipelines. ``` ### Column order `| table` and `| text` put a command's columns in the order the command declares, and every column it does not name after those, alphabetically. A command that declares nothing renders every column alphabetically, as before. `show bgp` declares the order an operator reads a peer in: ``` address name description remote-as peer-type state uptime state-changed last-error routes-received routes-accepted routes-sent updates-received updates-sent keepalives-received keepalives-sent eor-received eor-sent connections-dropped ``` The columns come in the order you read a peer in: - which peer the row is about - whether the session is up, and why it last went down - what the session carries - the counters you reach for only when something is wrong `| json`, `| ndjson` and `| yaml` keep their alphabetical keys. A program reads those three, and key order carries no meaning for a program. A column is never hidden. Ordering decides where a key renders, never whether it renders. A field you do not see in the order the command declared is still in the table, after the declared ones. Commands under `show bgp` declare an order, and so do some commands under `show config`, `show data`, `show env`, `show schema` and `show yang`. Two channels write them: Go compiled into the daemon, and a plugin's startup message. Each declaration is an operator judgment about what leads, so a command takes one as somebody makes that judgment. `ze help command "<path>" --json` answers what one command declares, and it reads the two channels together. <!-- source: internal/component/command/column_order.go -- RegisterColumns, ColumnsForCommand --> <!-- source: internal/component/command/pipe_table.go -- tableStyle.orderKeys, bestColumnOrder --> <!-- source: internal/component/bgp/plugins/cmd/peer/peer.go -- registerColumns --> <!-- source: internal/component/command/answer_shape.go -- RegisterPluginShapes --> ### Choosing the columns: `| display` and `| fill` A nineteen-column table answers many questions at once. `| display` cuts it to the question you asked: ``` show bgp peer list | display state name ``` Those two columns render, in that order, and no other column does. Press Tab after `| display` and the CLI offers the field names the command declared. `| fill` brings the rest back, behind what you displayed: ``` show bgp peer list | display state | fill # then the command's own order show bgp peer list | display state | fill alpha # then by field name show bgp peer list | fill alpha reverse # every column, reverse by name ``` | Written | The remaining columns come back | |---------|--------------------------------| | nothing | not at all | | `fill` | in the order the command declares, and by name when it declares none | | `fill alpha` | by field name, whatever the command declares | | `fill ... reverse` | in the same way, flipped | The two operators are independent. `| display` names the fields that lead, and `| fill` says what happens to the ones it did not name. With no `| display` every column is a remaining column, so `| fill alpha` sorts the whole table. Each takes one type of argument, and that is deliberate: `| display` takes field names, `| fill` takes keywords. A token that is a field name in one position and a keyword in another is a token you cannot complete and cannot read. A third way was removed on 2026-08-19. `| fill overall` ordered the columns by the width they render at, and that width is known only after every cell of the whole answer has been rendered, so the first row could not be written until the last had been read. A streamed answer cannot do that. `overall` is now refused by name, and `| fill` and `| fill alpha` are unaffected. **`| display` reaches `| json`, `| ndjson` and `| yaml`. `| fill` does not.** Which fields to answer with is a question you asked out loud, so a program gets the answer you asked for. The sequence of JSON keys carries no meaning for a program, so it stays alphabetical. ``` show bgp peer list | display state name | json # two fields per peer ``` <!-- source: internal/component/command/pipe_columns.go -- parseDisplay, parseFill, applyDisplaySelect --> <!-- source: internal/component/command/pipe_table.go -- tableStyle.orderKeys, fillKeys --> ### A name for a chain: pipe aliases Some selections are worth a name. `show bgp` answers its aggregate fields and its peer rows side by side, and an operator usually wants one half: ``` show bgp | peers # the peer rows, as a table show bgp | summary # router-id, local AS, uptime and the peer counts ``` Each is a name for a `| display` you would otherwise type in full. `| peers` is `| display peers`, and `| summary` names the five aggregate fields. Press Tab after the pipe character and both are offered beside the operators. An alias is fixed at registration, which keeps it readable: - It takes no argument. `| peers established` is refused by name. - It never stands for another alias, so what you see is one substitution. - It is expanded before the command runs, so `| json`, `| text` and every other format carry it. A plugin names an alias for its own commands too. The BGP RPKI plugin declares `summary` on `show bgp rpki`, so the counters come back without the cache server rows: ``` show bgp rpki # the counters and one row for each cache server show bgp rpki | summary # the counters alone ``` `command help "show bgp rpki"` lists the aliases a command answers to, with the chain each one stands for. A plugin's alias is registered in the daemon, and one client does not read it. `ze cli` with no command argument runs its own copy of the interactive model and expands the chain itself. A plugin's alias comes back there as `pipe error: unknown pipe operator: summary`, and Tab does not offer it. Use `ze cli -c "show bgp rpki | summary"`, or the interactive session a plain ssh client reaches. The aliases built into Ze itself, `| summary` and `| peers` on `show bgp`, resolve in every client. <!-- source: internal/component/command/alias.go -- RegisterAliases, RegisterPluginAliases, AliasesForCommand --> <!-- source: internal/component/bgp/plugins/cmd/peer/peer.go -- registerAliases --> <!-- source: internal/component/bgp/plugins/rpki/rpki.go -- overviewCommand, summaryAliasExpansion --> <!-- source: internal/component/cli/model_mode.go -- executeOperationalCommand --> ### Command-specific filters Some commands extend the generic set with their own filter vocabulary, folded into the command itself rather than applying to its answer. `show bgp rib`, for example, adds: | Filter | Description | |--------|-------------| | `received` / `advertised` | Select the Adj-RIB-In or Adj-RIB-Out side | | `peer <selector>` | Filter by peer | | `family <afi/safi>` | Filter by address family | | `prefix <pattern>` | Filter by prefix | | `path <pattern>` | Filter by AS path | | `community <value>` | Filter by standard community | | `match <pattern>` | Cross-field structured match | | `count` | Count matching routes without serializing rows | | `first <n>` / `last <n>` | Take first or last N routes | | `histogram` | Count routes by family and prefix length | | `graph` | Render an AS-path topology graph (box-drawing) | | `reason` | Explain best-path selection (`show bgp rib best` only) | These are parsed and validated the same way as generic pipes, but resolved into command arguments before the command runs, rather than filtering output afterward. `show bgp rib | peer 10.0.0.1 | count` counts only that peer's routes in the RIB iterator. It does not serialize every route and count the answer. Both kinds run in the daemon. A generic pipe filters what the command already produced. A command filter stops it being produced. <!-- source: internal/component/bgp/plugins/cmd/rib/rib.go -- PipeFilter registrations --> <!-- source: internal/component/command/pipe.go -- foldFilters, lookupFilter, validateFilter --> --- ### Page: Interface Management https://ze-software.net/features/interfaces/ # Interface Management Ze manages Linux network interfaces via pure netlink (no iproute2 shell-outs). JunOS-style two-layer model: physical interfaces with named logical units. <!-- source: internal/component/iface/yang/ze-iface-conf.yang — interface container --> <!-- source: internal/component/iface/register.go — registration --> ## Capability Table | Category | Feature | Status | Priority | |---|---|---|---| | **Interface Types** | Ethernet (physical) | have | | | | Dummy (virtual) | have | | | | Veth pairs | have | | | | Bridge (basic) | have | | | | VLAN 802.1Q | have | | | | VLAN 802.1p QoS maps (ingress/egress PCP-priority) | have | | | | Class-of-service named profiles (802.1p, interface-level inheritance) | have | | | | Loopback | have | | | | Bonding / LACP | missing | high | | | VXLAN (a `tunnel` encapsulation, netlink and VPP) | have | <!-- source: internal/plugins/iface/netlink/tunnel_linux.go -- buildVxlan --> | | | GRE / GRETAP / IPIP / SIT tunnels | have | | | | IP6GRE / IP6GRETAP / IP6TNL / IPIP6 tunnels | have | | | | ERSPAN | missing | lower | | | WireGuard (declarative peers, `$9$`-encoded keys) | have | | | | MACsec | missing | lower | | | MACVLAN | missing | lower | | | Geneve | missing | lower | | | PPPoE | missing | lower | | | L2TPv3 | missing | lower | | | WiFi | missing | lower | | | XFRM (route-based IPsec, if_id) | have | | | | VTI (legacy IPsec tunnel) | missing | lower | | | QinQ (802.1ad) | missing | lower | | **Logical Model** | Two-layer physical + unit | have | | | | Unit 0 implicit | have | | | | VLAN units create subinterfaces | have | | | **Lifecycle** | Create (dummy, veth, bridge, VLAN, tunnel) | have | | | | Delete | have | | | | Auto-up on create | have | | | | Admin state control (explicit up/down) | have | | | | Interface rename | missing | lower | | **Address Management** | Add/remove IPv4/IPv6 CIDR | have | | | | DAD-aware monitoring | have | | | | MAC address set/get | have | | | | Gratuitous ARP on add | missing | medium | | | Neighbor table (ARP/NDP) | missing | lower | | **DHCP** | DHCPv4 (RFC 2131, full DORA) | have | | | | DHCPv6 (SARR, IA_NA, IA_PD) | have | | | | Concurrent v4+v6 | have | | | | Direct netlink install | have | | | | Bus events (acquired/renewed/expired) | have | | | | Config-driven (`dhcp { enabled true }`) | have | | | | Default route from DHCP Router option | have | | | | Route priority per unit (`route-priority`) | have | | | | Link-down route deprioritization (metric + 1024) | have | | | | IPv6 default route from RA with configurable metric | have | | | | Routes carry `proto 253` (`ze-iface`), so a teardown removes only its own | have | <!-- source: internal/plugins/iface/netlink/manage_linux.go -- AddRoute stamps rtm_protocol, RemoveRoute matches on it --> | | | Learned default routes rank at metric 254, clear of a static route's metric | have | <!-- source: internal/component/iface/config.go -- defaultLearnedRouteMetric --> | | | DNS from DHCP to `/tmp/resolv.conf` | have | | | | Hostname in DHCPv4 (option 12) | have | | | | Client-ID in DHCPv4 (option 61) | have | | | | NTP servers from DHCP (option 42) | have | | | | DHCPv6 proper Renew (not re-solicit) | missing | medium | | | DHCP relay | missing | lower | | | DHCP server | missing | lower | | **Monitoring** | Netlink multicast (link + addr + neigh) | have | | | | Virtual iface state detection | have | | | | 11 bus topics, JSON payloads | have | | | | Interface statistics/counters | have | | | | Persistent counter tracking | missing | medium | | **Per-Interface Tuning** | IPv4 forwarding | have | | | | ARP filter / ARP accept | have | | | | IPv6 autoconf (SLAAC), netlink backend only | have | | | | IPv6 accept-ra (0/1/2), netlink backend only | have | | | | IPv6 forwarding | have | | | | Proxy ARP | have | | | | ARP announce / ARP ignore | have | | | | RPF / source validation | have | | | | TCP MSS clamping (v4+v6) | missing | medium | | | Directed broadcast | missing | lower | | **IPv6 Extended** | EUI-64 address generation | missing | medium | | | DAD configuration (messages, accept) | missing | medium | | | Custom interface identifiers | missing | lower | | **Router Advertisement** | RA sender per unit (RFC 4861) | have | | | | Prefix Information options for SLAAC | have | | | | RDNSS resolver option (RFC 8106) | have | | | | Router Solicitation answers with rate limit | have | | | | Route Information option (RFC 4191) | missing | lower | | **Traffic Mirroring** | Ingress/egress via tc mirred | have | | | | Idempotent setup/cleanup | have | | | | Traffic redirect (vs mirror) | missing | lower | | **Traffic Control** | QoS / shaping | missing | lower | | | Policing | missing | lower | | | Queuing disciplines | missing | lower | | **Migration** | Make-before-break 5-phase | have | | | | BGP readiness signaling | have | | | | Per-phase rollback | have | | | **BGP Integration** | Reactor subscribes to addr events | have | | | | Listener start/stop on addr change | have | | | | bgp/listener/ready publish | have | | | **Bridge Features** | Create and bring up | have | | | | STP | have | | | | VLAN filtering | missing | medium | | | Add/remove member ports | have | | | | Multicast snooping | missing | lower | | | Port isolation | missing | lower | | | Ageing/forward delay/hello/max age | missing | lower | | **Bonding** | Mode selection | missing | high | | | Hash policy | missing | high | | | LACP rate | missing | high | | | MII monitoring | missing | high | | | Min active links | missing | high | | | Member management | missing | high | | **VRF** | YANG leaf exists | partial | high | | | Route table isolation | missing | high | | | Per-VRF address assignment | missing | high | | | VRF-aware DHCP | missing | medium | | **Gateway Redundancy** | VRRP / keepalived | have | | | | Virtual MAC | have | | | | State monitoring/failover | missing | medium | | **Physical Layer** | Speed / duplex / autoneg | missing | medium | | | Hardware offload (GRO/GSO/TSO/LRO) | missing | medium | | | Ring buffer sizing | missing | lower | | | RPS / RFS | missing | lower | | | ethtool integration | missing | medium | | **Security** | 802.1X / EAPoL | missing | lower | | | Storm control | missing | lower | | **Configuration** | YANG model (all types + units) | have | | | | Input validation (name, VLAN, MTU) | have | | | **Platform** | Pluggable backend interface | have | | | | Linux netlink backend (default) | have | | | | YANG `backend` leaf (config-driven selection) | have | | | | VPP backend (ifacevpp, via GoVPP) | have | ResetCounters via sw_interface_clear_stats; ListKernelRoutes via ip_route_v2_dump; RouteLookup via IPRouteLookupV2 (VPP FIB is authoritative); ListNeighbors via ip_neighbor_dump; VXLAN/GRE/IPIP/LCP/stats-socket/mirror/STP pending third-party vendoring | | | macOS / Darwin | missing | lower | | | FreeBSD / OpenBSD | missing | lower | | | systemd-networkd | missing | lower | | **Quality** | Context-wrapped errors | have | | | | Panic recovery | have | | | | 14 test files (unit + integration) | have | | <!-- source: internal/component/iface/backend.go — Backend interface, RegisterBackend, LoadBackend --> <!-- source: internal/component/iface/dispatch.go — package-level functions delegating to backend --> <!-- source: internal/component/iface/iface.go — bus topics, payload types, InterfaceStats --> <!-- source: internal/component/iface/migrate_linux.go — MigrateInterface 5-phase protocol --> <!-- source: internal/plugins/iface/netlink/manage_linux.go — CreateDummy, CreateVeth, CreateBridge, etc. --> <!-- source: internal/plugins/iface/netlink/tunnel_linux.go — CreateTunnel for 8 tunnel kinds via Gretun/Gretap/Iptun/Sittun/Ip6tnl --> <!-- source: internal/plugins/iface/netlink/wireguard_linux.go — CreateWireguardDevice (netlink), ConfigureWireguardDevice and GetWireguardDevice (wgctrl) --> <!-- source: internal/plugins/iface/netlink/monitor_linux.go — netlink multicast subscription --> <!-- source: internal/plugins/iface/netlink/bridge_linux.go — bridge ports, STP via sysfs --> <!-- source: internal/component/sysctl/backend_linux.go -- per-interface sysctl writes --> <!-- source: internal/plugins/iface/netlink/mirror_linux.go — traffic mirroring via tc --> <!-- source: internal/plugins/iface/vpp/ifacevpp.go — VPP Backend implementation (lazy GoVPP channel) --> <!-- source: internal/plugins/iface/vpp/query.go — ListInterfaces/GetInterface/Get/SetMACAddress via SwInterfaceDump and SwInterfaceSetMacAddress --> <!-- source: internal/plugins/iface/vpp/monitor.go — StartMonitor via WantInterfaceEvents + SubscribeNotification --> <!-- source: internal/plugins/iface/vpp/naming.go — ze short name <-> VPP SwIfIndex bidirectional map --> <!-- source: internal/plugins/iface/vpp/fib.go — RouteLookup via IPRouteLookupV2; ListKernelRoutes via ip_route_v2_dump over both v4 and v6 tables --> <!-- source: internal/plugins/iface/dhcp/dhcp_v4_linux.go — DHCPv4 worker --> <!-- source: internal/plugins/iface/dhcp/dhcp_v6_linux.go — DHCPv6 worker --> <!-- source: internal/component/bgp/reactor/reactor_iface.go — BGP integration --> ## Architecture ``` Config (YANG: ze-iface-conf.yang, "backend" leaf selects backend) | v iface component (register.go) -- OnConfigure() loads backend, starts monitor | v Backend interface (backend.go) -- 45 methods: lifecycle, address, route, bridge, mirror, monitor | v +------------------+--------------------+ | | | netlink backend DHCP plugin (future: networkd, FreeBSD) (ifacenetlink/) (ifacedhcp/) | | netlink calls lease negotiation | | v v Bus topics -- interface/{created,deleted,up,down,addr/*,dhcp/*} | v Subscribers -- BGP reactor (starts/stops listeners on address changes) ``` ## Interface Discovery During `ze init`, Ze discovers OS network interfaces and generates initial configuration entries. The `DiscoverInterfaces` function enumerates interfaces via `ListInterfaces` (netlink on Linux, stdlib on other platforms) and classifies each by Ze type: ethernet, bridge, veth, dummy, or loopback. On Linux, the netlink `device` type maps to ethernet (except `lo`, which maps to loopback). Results are sorted by type then name. <!-- source: internal/component/iface/discover.go -- DiscoverInterfaces, infoToZeType --> The generated config uses descriptive names as YANG list keys (the OS interface name at discovery time). The MAC address serves as the physical binding between configuration and hardware. For ethernet, veth, and bridge interfaces, the MAC address (`mac { address }`) is optional and, when set, must be unique within each list. Omit it to keep the hardware-assigned MAC, or set it to override the address and pin the named config entry to a specific physical device. <!-- source: internal/component/iface/yang/ze-iface-conf.yang -- unique on ethernet/veth/bridge lists --> Discovery writes one selector per entry. An ethernet that reports a factory address (`IFLA_PERM_ADDRESS`) is bound by `mac { match }` against that address, so the entry follows the NIC across a kernel rename. Every other discovered kind records an `os-name` hidden leaf holding the original OS interface name, which remains available for debugging and internal binding after the user renames the config entry. A discovered ethernet gets no `mac { address }` override: that leaf imposes an address on whatever device the entry resolves to, which is a wrong write the moment the name reaches a different port. <!-- source: internal/component/iface/yang/ze-iface-conf.yang -- os-name hidden leaf in interface-common grouping --> Operator-facing interface operations resolve the logical name to its kernel device through the shared resolver before touching the kernel, so a configured name that differs from the OS device (via the `os-name` or `mac/match` selector) is honored uniformly: the `iface` CLI ops (set MTU, add/remove address, admin up/down, bridge, mirror, ...), the DHCP client socket binding, and the routing/protocol consumers all act on the bound kernel device. The dispatch layer performs this translation for the by-name backend ops and leaves `GetInterface` and `ListInterfaces` raw because the resolver is built on them. `./le iface-resolution check` keeps new consumers from resolving the kernel directly instead of through the resolver. The **config apply path** resolves separately, and on purpose. It takes ONE interface listing per apply and binds every ethernet entry from it, so each logical name resolves once and Phase 2, Phase 2c and the address reconcile all key their work by the same kernel device. Going through the dispatch wrappers instead would translate at each of about twenty call sites and still miss the sites that take a device string rather than make a backend call: the per-interface sysctl keys and the ethtool offload ioctls. An entry whose selector names no present device is **unbound**: every phase skips it, and none falls back to the logical name. That fallback is what made an aliased entry configure whatever else carried its name. An entry whose `mac/match` names more than one device refuses the apply, and a device only answers a `mac/match` when the address it is matched on is its own: a VLAN inherits its parent's, and a bridge or bond wears a member's, so neither is a candidate. `ResolveDevice` draws the same line for the by-name dispatch ops: a name with no selector passes through unchanged, and a name WITH a selector that fails to resolve returns an error rather than the name. The two settings that name a SECOND interface resolve through the same map: a bridge member is enslaved by the kernel device its entry selects, and a mirror sends its copy to the capture port's kernel device. <!-- source: internal/component/iface/config_apply.go -- bindDevices, deviceFor, validateSelectors --> <!-- source: internal/component/iface/dispatch.go -- ResolveDevice translation in the by-name dispatch ops --> <!-- source: internal/component/iface/resolve.go -- Resolve / Addresses / Subscribe logical-name resolver --> <!-- source: internal/le/ifaceresolution/ifaceresolution.go -- Answer --> A MAC address validator (`ze:validate "mac-address"`) provides format checking (colon-separated hex octets) and live OS autocomplete. The `CompleteFn` calls `DiscoverInterfaces` on each tab press, returning MAC addresses from currently active OS interfaces. The `os-name` selector carries the sibling validator `ze:validate "os-device-name"`, which screens the FORM of a kernel device name and completes to the device names present on the box. It does not check that the device exists: the YANG promises a binding that defers until its device appears, and a config is validated on machines that will never run it. <!-- source: internal/component/config/validators.go -- MACAddressValidator, OSDeviceNameValidator --> <!-- source: internal/component/iface/validators.go -- macAddressCompleteFn, osDeviceNameCompleteFn --> <!-- source: internal/component/config/validators.go -- MACAddressValidator, CompleteFn --> <!-- source: internal/component/config/validators_register.go -- mac-address registration --> ## Key Design Decisions - **Descriptive names as keys, MAC as binding.** Interface names in config are user-chosen descriptive labels. The MAC address ties the config entry to physical hardware. This separates the logical identity (name) from the physical identity (MAC). - **Pluggable backends.** The iface component defines a `Backend` interface (34 methods). OS-specific operations live in backend plugins (`ifacenetlink` for Linux). The YANG `backend` leaf selects the backend (default: `netlink`). DHCP is a separate plugin (`ifacedhcp`) that uses the backend for address operations. - **Pure netlink, no shell-outs.** The netlink backend uses `github.com/vishvananda/netlink`. - **Event-driven.** Monitor publishes to bus; consumers subscribe and react. No polling. - **Per-family addresses.** Addresses live inside `ipv4 { address [...]; }` and `ipv6 { address [...]; }` containers within each unit, following the Junos/Nokia model. - **DAD-aware.** IPv6 addresses with `IFA_F_TENTATIVE` flag are held until DAD completes. - **Make-before-break.** Migration adds new address, waits for BGP readiness, then removes old. Prevents session loss during address moves. - **Same-subnet renumbers keep working.** Because the new address is added first, a renumber inside one subnet (`10.0.0.1/24` -> `10.0.0.2/24`) briefly leaves the new address as a Linux IPv4 *secondary* of the old one, and Linux deletes every secondary of a subnet along with its primary. Before such a removal the netlink backend enables `net.ipv4.conf.<device>.promote_secondaries` so the kernel promotes a secondary instead of flushing the subnet, logging `enabled promote_secondaries` when it changes the knob. If the knob cannot be set the removal fails, naming the addresses that would have been destroyed, rather than silently emptying the interface. The knob is left enabled: restoring it would re-arm the same hazard on the next removal. IPv6 has no primary/secondary distinction and is untouched, as is the VPP backend, which deletes exactly the requested address. <!-- source: internal/plugins/iface/netlink/addr_primary.go -- ensureDeleteIsolated, flushedByDelete --> <!-- source: internal/plugins/iface/netlink/manage_linux.go -- RemoveAddress delegates to removeAddressGuarded --> - **Virtual interface state.** Dummy/bridge/veth report `OperUnknown` not `OperUp`; monitor checks `IFF_UP` flag as fallback. - **Tunnel encapsulation as YANG choice/case.** The `tunnel` list at the iface level is one container with an `encapsulation` choice that branches into one case per Linux netlink kind (gre, gretap, ip6gre, ip6gretap, ipip, sit, ip6tnl, ipip6). Per-case leaf sets are constrained by the schema: `key` only appears in GRE-family cases, `hoplimit`/`tclass`/`encaplimit` only in v6-underlay cases. Local and remote endpoints use the same `local { ip ... } remote { ip ... }` shape as the BGP peer connection block, with `local { interface ... }` as an alternative when the source should be taken from another interface. - **Idempotent cleanup.** Delete and mirror removal succeed even if already gone. - **The mirror shares its qdisc, so it owns only its filters.** Mirroring attaches a tc mirred filter at priority 1 to the qdisc at handle `ffff:` on the source interface. That qdisc is a shared attachment point: flow-export sampling attaches its own filter at priority 100 to the same object, and both hooks, ingress and egress, hang off it. So mirror setup accepts a qdisc another subsystem created (`EEXIST` is not an error), and mirror teardown deletes its own filters and leaves the qdisc standing, empty. A teardown cannot know who created a shared qdisc, and the set of filters on it can change between the moment it is read and the moment it is acted on. An empty qdisc carries no filter and classifies no packet, and the next mirror or sampling setup adopts it. Only the rollback of a failed setup deletes a qdisc, because it created that qdisc moments earlier and knows so. Ze always creates clsact, never the older ingress qdisc, because clsact carries both hooks and can therefore serve a mirror with a different destination per direction. `tc qdisc show` on an interface that once mirrored reports a clsact qdisc with no filter on it, which is expected. <!-- source: internal/plugins/iface/netlink/mirror_linux.go -- RemoveMirror, removeMirrorFilters, undoMirrorSetup, ensureClsactQdisc --> - **A mirror the config drops is removed.** `applyConfig` compares the mirrors the new config asks for against the mirrors the previous config installed, and removes every one that was dropped or changed before it installs the new set. A changed destination is a remove followed by an install, because tc filters are additive: installing the new destination would otherwise leave the old one duplicating traffic. - **The reconcile reads the dataplane, so a restart retires a stranded mirror.** A second pass runs inside the config reconcile. It compares the mirrors the dataplane carries against the mirrors the configuration asks for, so it needs no previous config. A mirror the operator deleted while ze was down is therefore removed on the next boot. So is a mirror an earlier apply failed to remove. A backend that cannot read its live mirrors removes nothing, because "the read failed" is not "no mirror is installed". - **The pass acts only on the interfaces the configuration names.** A priority-1 matchall mirred filter is a shape, not a mark of ownership, and another tool can install the same one. So ze removes a mirror only on an interface named by the current config or the previous one. One case is out of reach: an interface whose whole stanza was deleted while ze was down keeps its mirror. Nothing then tells that mirror apart from a filter ze never installed. To retire that mirror, delete the interface from the configuration while ze runs, or clear the mirror leaves first. <!-- source: internal/component/iface/config_mirror.go -- indexMirrorSpecs, removeStaleMirrors, reconcileMirrors, applyMirror --> <!-- source: internal/plugins/iface/netlink/mirror_linux.go -- ListMirrors, mirrorDestinationName --> ## Tunnel Configuration ``` interface { tunnel gre0 { encapsulation { gre { local { ip 192.0.2.1; } remote { ip 198.51.100.1; } key 42 } } unit default { ipv4 { address [ 10.0.0.1/30 ] } } } tunnel sixin4 { encapsulation { sit { local { ip 192.0.2.1; } remote { ip 198.51.100.1; } } } unit default { ipv6 { address [ 2001:db8::1/64 ] } } } tunnel v6ov6 { encapsulation { ip6tnl { local { ip 2001:db8::1; } remote { ip 2001:db8::2; } hoplimit 64 encaplimit 4 } } } } ``` The nine supported encapsulation kinds map to Linux netlink kinds: | Kind | Linux netlink | Underlay | Layer | Notes | |------|--------------|----------|-------|-------| | `gre` | gre | IPv4 | L3 | RFC 2784, RFC 2890 key | | `gretap` | gretap | IPv4 | L2 (bridgeable) | Ethernet over GRE | | `ip6gre` | ip6gre | IPv6 | L3 | hoplimit/tclass per RFC 2473 | | `ip6gretap` | ip6gretap | IPv6 | L2 (bridgeable) | | | `ipip` | ipip | IPv4 | L3 | RFC 2003, no key | | `sit` | sit | IPv4 | L3 | 6in4 per RFC 4213 | | `ip6tnl` | ip6tnl | IPv6 | L3 | RFC 2473 with Proto=IPV6 | | `ipip6` | ip6tnl | IPv6 | L3 | RFC 2473 with Proto=IPIP | | `vxlan` | vxlan | IPv4 (UDP) | L2 (bridgeable) | RFC 7348. Mandatory `vni` 1..16777215, `port` default 4789 | `ipip6` shares the kernel `ip6tnl` netdev with a different inner protocol byte (4 vs 41). Both surface as distinct YANG cases so the schema and config are unambiguous. L2 tunnel kinds (`gretap`, `ip6gretap`) support an optional `mac` container (with an `address` leaf) inside the case container. L3 kinds do not carry a MAC address (the kernel does not assign one). ### Outer-header TTL A tunnel that names no `ttl` carries an outer TTL of 64, and a tunnel that names no `hoplimit` carries an outer hop limit of 64. The value 0 keeps its meaning: it tells the kernel to copy the inner packet's TTL onto the outer header, and an operator who wants that mode writes `ttl 0`. 64 is the default because inherit blackholes a multi-hop underlay. A locally originated packet can carry a small inner TTL, and every underlay router decrements the outer header, so an inherited value expires the encapsulated packet before it reaches the far endpoint. The tunnel then works between directly connected endpoints and drops everything else. Each default is declared once, in the YANG leaf, and the parse materializes it into the tunnel spec before the backend reads the leaves. No Go constant repeats it, so the schema, the CLI completion, the config diff and the device cannot disagree. The defaults reach netlink-backed tunnels. The VPP tunnel calls carry no outer-TTL field, so the `ttl` leaf is declared `ze:backend "netlink"` and a commit that names one on the vpp backend is refused by path: ``` /interface/tunnel/t0/encapsulation/gre/ttl: feature not supported by backend "vpp" (supported: netlink) ``` A vpp-backed tunnel that names no `ttl` still commits, because the gate reads what the operator wrote and the schema default is materialized after it. `sit` and the other netlink-only kinds are refused at the kind rather than at this leaf. <!-- source: internal/component/iface/yang/ze-iface-conf.yang -- the ttl and hoplimit leaves and their defaults --> <!-- source: internal/component/iface/tunnel.go -- tunnelSchema, loadTunnelSchema, applyDefaults --> <!-- source: internal/plugins/iface/vpp/tunnel.go -- createGRETunnel and createIPIPTunnel, which carry no TTL field --> <!-- source: test/plugin/tunnel-ttl-default.ci -- the outer TTL read back from each device --> <!-- source: internal/component/iface/register.go -- validateBackendGate, run before parseIfaceSections --> ERSPAN, GRE keepalives, VRF underlay/overlay leaves, and `ignore-df` on gretap are out of scope for v1. All nine kinds are creatable on the gokrazy appliance image. Nine kinds need six kernel symbols plus the GRE demux gate, and the appliance kernel builds all seven in: `NET_IPGRE_DEMUX`, `NET_IPGRE`, `IPV6_GRE`, `NET_IPIP`, `IPV6_TUNNEL`, `IPV6_SIT`, `VXLAN`. `runtime.require` pins the same seven and the required-symbol check accepts `=y` alone, so a Kconfig `default m` answer fails the image build rather than shipping a kind nobody can create. Earlier images carried two of the nine, so a config Ze accepted produced a device that never appeared. Measured cost of the seven: vmlinuz grew 98304 bytes, 0.60%. <!-- source: gokrazy/kernel/runtime.config -- the seven tunnel symbols set to =y --> <!-- source: gokrazy/kernel/runtime.require -- the same seven pinned --> <!-- source: test/plugin/iface-tunnel-kinds.ci -- every kind created on the appliance kernel --> <!-- source: internal/component/iface/yang/ze-iface-conf.yang -- list tunnel, choice kind, tunnel-v4-endpoints / tunnel-v6-endpoints groupings --> <!-- source: internal/component/iface/tunnel.go -- TunnelKind enum, TunnelSpec struct --> <!-- source: internal/plugins/iface/netlink/tunnel_linux.go -- CreateTunnel switch and per-kind builders --> ## Tunnel Reload Behaviour On config reload (SIGHUP or transaction commit), `applyConfig` compares each tunnel's spec against the previously applied config, which `indexTunnelSpecs` indexes by name. Tunnels whose spec is unchanged are left alone; MTU, MAC, and addresses still reconcile through later phases, so non-spec changes still take effect. Tunnels whose spec changed (encapsulation kind, `local`, `remote`, `key`, `ttl`, `hoplimit`, and the rest of the per-case leaves) are deleted and recreated, because Linux does not support in-place modification of most tunnel kinds. The recreate briefly drops traffic on the changed tunnel only; unrelated tunnels are not disturbed. A third case applies when the previously applied config is not available. The comparison above reads the config this process applied before, and a netdev outlives the process that made it. A tunnel therefore has no previous spec at every plugin start, at every daemon start, and in the second apply of a reload that starts the interface plugin, so the apply tries to create a link that is already there. What holds the name decides the result: | What holds the name | Result | |---------------------|--------| | Nothing | The create runs. A failure here is the kernel's own answer, and it fails the apply | | A tunnel of the configured kind | Ze keeps it and logs a WARN. The link is not deleted and not rebuilt, so traffic crossing it is not interrupted. Its encapsulation parameters are not compared: the read-back carries the link type and no encapsulation field | | Any other device: a dummy, a bridge, a physical NIC, or a tunnel of a different kind | The apply fails. Ze does not delete a device it did not create, and it does not push this tunnel's MTU, admin state and addresses onto a device that is not the tunnel | <!-- source: internal/component/iface/config_apply.go — applyConfig, the tunnel create step --> <!-- source: test/plugin/iface-tunnel-restart.ci — ze runs twice over one tunnel config; the kernel ifindex is compared across the restart --> The last row is also what an operator meets after editing `encapsulation` while the daemon is down. There is no previous spec to compare against, so the edit does not reach the delete-and-recreate path. Ze refuses the config instead of running the old encapsulation under the new addresses. Delete the tunnel, or give the new encapsulation a new interface name. <!-- source: internal/component/iface/config_apply.go -- applyConfig, indexTunnelSpecs --> <!-- source: internal/component/iface/tunnel.go -- kernelLinkTypes, the link type each kind reports --> ## Tunnel Validation Scope `ze config validate`, API pre-save validation, and CLI commit validation run YANG schema checks plus registered side-effect-free in-process plugin verifiers. They do not call live external plugin `OnConfigVerify` callbacks because those callbacks are runtime transaction participants. Live external plugin verification runs when the daemon loads, reloads, or commits config; failed API commits roll the saved file back to the previous content before returning the reload error. Interface validation that has an in-process verifier, such as tunnel case consistency and backend feature gates, is visible in static validation. Any third-party external plugin that only implements a live `OnConfigVerify` callback is verified at daemon transaction time, not by `ze config validate`. <!-- source: internal/component/config/cli/cmd_validate.go -- runValidation generic in-process plugin verifier loop --> <!-- source: internal/component/config/plugin_verify.go -- VerifyPluginConfig uses InProcessConfigVerifier only --> <!-- source: internal/component/iface/config.go -- parseTunnelEntry used by iface in-process verifier and OnConfigVerify --> <!-- source: internal/component/api/config_session.go -- failed reload restores previous content --> ## WireGuard Configuration WireGuard interfaces are a top-level `wireguard` list under `interface`, alongside `ethernet`, `tunnel`, and the other iface kinds. Each entry carries interface-level parameters plus a nested `peer` list; unit-level addresses ride the same `interface-unit` grouping used everywhere else. ``` interface { wireguard wg0 { listen-port 51820 fwmark 0 private-key "$9$ABCabc..." # $9$-encoded; see below peer site2 { public-key "YYYY..." # base64, plaintext preshared-key "$9$DEF..." # optional, also $9$-encoded endpoint { ip 198.51.100.2 port 51820 } allowed-ips [ 10.0.0.2/32 192.168.10.0/24 ] persistent-keepalive 25 } unit default { ipv4 { address [ 10.0.0.1/24 ] } } } } ``` <!-- source: internal/component/iface/yang/ze-iface-conf.yang -- list wireguard, ze:sensitive on private-key and peer preshared-key, ze:listener on the list entry --> <!-- source: internal/component/iface/wireguard.go -- WireguardSpec / WireguardPeerSpec types --> <!-- source: internal/component/iface/config.go -- parseWireguardEntry, parseWireguardPeer --> <!-- source: internal/component/iface/config_apply.go -- applyConfig, indexWireguardSpecs, wireguardSpecEqual --> ### Key material and `$9$` encoding `private-key` and peer `preshared-key` are marked `ze:sensitive` in YANG. The config parser auto-decodes `$9$`-prefixed values on load (`internal/component/config/parser.go`); `ze config show` / `ze config dump` always re-encodes them on output (`internal/component/config/cli/cmd_dump.go`), so the plaintext base64 form never reaches the config file on disk. Public keys are public and stored plaintext. **`$9$` is JunOS-compatible obfuscation, not encryption.** Anyone with read access to the config file (or the zefs blob, depending on storage backend) can trivially recover the plaintext key via `secret.Decode`. The protection is on the filesystem layer: `chmod 600 /etc/ze/ze.conf` (or the equivalent on the `.zefs` blob). This is the same posture ze uses for BGP MD5 passwords, SSH secrets, MCP tokens, and API tokens. <!-- source: internal/component/config/secret/secret.go -- Encode, Decode, IsEncoded ($9$ implementation) --> ### Reconciliation On reload, `applyConfig` compares the new spec to the previously applied spec via `wireguardSpecEqual`. Unchanged entries are a no-op; the kernel is not touched and peer handshake state is preserved. Changed entries trigger a single `ConfigureWireguardDevice` call with `wgtypes.Config{ReplacePeers: true}` -- the kernel matches unchanged peers by public-key and preserves their handshake state, so "apply entire spec on every change" is functionally equivalent to a per-peer diff at a tiny fraction of the code. New wireguard entries get a `CreateWireguardDevice` (netlink) before the Configure call. Wireguard list entries removed from config are deleted by Phase 4 reconciliation, same as tunnels. Peer names in config (`peer site2 { ... }`) are operator-chosen labels. The kernel tracks peers only by public key, so the label can change freely without affecting the handshake. `ze init` emits discovered peers with synthetic names (`peer0`, `peer1`, ...) which operators typically rename via `ze config edit`. <!-- source: internal/component/iface/config_apply.go -- wireguardSpecEqual, wireguardPeerEqual --> <!-- source: internal/component/iface/backend.go -- CreateWireguardDevice, ConfigureWireguardDevice, GetWireguardDevice interface methods --> ### Port conflict detection `listen-port` participates in the same conflict-detection mechanism as TCP services (web, ssh, mcp, etc.) with one Phase-5 extension: `ListenerEndpoint` gained a `Protocol` field so wireguard's UDP ports never clash with a TCP service on the same port. Two wireguards with the same `listen-port` are rejected at reload time. <!-- source: internal/component/config/listener.go -- ListenerEndpoint.Protocol, buildListenerService, CollectListeners, FindListenerConflict --> ### Dependencies WireGuard peer and key configuration uses [`golang.zx2c4.com/wireguard/wgctrl`](https://github.com/WireGuard/wgctrl-go), the reference Go client maintained by the WireGuard authors (Donenfeld, Layher). It is vendored under `vendor/golang.zx2c4.com/wireguard/wgctrl` along with its transitive dependencies `github.com/mdlayher/genetlink` and `github.com/mdlayher/netlink`. WireGuard has no RFC; reference material is the original whitepaper (https://www.wireguard.com/papers/wireguard.pdf), the Linux kernel genetlink spec (https://www.kernel.org/doc/html/latest/userspace-api/netlink/specs/wireguard.html), and `wg(8)`. ## IPv6 Router Advertisements Ze sends IPv6 Router Advertisements on a LAN unit, which is the job radvd does on other systems. Hosts on the link build addresses by stateless address autoconfiguration (SLAAC), learn a default router, and learn DNS resolvers. The `iface-ra` plugin owns the socket, the timers, and the answers to Router Solicitations (RFC 4861). The `router-advertisement` container sits inside the per-unit `ipv6` container. It carries `ze:os "linux"` and `ze:backend "netlink"`. The schema walker prunes it on another platform, and a tree with `backend vpp` rejects it at config verify with `feature not supported by backend "vpp"`. <!-- source: internal/component/iface/yang/ze-iface-conf.yang -- container router-advertisement --> <!-- source: internal/component/config/backend_gate.go -- ze:backend commit-time feature gate --> <!-- source: internal/plugins/iface/ra/register.go -- iface-ra registration --> ### Send and accept are separate An advertising interface tells hosts to send it their off-link traffic. Set `ipv6 forwarding true` on that unit, or the kernel drops that traffic and every host on the link loses connectivity. Leave `accept-ra` at 0 unless the same interface also learns from another router. Config verify cannot catch this, because forwarding can arrive from a sysctl profile after verify runs. Ze reports the state instead. The `doctor-iface-ra-forwarding` check emits one warning for each advertising interface whose `net.ipv6.conf.<device>.forwarding` is 0. <!-- source: internal/plugins/iface/ra/doctor.go -- checkRAForwarding, doctor-iface-ra-forwarding --> ### Receiving advertisements needs the netlink backend The `autoconf` and `accept-ra` leaves of the per-unit `ipv6` container each carry `ze:backend "netlink"`. The kernel builds a SLAAC address only when Router Advertisements reach the device it owns. On the netlink backend that device is the NIC. On the vpp backend VPP owns the NIC, the kernel sees the Linux Control Plane tap, and no advertisement reaches it. Ze writes `net.ipv6.conf.<device>.autoconf` and `net.ipv6.conf.<device>.accept_ra` on the tap, the kernel waits, and no address appears. A tree that sets either leaf with `backend vpp` is rejected at config verify with `feature not supported by backend "vpp" (supported: netlink)`, and the message names the unit. This matches the refusal the `dhcp`, `dhcpv6` and `router-advertisement` containers already carry. <!-- source: internal/component/iface/yang/ze-iface-conf.yang -- leaf autoconf, leaf accept-ra --> <!-- source: internal/component/config/backend_gate.go -- ValidateBackendFeatures, backendAnnotation --> <!-- source: internal/component/iface/config_sysctl.go -- applySysctl emits the two keys --> ### Container leaves | Leaf | Type | Range | Unit | Default | Meaning | |------|------|-------|------|---------|---------| | `enabled` | boolean | | | `false` | Send advertisements on this unit. RFC 4861 Section 6.2.1 requires the default `false`, so a node never becomes a router by accident | | `maximum-interval` | uint16 | 4..1800 | seconds | 600 | Longest wait between unsolicited advertisements (MaxRtrAdvInterval) | | `minimum-interval` | uint16 | 3..1350 | seconds | 200 | Shortest wait between unsolicited advertisements (MinRtrAdvInterval) | | `router-lifetime` | uint16 | 0..9000 | seconds | 1800 | How long hosts keep Ze in their default router list (AdvDefaultLifetime) | | `hop-limit` | uint8 | 0..255 | | 64 | Value hosts put in the Hop Limit field of their outgoing packets. 0 states no value | | `managed` | boolean | | | `false` | The M flag: hosts get their addresses from DHCPv6 | | `other-config` | boolean | | | `false` | The O flag: hosts get other configuration, such as DNS, from DHCPv6 | | `reachable-time` | uint32 | 0..3600000 | milliseconds | 0 | How long a host treats a neighbor as reachable after a confirmation. 0 states no value | | `retransmit-timer` | uint32 | | milliseconds | 0 | Time between retransmitted Neighbor Solicitations on this link. 0 states no value | Ze picks each unsolicited interval at random between `minimum-interval` and `maximum-interval`, which keeps two routers on one link from synchronizing (RFC 4861 Section 6.2.4). The first three advertisements after a start wait 16 seconds at most, so a new router is found quickly. <!-- source: internal/core/ndp/schedule.go -- UnsolicitedInterval, MaxInitialAdvertisements --> ### Prefix list Each `prefix` entry becomes one Prefix Information option (RFC 4861 Section 4.6.2). The list key is the prefix in CIDR notation. SLAAC needs a 64-bit prefix (RFC 4862 Section 5.5.3). | Leaf | Type | Unit | Default | Meaning | |------|------|------|---------|---------| | `on-link` | boolean | | `true` | The L flag: hosts treat addresses in this prefix as on-link | | `autonomous` | boolean | | `true` | The A flag: hosts build addresses from this prefix by SLAAC | | `valid-lifetime` | uint32 | seconds | 2592000 | How long the prefix stays valid. 30 days. 4294967295 never expires | | `preferred-lifetime` | uint32 | seconds | 604800 | How long addresses from the prefix stay preferred. 7 days | <!-- source: internal/component/iface/config_ra.go -- raParsePrefixEntry, raDefaultValidLifetime, raDefaultPreferredLifetime --> ### Resolvers (RDNSS) The `rdnss` container points a link at a resolver without a DHCPv6 server (RFC 8106 Section 5.1). `server` is a leaf-list of up to 8 IPv6 addresses, and all of them share one `lifetime`. `lifetime` has no default, and the two zero-like cases stay apart because of that. | You write | Ze advertises | Hosts do | |-----------|---------------|----------| | No `lifetime` leaf | 3 x `maximum-interval` | Use the resolvers, and refresh them on each advertisement | | `lifetime 0` | 0 | Stop using these resolvers | | `lifetime 4294967295` | 4294967295 | Use the resolvers, and never expire them | <!-- source: internal/component/iface/config_ra.go -- raUnitConfig.RDNSSLifetime, EffectiveRDNSSLifetime --> ### Validation The YANG ranges bound each leaf on its own. Config verify applies the cross-leaf rules of RFC 4861 that no single range can express, and every one of them rejects the commit. | Rule | Rejected input | Message | |------|----------------|---------| | `minimum-interval` is at most 0.75 x `maximum-interval` | `minimum-interval 500` with `maximum-interval 600` | `above three quarters of maximum-interval` | | `router-lifetime` is 0, or at least `maximum-interval` | `router-lifetime 300` with `maximum-interval 600` | `below maximum-interval, and not 0` | | `preferred-lifetime` is at most `valid-lifetime` | `valid-lifetime 3600` alone | `preferred-lifetime 604800 is above valid-lifetime 3600` | | The prefix carries no host bits | `prefix 2001:db8:1::1/64` | `host bits set below the prefix length, write 2001:db8:1::/64` | | The prefix is not link-local | `prefix fe80::/64` | `the link-local prefix is not advertised` | Two of these rows are traps worth reading twice. `router-lifetime 0` is valid input, and it means that Ze advertises its prefixes and its resolvers while it is not a default router (RFC 4861 Section 4.2). The "at least `maximum-interval`" rule applies to every other value. `preferred-lifetime` defaults to 604800, so lowering only `valid-lifetime` below that number is rejected. Set both leaves together. Ze rejects a prefix with host bits rather than masking them, because a masked prefix advertises something the operator did not write. <!-- source: internal/component/iface/config_ra.go -- raValidate, raParsePrefixEntry --> ### Example A LAN unit that advertises one prefix, two resolvers, and itself as the default router: ``` interface { backend netlink; ethernet eth0 { unit 0 { ipv6 { address [ 2001:db8:1::1/64 ]; forwarding true; router-advertisement { enabled true; maximum-interval 900; minimum-interval 300; router-lifetime 1800; hop-limit 64; prefix 2001:db8:1::/64 { on-link true; autonomous true; valid-lifetime 86400; preferred-lifetime 43200; } rdnss { server [ 2001:db8:1::53 2001:db8:1::54 ]; lifetime 3600; } } } } } } ``` <!-- source: test/parse/iface-router-advertisement.ci -- accepted router-advertisement unit --> ### Send loop One goroutine owns the socket and every timer of one sender. It joins `ff02::2` to receive Router Solicitations, and it sends to `ff02::1`. Every advertisement leaves with Hop Limit 255, which RFC 4861 Section 6.1.2 makes a receiver check. A solicitation draws an answer after a random wait of 500 milliseconds at most, and a burst of solicitations draws one answer, timed from the first of them. Consecutive multicast advertisements stay 3 seconds apart, so a flood of solicitations cannot become a flood of advertisements (RFC 4861 Section 6.2.6). A sender that stops sends ONE advertisement with a Router Lifetime of 0, so each host drops Ze from its default router list at once instead of waiting the lifetime out (RFC 4861 Section 6.2.5). The RFC permits up to three. Ze sends one because three leave in a single scheduler tick, so a receiver cannot tell them apart and a link that drops one drops all three; radvd, FRR and BIRD each send one as well. Nothing leaves a link that is down. The next link-up event restarts the initial burst. <!-- source: internal/plugins/iface/ra/sender_linux.go -- openRASocket, run, sendFinal --> <!-- source: internal/core/ndp/schedule.go -- SolicitedDelay, SolicitedSendTime, MinDelayBetweenRAs --> ### Counters | Metric | Type | Labels | Counts | |--------|------|--------|--------| | `ze_iface_ra_sent_total` | CounterVec | `interface` | Every advertisement put on the wire, unsolicited and solicited together | | `ze_iface_ra_solicited_total` | CounterVec | `interface` | The advertisements that answered a Router Solicitation | A solicited advertisement increments both counters, so `ze_iface_ra_sent_total` stays the total. <!-- source: internal/plugins/iface/ra/ifacera.go -- SetMetricsRegistry, incSent, incSolicited --> ## Plugin-Owned Devices (Macvlan) Beyond the plugin-owned **address** registry (a plugin declares addresses ze should keep on an interface), iface offers a plugin-owned **device** registry for bridge-mode macvlan devices. A same-process plugin asks iface to maintain a macvlan carrying a caller-chosen unicast MAC on a parent interface; iface creates it, keeps it reconciled, and deletes it on release or after a crash. The one consumer today is VRRP (one macvlan per group carrying the RFC virtual MAC), but the mechanism is generic and carries zero VRRP knowledge. The API is a small value struct plus three calls (mirroring the address registry): - `MacvlanSpec{Name, Parent, MAC}` -- `Parent` is the parent's OS device name; mode is always bridge (no field). - `ComposeOwnedDeviceName(prefix, parentIfindex, id)` -- builds a deterministic, collision-free name `<prefix>-<parentIfindex>-<id>` that fits the 15-char IFNAMSIZ budget, rejecting (never truncating) an over-budget candidate. - `RegisterOwnedMacvlan(owner, spec)`, `UnregisterOwnedMacvlan(owner, name)`, `UnregisterOwnedMacvlans(owner)`. **Ownership marker.** At create, the device's kernel link alias (IFLA_IFALIAS) is set to `ze:owned:<owner>`, atomically with the MAC in one RTM_NEWLINK. The reconcile pass reads ownership back from the kernel rather than remembering history, so orphan cleanup works across restarts and crashes -- the one structural difference from the address registry (which must track its own stale interfaces because an address carries no owner). **Reconcile ordering.** The device pass runs inside the same reconcile as addresses, BEFORE the address loops: registered devices are created (or re-asserted on MAC/parent/MTU drift by delete + recreate) first, so a VIP registered on the device name via the address registry lands on an existing device in the same pass. Then the orphan scan deletes any macvlan carrying the `ze:owned:` alias that has no registration; deletion requires BOTH kind macvlan AND the alias, so an operator's own macvlan is never touched. Macvlans are not in the Phase 4 prune set (`zeManageable`) -- they are alias-guarded, not YANG-managed. A registered device whose parent is absent is retried when the parent appears (monitor `interface/created` / `interface/up` re-triggers the pass). Non-netlink backends (VPP, non-Linux) reject `CreateMacvlanDevice` fail-closed (exact-or-reject). A `ze doctor` check (`doctor-iface-macvlan`) probes kernel macvlan capability (create + delete a throwaway device pair), and the gauge `ze_iface_owned_devices{owner}` tracks the registry. <!-- source: internal/component/iface/macvlan.go -- MacvlanSpec, ComposeOwnedDeviceName --> <!-- source: internal/component/iface/device_owner.go -- RegisterOwnedMacvlan, alias marker, gauge --> <!-- source: internal/component/iface/config_apply.go -- reconcileOwnedDevices (device pass) --> <!-- source: internal/plugins/iface/netlink/macvlan_linux.go -- CreateMacvlanDevice --> <!-- source: internal/plugins/iface/netlink/doctor.go -- doctor-iface-macvlan capability check --> ## Bus Topics | Topic | Trigger | Payload | |---|---|---| | `interface/created` | First RTM_NEWLINK for an index | name, type, index, mtu, managed | | `interface/deleted` | RTM_DELLINK | name, type, index, mtu, managed | | `interface/up` | OperUp or OperUnknown+IFF_UP | name, index | | `interface/down` | Other oper states | name, index | | `interface/addr/added` | RTM_NEWADDR (DAD complete) | name, unit, index, address, prefix-length, family, managed | | `interface/addr/removed` | RTM_DELADDR | name, unit, index, address, prefix-length, family, managed | | `interface/dhcp/lease-acquired` | DHCPv4 ACK | name, unit, address, prefix-length, router, dns, lease-time | | `interface/dhcp/lease-renewed` | Renewal success | name, unit, address, prefix-length, router, dns, lease-time | | `interface/dhcp/lease-expired` | Lease timeout | name, unit, address, prefix-length, router, dns, lease-time | <!-- source: internal/component/iface/iface.go — Topic* constants, *Payload structs --> ## Compound Commands (Auto-Ensure Parent) When a command creates a sub-resource (VLAN unit, address) on an interface that may not exist yet, the compound form auto-creates the parent: | Command | Behavior | |---------|----------| | `create interface dummy name <name> unit <vid>` | Creates dummy `<name>` if missing, then creates VLAN `<name>.<vid>` | | `create interface dummy name <name> address <prefix>` | Creates dummy `<name>` if missing, then adds address | | `create interface bridge name <name> unit <vid>` | Creates bridge `<name>` if missing, then creates VLAN | | `create interface bridge name <name> address <prefix>` | Creates bridge `<name>` if missing, then adds address | | `create interface <name> unit <vid>` | Direct form: parent must already exist | | `create interface <name> address <prefix>` | Direct form: interface must already exist | The type keyword (`dummy`, `bridge`) is required when the parent does not exist, because the system needs to know what kind of interface to create. If the parent already exists with a different type, the command fails (e.g., `create interface dummy name br0 unit 100` rejects if `br0` is a bridge). Rollback: if the sub-resource creation fails after the parent was auto-created, the parent is deleted. Pre-existing parents are never deleted on failure. The mechanism is driven by the `ze:ensure-exists` YANG extension on the type containers (`dummy`, `bridge`). The dispatch system builds an ensure chain at registration time and wraps the leaf handler automatically. Telling those two rollback cases apart requires the creation handler to report whether it created the resource or found it already present. That report is mandatory: if a handler on an ensure-exists path returns no `created` flag, the command is refused with `ensure-exists contract violation` naming the handler, and the sub-resource step never runs. Ze fails closed here rather than guessing, because each guess corrupts a different case: assuming "created" would delete an interface the operator already owned, and assuming "not created" would leave a half-built interface behind. `veth` has no compound form. Creating a veth creates a *pair*, so it needs a peer name, and the ensure chain invokes parent handlers with no arguments (it only knows the parent's name selector). There is nowhere in `create interface veth name <name> unit <vid>` to put the peer, so veth is deliberately absent from the table above; create the pair first, then use the direct form on either end. <!-- source: internal/component/iface/yang/ze-iface-cmd.yang — ze:ensure-exists on dummy, bridge; veth has none --> <!-- source: internal/component/plugin/server/ensure.go — wrapWithEnsureChain, buildEnsureChain, wasCreated, ErrEnsureContract --> <!-- source: internal/component/iface/cmd/manage.go — idempotent handleCreateTyped; handleCreateVeth requires a peer arg --> ## Backend Implementations The `Backend` interface declares 34 methods. Three implementations ship in tree; coverage varies per platform and dataplane. The table below lists each method against the backend and whether it is wired or returns an error. `err` means the method is implemented as a stub that rejects every call with a descriptive error. `real` means the method drives the underlying mechanism. Cells with a footnote carry a caveat. | Category | Method | netlink (Linux) | VPP | stub (non-Linux) | |---|---|---|---|---| | **Lifecycle** | `CreateDummy` | real | real (CreateLoopback) | err | | | `CreateVeth` | real | err (VPP uses memif/TAP) | err | | | `CreateBridge` | real | real (BridgeDomainAddDelV2) | err | | | `CreateVLAN` | real | real (CreateVlanSubif) | err | | | `CreateTunnel` | real [1] | err (pending GoVPP tunnel API) | err | | | `CreateWireguardDevice` | real (rtnetlink) | err (requires VPP wg plugin) | err | | | `ConfigureWireguardDevice` | real (wgctrl) | err (requires VPP wg plugin) | err | | | `GetWireguardDevice` | real (wgctrl) | err (requires VPP wg plugin) | err | | | `CreateXFRM` | real (rtnetlink) | err (XFRM is Linux netlink only) | err | | | `GetXFRMInfo` | real (rtnetlink+xfrm) | err (XFRM is Linux netlink only) | err | | | `DeleteInterface` | real | real (DeleteLoopback/DeleteSubif) | err | | **Address** | `AddAddress` | real | real (SwInterfaceAddDelAddress) | err | | | `RemoveAddress` | real | real (SwInterfaceAddDelAddress) | err | | | `ReplaceAddressWithLifetime` | real | partial [2] | err | | | `AddAddressP2P` | real | err (PPP NCP not supported yet) | err | | **Route** | `AddRoute` | real | err (use fib-vpp plugin) | err | | | `RemoveRoute` | real | err (use fib-vpp plugin) | err | | | `ListRoutes` | real | err (use fib-vpp plugin) | err | | | `RouteLookup` | real (netlink RouteGet) | real (IPRouteLookupV2 LPM) | err | | | `ListKernelRoutes` | real | err [3] | err | | **Link state** | `SetAdminUp` | real | real (SwInterfaceSetFlags) | err | | | `SetAdminDown` | real | real (SwInterfaceSetFlags) | err | | **Properties** | `SetMTU` | real | real (SwInterfaceSetMtu) | err | | | `SetMACAddress` | real | real (SwInterfaceSetMacAddress) | err | | | `GetMACAddress` | real | real (via SwInterfaceDump) | err | | | `GetStats` | real | err (pending GoVPP stats API) | err | | | `ResetCounters` | real [4] | err (pending sw_interface_clear_stats) | err | | **Query** | `ListInterfaces` | real | real (SwInterfaceDump) | err | | | `GetInterface` | real | real (SwInterfaceDump) | err | | | `ListNeighbors` | real | err (pending ip_neighbor_dump) | err | | **Bridge** | `BridgeAddPort` | real | real (SwInterfaceSetL2Bridge) | err | | | `BridgeDelPort` | real | real (SwInterfaceSetL2Bridge) | err | | | `BridgeSetSTP` | real (sysfs) | err (VPP STP varies by version) | err | | **Mirror** | `SetupMirror` | real (tc mirred) | real (device SPAN) | err | | | `RemoveMirror` | real | real (device SPAN) | err | | | `ListMirrors` | real (tc filter dump) | real (SwInterfaceSpanDump) | err | | **Monitor** | `StartMonitor` | real (netlink multicast) | real (WantInterfaceEvents) | err | | | `StopMonitor` | real | real | no-op | | | `Close` | real | real | no-op | [1] `CreateTunnel` rejects an unknown kind with `unsupported tunnel kind <k>`. Valid kinds are gre, gretap, ip6gre, ip6gretap, ipip, sit, ip6tnl, ipip6. [2] VPP has no kernel-style address lifetimes. The VPP backend ignores the `validLft`/`preferredLft` arguments and installs the address without an expiry, matching the exact-or-reject rule only when the operator does not actually need DHCP lease-aware behaviour on VPP. DHCP runs against the netlink backend today. [3] On VPP the kernel FIB is not authoritative; the VPP backend rejects `ListKernelRoutes` rather than return misleading data. A VPP FIB dump via `ip_route_v2_dump` is the correct replacement and is not yet wired. [4] Linux netlink has no generic counter-reset syscall. The netlink backend returns `iface.ErrCountersNotResettable` and the dispatch layer falls back to a per-interface baseline-delta model: the current values become a baseline and `GetStats` / `ListInterfaces` / `GetInterface` subtract the baseline before returning, so the operator sees "since last clear" values. ### Netlink (Linux, default) Pure netlink via `vishvananda/netlink`, with WireGuard peer/key operations via `golang.zx2c4.com/wireguard/wgctrl`. Every method is implemented; the only caveats are `CreateTunnel` rejecting unknown kinds and `ResetCounters` using baseline-delta (both noted above). No iproute2 shell-outs. Non-Linux builds of this package compile into the stub backend described below. <!-- source: internal/plugins/iface/netlink/manage_linux.go -- CreateDummy/CreateVeth/CreateBridge/CreateVLAN/Delete/AddAddress/RemoveAddress/ReplaceAddressWithLifetime/AddAddressP2P/SetAdminUp/SetAdminDown/SetMTU --> <!-- source: internal/plugins/iface/netlink/tunnel_linux.go -- CreateTunnel switch over 8 kinds --> <!-- source: internal/plugins/iface/netlink/wireguard_linux.go -- CreateWireguardDevice/ConfigureWireguardDevice/GetWireguardDevice --> <!-- source: internal/plugins/iface/netlink/bridge_linux.go -- BridgeAddPort/BridgeDelPort/BridgeSetSTP --> <!-- source: internal/plugins/iface/netlink/mirror_linux.go -- SetupMirror/RemoveMirror via tc --> <!-- source: internal/plugins/iface/netlink/route_linux.go -- AddRoute/RemoveRoute/ListRoutes/ListKernelRoutes --> <!-- source: internal/plugins/iface/netlink/neighbor_linux.go -- ListNeighbors, ResetCounters returning ErrCountersNotResettable --> <!-- source: internal/plugins/iface/netlink/show_linux.go -- ListInterfaces/GetInterface/GetStats --> <!-- source: internal/plugins/iface/netlink/monitor_linux.go -- StartMonitor/StopMonitor --> <!-- source: internal/component/iface/counters.go -- ResetCounters baseline-delta fallback --> ### VPP (opt-in, via GoVPP) Selected by the `backend vpp` leaf. 17 methods drive the VPP binary API through GoVPP; 16 return `errNotSupported` with a string naming the missing GoVPP call or the plugin gap. The channel to VPP is acquired lazily on first method call; before the first successful acquire, every method returns `iface.ErrBackendNotReady` so the reconciliation phase can retry. Use the VPP backend when VPP owns the dataplane. Use netlink elsewhere. Routes, stats, counter reset, neighbour table, mirrors, and STP are the main gaps against feature parity. <!-- source: internal/plugins/iface/vpp/ifacevpp.go -- full Backend implementation, errNotSupported reasons per method --> <!-- source: internal/plugins/iface/vpp/query.go -- ListInterfaces/GetInterface/GetMACAddress/SetMACAddress via SwInterfaceDump, SwInterfaceSetMacAddress --> <!-- source: internal/plugins/iface/vpp/monitor.go -- StartMonitor/StopMonitor via WantInterfaceEvents + SubscribeNotification --> <!-- source: internal/plugins/iface/vpp/naming.go -- ze short name <-> VPP SwIfIndex map --> ### Stub (non-Linux) On darwin, macOS, BSD, and Windows the netlink backend package compiles to a stub whose constructor succeeds but whose every method returns `"interface management not supported on <GOOS>"`. `StopMonitor` and `Close` are no-ops. The stub exists so the rest of the daemon can load and the binary remains testable on developer machines; real interface management requires Linux. The stub never installs itself as the default silently. A macOS daemon that actually tries `ze interface show` or any config-driven reconciliation sees the explicit error and rejects under exact-or-reject. <!-- source: internal/plugins/iface/netlink/backend_other.go -- stubBackend, unsupported() returning "not supported on <GOOS>" --> --- ### Page: Interoperability Testing https://ze-software.net/features/interoperability-testing/ # Interoperability Testing <!-- source: internal/le/interoplab/bgp/run.go -- native interop runner --> <!-- source: internal/le/interoplab/lab.go -- scenario lifecycle --> Ze ships a Docker-based interoperability test suite that verifies protocol correctness against real third-party BGP implementations. Tests are not mocks -- they launch actual daemon instances in containers and exchange real BGP messages. | Feature | Description | |---------|-------------| | Target daemons | FRR, BIRD, GoBGP (tested), rustbgpd, RustyBGP, freeRtr (Dockerfiles ready) | | Scenario count | 100+ scenarios covering core BGP protocol and extensions plus adjacent protocols (RPKI, BMP, IS-IS, OSPF/OSPFv3) | | Runner | `./le integration interop` (all) or `INTEROP_SCENARIO=<name> ./le integration interop` (single) | | Container images | Customizable via env vars (e.g., `FRR_IMAGE=quay.io/frrouting/frr:10.3`) | ## Scenarios The table below is a representative subset covering the core BGP protocol and extensions. The full suite has over 100 scenarios; the remainder exercise RPKI, BMP, IS-IS, and OSPF/OSPFv3 interoperability against FRR. | # | Scenario | Target | |---|----------|--------| | 01 | eBGP IPv4 | FRR | | 02 | eBGP IPv4 | BIRD | | 03 | iBGP | FRR | | 04 | 4-byte ASN | FRR | | 05 | Routes from | FRR | | 06 | Routes from | BIRD | | 07 | Routes to | FRR | | 08 | Triangle topology | multi | | 09 | Route withdrawal | FRR | | 10 | IPv6 eBGP | FRR | | 11 | Add-Path | FRR | | 12 | Route Refresh | FRR | | 13 | Graceful Restart | FRR | | 14 | Route Server | FRR | | 15 | Standard communities | FRR | | 16 | Extended communities | FRR | | 17 | MD5 authentication | FRR | | 18 | eBGP | GoBGP | | 19 | Routes | GoBGP | | 20 | BGP Roles | FRR | | 21 | BGP Roles | GoBGP | | 22 | EVPN | FRR | | 23 | VPN | FRR | | 24 | FlowSpec | FRR | | 25 | IPv6 eBGP | BIRD | | 26 | IPv6 eBGP | GoBGP | | 27 | Multihop eBGP | FRR | | 28 | EVPN | GoBGP | | 29 | VPN | GoBGP | | 30 | FlowSpec | GoBGP | | 31 | Multihop eBGP | BIRD | | 32 | Multihop eBGP | GoBGP | | 33 | BFD failover | FRR | | 34 | ECMP | FRR + GoBGP | | 35 | SRv6 VPNv6 | FRR | | 36 | Remove private AS | FRR | | 37 | Remove private AS via AS4_PATH | FRR + BIRD | Only Ze and rustbgpd ship cross-implementation interop test suites among open-source BGP daemons. Ze's suite has more scenarios and tests against more target implementations. --- ### Page: Self-Documenting System https://ze-software.net/features/introspection/ # Self-Documenting System <!-- source: internal/component/config/yang/cli/main.go -- ze schema subcommands --> <!-- source: internal/plugins/env/env.go -- ze env subcommands --> <!-- source: cmd/ze/help_ai.go -- ze help ai output --> <!-- source: internal/le/inventory/inventory.go -- Answer --> <!-- source: internal/le/command/list/commandlist.go -- Answer --> <!-- source: internal/le/docvalid/actions.go -- Answer --> <!-- source: internal/le/docvalid/actions.go -- Answer --> Ze is self-documenting: every plugin, environment variable, RPC, event type, and CLI command is registered at startup and discoverable at runtime. Nothing exists unregistered -- the system enforces this with compile-time registration (`init()`) and runtime abort on unregistered access (`env.MustRegister()`). ## Runtime Introspection | Command | What it shows | |---------|---------------| | `ze schema list` | All registered YANG modules (65 modules) | | `ze schema show <module>` | Full YANG content for a module | | `ze schema methods [module]` | All RPCs with parameters from YANG | | `ze schema events` | All notification/event types from YANG | | `ze schema handlers` | Which handler serves which YANG module | | `ze schema protocol` | Protocol version and wire format info | | `ze env list` | All registered environment variables with types and defaults | | `ze env list -v` | Same, plus current values | | `ze env get <key>` | Details for a single environment variable | | `ze show plugin list` | All registered plugins with families, RFCs, and capability codes | | `ze show plugin declarations` | One row for each plugin, with the state that says whether its declaration was read | | `ze show plugin declarations config <path>` | The same, plus a row for each plugin that config file names | | `ze help command [filter]` | Full command catalog, filterable, with descriptions | | `ze help command --json` | Command catalog as JSON (for wiki generation, tooling) | | `ze help ai` | Machine-readable command reference generated from live binary | | `ze help ai api` | Daemon API endpoints (`ze-show:*`, `ze-set:*`, ...) with parameters | An in-tree plugin's declarations are in reach of both catalogs. `ze help command --json` and `./le command list` read the compiled command tree in their own process and start no plugin, but a registered plugin puts the same declarations on its `registry.Registration`: `Commands` for what each answer holds and `Pipes` for the aliases it puts on its commands. So both catalogs name every plugin command and publish its `answer-shape`, `column-orders`, `address-fields` and `pipe-aliases`. A command the plugin marks hidden is left out of both, the way the daemon leaves it out of completion. One registration is still out of their reach: an EXTERNAL plugin's. It registers nothing in the composition root, so its declaration reaches a running daemon alone, through `show command help "<name>"` and Tab completion in the interactive session. That answer carries a command's two help texts under `description` (the one-line summary) and `long-help` (the explanation), for a builtin and for a plugin command alike. `ze show plugin declarations` closes that last gap. One row for each plugin this binary carries, one for each plugin the named config file declares, and no plugin omitted. The plugins this binary carries are the set `ze show plugin list` answers for, so a plugin whose own `init()` recorded a setup outcome and never completed its registration keeps a row in both commands. The config block decides where a row's declaration comes from, because the block says which binary holds the plugin's code. An `internal` block names code this binary carries, so the row is answered from the compiled-in `registry.Registration` and no process is started. An `external` block names another program, whatever name the block gives it, so the row is answered by starting that program in query mode (`ZE_PLUGIN_MODE=declare`), with every `ze.plugin.` variable removed from the child's environment, and reading the declaration it writes to stdout. A block reading `external mrt { run "/opt/vendor/my-mrt" }` is a vendor's program that took a name Ze also uses, and it answers for itself. Anything else the child writes is ignored, so a banner or a log line cannot corrupt the answer. The state field carries one of five values, so absence never stands in for an answer: | State | What it means | |-------|---------------| | `declared` | an answer arrived carrying declarations | | `declared-none` | an answer arrived carrying an empty declaration | | `no-answer` | the process ran and wrote no declaration, which is what a plugin with no query mode in it does | | `unstartable` | the process could not be started | | `timeout` | the process started, wrote no declaration inside the budget, and was stopped | The budget is 5 seconds, the same wait a startup stage gets, and `ze.plugin.query.timeout` sets it. A value of zero or less is refused rather than read as "wait no time". A plugin that runs out of the budget is stopped with everything it started: the run string is given to a shell, and a shell that runs more than one command holds the plugin as its own child. <!-- source: internal/component/plugin/declarations.go -- dataDeclarations, declarationRows, queriedDeclarationRow --> <!-- source: internal/component/config/register_plugin_declarations.go -- readConfiguredPlugins --> <!-- source: cmd/ze/help_command.go -- collectCommands, extractPipes --> <!-- source: internal/component/command/declared.go -- DeclaredForCommand --> <!-- source: internal/plugins/meta/cmd/help.go -- commandHelp, pipeAliasHelp --> ## Build-Time Verification | Native action | What it does | |---------------|--------------| | `./le inventory` | Reports plugins, YANG modules, RPCs, families, tests, and packages | | `./le command list` | Reads every CLI command from the compiled registries | | `./le docvalid command-contract` | Cross-checks YANG commands and handlers | | `./le docvalid doc-drift` | Detects documentation drift | Each plugin the inventory reports also carries the package directory it registers from and every YANG file beside it. Both are DERIVED, so no plugin declares either: the directory is the package the plugin's engine function was compiled in, and the file list is the directory holding the module the registration carries. The public plugin catalog publishes both. <!-- source: internal/le/inventory/plugins.go -- pluginPackageDir, pluginYANGFiles --> ## Design Principle The self-documenting property emerges from the registration architecture: - **Plugins** register via `registry.Register()` in `init()` -- name, families, capabilities, YANG schema, dependencies, event types, send types, features - **Environment variables** register via `env.MustRegister()` -- calling `env.Get()` with an unregistered key aborts the process - **RPCs** are defined in YANG schemas -- no command handler exists without a schema definition - **CLI dispatch** is auto-generated from registrations -- no hand-wired dispatch tables - **Tab completion** is driven by YANG schemas -- new config leaves appear automatically - **Web UI** is generated from YANG schemas -- no hardcoded forms The result: adding a plugin with a YANG schema automatically updates the CLI, web UI, tab completion, schema discovery, inventory, and environment variable listing. No manual wiring. No other open-source BGP daemon provides runtime introspection of its own capabilities. --- ### Page: Looking Glass https://ze-software.net/features/looking-glass/ # Looking Glass Ze includes a built-in looking glass that exposes BGP session state and route information via both an HTMX web UI and a birdwatcher-compatible REST API. The looking glass runs as a separate HTTP server on its own port (default 8443). It is read-only and open by default, because a looking glass is a public surface. It serves TLS by default, with a self-signed certificate unless the `certificate` leaf names an entry in the PKI store. An optional bearer `token` gates every route when you set one. | Feature | Description | |---------|-------------| | Peer dashboard | Live peer table with state, ASN, route counts, SSE updates | | Route lookup | Prefix and IP containment search with full attribute display | | AS path search | Pattern-based AS path filtering | | Community search | Standard and large community filtering | | AS path topology graph | Server-side SVG visualization of AS path DAGs | | Birdwatcher REST API | Alice-LG compatible JSON endpoints under `/api/looking-glass/`. Specified in RFC 2119 terms in [Birdwatcher compatibility](https://github.com/ze-software/ze/blob/main/docs/architecture/api/birdwatcher-compat.md) | | htmx web UI | Server-rendered HTML pages under `/lg/` with fragment updates. htmx 4.0.0 is embedded, with `hx-sse.min.js` on the peers page | | templ rendering | Every page and fragment is written in a `.templ` source and compiled to Go, and each view takes a typed struct. The two SVG graph builders keep their markup in Go, because every attribute they write is a coordinate the layout pass computed | | TLS certificate | `certificate` names a `pki { certificate <name> }` entry. The listener serves that leaf and every intermediate the store holds, so a visitor's browser accepts the chain. A named certificate needs no blob storage, so it serves on a deployment that never ran `ze init` | | Certificate fails closed | A name the PKI store does not hold stops the daemon at start, and a reload that introduces one is rejected as a whole. Ze never falls back to the self-signed certificate for a configured name. `ze doctor` reports a broken reference, and an expiry less than 30 days away, before the operator commits | | Certificate rotation | A commit that changes the name installs the new chain on the running server. The listener keeps its socket, and the next handshake serves the new chain. A looking glass serving plaintext takes no rotation, and a restart is what puts a certificate on the wire | | YANG configuration | `environment/looking-glass` block with enabled, server (ip, port), tls (default true), certificate, and token settings | <!-- source: internal/component/lg/server.go -- LGServer, HTTP lifecycle --> <!-- source: internal/component/lg/handler_api.go -- Birdwatcher REST API handlers --> <!-- source: internal/component/lg/handler_ui.go -- HTMX UI handlers --> <!-- source: internal/component/lg/view.go -- typed view models: layoutView, searchView, peerRow --> <!-- source: internal/component/lg/markup_check_test.go -- lgMarkupExempt: the two exempt drawings --> <!-- source: internal/component/lg/yang/ze-lg-conf.yang -- leaf certificate --> <!-- source: cmd/ze/hub/service_tls.go -- listenerTLSMaterial, the named-certificate precedence --> <!-- source: internal/component/lg/server.go -- UpdateTLSCertificate, getCertificate --> <!-- source: internal/component/lg/doctor.go -- the looking-glass certificate reference check --> <!-- source: internal/component/lg/handler_graph.go -- AS path graph handler --> <!-- source: internal/component/lg/graph.go -- Graph data model --> <!-- source: internal/component/lg/layout.go -- Layout algorithm and SVG rendering --> See [Looking Glass Guide](../../guides/looking-glass/index.md) for configuration and usage. ### AS Path Topology Graph The looking glass includes a server-side SVG graph that visualizes AS path topology for any prefix. When looking up a route, clicking "Show topology" renders a directed acyclic graph where nodes represent autonomous systems and edges represent peering links. | Feature | Description | |---------|-------------| | Server-side SVG | Rendered entirely in Go, no external dependencies (no GraphViz, no WASM, no JS graph library) | | Layered layout | Sugiyama-inspired left-to-right layout with source ASes on the left, origin on the right | | AS prepending | Consecutive duplicate ASNs collapsed to a single node | | Multi-path | Multiple AS paths to the same prefix shown as a branching DAG | | ASN labels | Each node shows AS number and organization name (when decorator is available) | | Node cap | Graphs limited to 100 nodes to prevent resource exhaustion | | HTMX integration | Loaded as an inline SVG fragment via `GET /lg/graph?prefix=X` | <!-- source: internal/component/lg/graph.go -- buildGraph, extractASPath --> <!-- source: internal/graph/graph.go -- BuildGraphFromPaths, DeduplicateASPath --> <!-- source: internal/component/lg/layout.go -- computeLayout, renderGraphSVG --> <!-- source: internal/component/lg/handler_graph.go -- handleGraph endpoint --> --- ### Page: MCP Integration https://ze-software.net/features/mcp-integration/ # MCP Integration <!-- source: internal/component/mcp/tools.go -- MCP tool dispatch primitives --> <!-- source: internal/test/cli/cmd_mcp.go -- MCP test client --> Ze includes an MCP (Model Context Protocol) server that makes the BGP daemon **AI-ready**. Any AI assistant (Claude, GPT, or custom agents) can connect via MCP and fully control Ze -- the same operations available through the CLI are accessible programmatically through typed tools. ## AI-Ready BGP Operations The MCP server exposes typed tools with structured parameters, so AI assistants can manage BGP without parsing CLI output: | Tool | Description | |------|-------------| | `ze_execute` | Run **any** CLI command -- full daemon control (the escape hatch) | | `ze_reference` | Full machine-readable reference for this daemon (commands, RPC endpoints, dispatch keys, plugins, families, services); same JSON as `ze help ai --json`. Call first to discover capabilities. | | `ze_announce` | Announce routes with typed parameters (origin, next-hop, communities, prefixes) | | `ze_withdraw` | Withdraw routes | | `ze_show_bgp` | BGP peer state, ASN, uptime, and summary views (auto-generated from `show bgp ...`) | | `ze_request_peer` | Peer lifecycle: teardown, pause, resume, flush (auto-generated from `request peer ...`) | `ze_execute` and `ze_reference` are the two handcrafted tools. Every other tool is auto-generated from the command registry at runtime: a command prefix becomes a tool name (`show bgp rib` becomes `ze_show_bgp_rib`), with an `action` enum and optional `arguments` and `peer` parameters. New commands are exposed automatically without code changes. A plugin command registered with `Hidden` true reaches no tool list. The flag already removed the command from completion and from help, and `buildCommandMeta` is the one source both the MCP tool list and the API command list read, so the skip covers both surfaces. <!-- source: internal/component/mcp/tools.go -- toolName, generateTools --> <!-- source: cmd/ze/hub/command_meta.go -- buildCommandMeta: the Hidden skip --> The `ze_execute` tool is the key to full control: anything you can do in `ze cli` (interactive or `ze cli -c` for one-shot commands), an AI can do via MCP. This includes: - **Route management:** `send bgp * update text origin set igp nhop set 1.1.1.1 nlri ipv4/unicast add 10.0.0.0/24` - **RIB queries:** `show bgp rib received`, `show bgp rib sent`, `clear bgp rib in` - **Peer lifecycle:** `show bgp peer list`, `request peer 10.0.0.1 teardown 6`, `delete bgp peer <sel>` - **Configuration:** `request commit start window1`, route changes, `request commit end window1` - **Cache operations:** `show cache`, `send bgp <sel> cached <id>` - **Event subscription:** `request subscribe bgp/update` - **Schema discovery:** `command-list`, `command-help <name>` ## Starting the MCP Server ``` ze start --mcp 8080 # start from stored (blob) config; run `ze init` first ze --mcp 8080 config.conf # start from a config file ``` Or via config: ``` environment { mcp { enabled true server main { ip 127.0.0.1 port 8080 } } } ``` Environment variable overrides: `ze.mcp.listen=ip:port`, `ze.mcp.enabled=true`, `ze.mcp.token=<secret>`. Defaults to `127.0.0.1:8080` (security: local-only unless explicitly overridden via `ze.mcp.listen`). Bearer token auth available via `--mcp-token` flag, `ze.mcp.token` env var, or the config `token` leaf. ## AI Command Reference ``` ze help ai ze help ai api # daemon API endpoints (ze-show:*, ze-set:*, ...) ``` Generates a machine-readable command reference from code, suitable for feeding to an AI as context. Lists all available commands with their parameters, descriptions, and examples. The legacy `ze help --ai` flag form is still accepted. ## Example: AI-Driven Route Announcement An AI assistant connected via MCP can: 1. Check peer state: `ze_show_bgp` returns structured JSON with all peer status 2. Announce a route: `ze_announce` with origin=igp, next-hop=10.0.0.1, prefixes=[10.0.0.0/24] 3. Verify propagation: `ze_execute` with command `show bgp rib sent peer peer1 family ipv4/unicast` 4. Withdraw if needed: `ze_withdraw` with the same prefixes All without parsing text output -- each tool returns structured data. ## Protocol (2026-07-28) Ze speaks MCP protocol revision `2026-07-28` and no other. The profile is stateless, and every message is its own HTTP POST to `/mcp`. Each POST carries its own protocol version, client capabilities and credential. Three standard headers and a `_meta` block inside `params` hold them. There is no `initialize` handshake, no session, no `Mcp-Session-Id`, no GET stream, and no server-initiated request. <!-- source: internal/component/mcp/streamable.go -- ProtocolVersion, supportedProtocolVersions, handlePOST --> <!-- source: internal/component/mcp/meta.go -- parseRequestMeta --> Two consequences are worth stating plainly: - **Authentication runs on every request**, so a revoked token stops working on the very next call rather than at session expiry, and there is no long-lived identifier that acts as a bearer credential in its own right. <!-- source: internal/component/mcp/streamable.go -- authenticate --> - **Elicitation is inverted, not gone.** The revision forbids a server to send an independent JSON-RPC request on any stream, so a server can no longer push a prompt. The server RETURNS the prompt instead: `ze_execute` called without a `command` answers `resultType: "input_required"` with an `inputRequests` map, and that map carries an `elicitation/create` request. The client then retries the original call with `inputResponses`, which carries the value. Ze attaches no `requestState`, so it holds nothing between the two requests and authenticates each one on its own. A client that did not declare **form-mode** elicitation is never prompted, and it gets the missing-argument error instead. See [MCP Elicitation](../../guides/mcp/elicitation/index.md) for the full round trip. <!-- source: internal/component/mcp/mrtr.go -- inputRequiredForMissingCommand, elicitationFormSupported --> <!-- source: internal/component/mcp/tools.go -- ze_execute handler --> Capability discovery is a single optional call: `server/discover` returns the supported versions, the server's capabilities, and natural-language instructions. Its `capabilities.extensions` names two extensions, each with an empty settings object: `io.modelcontextprotocol/ui`, the MCP Apps extension, and `io.modelcontextprotocol/tasks`, the Tasks extension. The second extension is what makes the `task` result type interpretable, because a client can reject a `resultType` that no advertised extension defines. <!-- source: internal/component/mcp/discover.go -- serverDiscover, serverCapabilities --> ### Cacheable results `server/discover`, `tools/list`, `resources/list` and `resources/read` return <!-- doc-links: ignore (JSON-RPC method name, not a path) --> `ttlMs` and `cacheScope`, so a client can hold a result and does not re-fetch it every turn. The tool inventory and the discovery result are fresh for 60 seconds. The embedded UI assets are fresh for one hour. Every one of them is scoped `private`, so a shared gateway is not permitted to serve one caller's response to another. `tools/call` and the `tasks/*` methods return no hints. Their results are not <!-- doc-links: ignore (JSON-RPC method name, not a path) --> cacheable. Ze has no push invalidation. That 60-second TTL is therefore also the window in which a client can still offer a command that a config reload has removed. A call to that command returns an error. The protocol names such an error as grounds for an early re-fetch, so the stale entry usually clears after one failed call. <!-- source: internal/component/mcp/caching.go -- cacheTTLByMethod, cacheScopePrivate --> ### MCP Apps Tool descriptors for command groups carrying a `ze:ui-resource` YANG annotation include `_meta.ui`, pointing at a `ui://` asset the host renders in a sandboxed panel. That metadata is emitted only when the request declared the `io.modelcontextprotocol/ui` extension in a form compatible with the HTML bundles Ze serves. A host without MCP Apps support gets the same tool list, the same tools and the same behavior, minus the panel metadata. Ze rejects nothing. <!-- source: internal/component/mcp/apps.go -- clientSupportsUIApps, gateUIMeta --> ### Resources `resources/list` and `resources/read` serve every conformant caller, with no <!-- doc-links: ignore (JSON-RPC method name, not a path) --> client-capability gate. `resources` is a member of `ServerCapabilities`, not of `ClientCapabilities`, whose complete member set in this revision is `experimental`, `roots`, `sampling`, `elicitation` and `extensions`. A conformant client therefore never declares `resources`, and a gate on it refused every conformant caller while `server/discover` advertised the capability and `tools/list` published `_meta.ui.resourceUri` pointing at those same assets. <!-- doc-links: ignore (JSON-RPC method name, not a path) --> A `ui://` URI is validated before any read: the scheme must match, the cleaned path must equal the given path, and the depth is capped at 8 segments. A URI that fails validation gets `invalid uri`, and one that resolves to nothing gets `resource not found`. <!-- source: internal/component/mcp/resources.go -- resourcesList, resourcesRead, validateResourceURI --> ## Testing `ze-test mcp` provides a functional test client with `wait-established` synchronization for CI pipelines. It also provides a `probe-*` directive family, which drives deliberately-malformed requests at the conformance surface (header mismatch, unsupported version, malformed `_meta`, GET and DELETE). Background execution is driven by `task-call` (an ordinary `tools/call` the <!-- doc-links: ignore (JSON-RPC method name, not a path) --> server must answer with a task handle), its twin `call-sync` (which requires a synchronous answer and no taskId), then `task-get`, `task-result`, `task-update`, `task-cancel` and `task-wait`. The `--tasks` flag declares the `io.modelcontextprotocol/tasks` extension on every request. A run without the flag is itself a test, because the server must still serve an undeclaring client synchronously. <!-- source: internal/test/cli/cmd_mcp.go -- taskDirective --> See [MCP Guide](../../guides/mcp/overview/index.md) for details and [MCP Remote Access](../../guides/mcp/remote-access/index.md) for tunneling. --- ### Page: SRv6 (Segment Routing over IPv6) https://ze-software.net/features/srv6/ # SRv6 (Segment Routing over IPv6) <!-- source: internal/component/bgp/plugins/rib/pool/srv6sid.go -- SRv6 SID extraction and transposition --> <!-- source: internal/component/bgp/plugins/rib/rib_bestchange.go -- SID lookup at best-path emission --> <!-- source: internal/component/sysrib/sysrib.go -- SID resolvability check and FIB emission --> <!-- source: internal/plugins/fib/kernel/nexthop_linux.go -- Linux SEG6 encap --> <!-- source: internal/plugins/fib/vpp/srv6.go -- VPP SR steer --> <!-- source: internal/component/bgp/message/rfc7606.go -- PrefixSID attribute validation --> <!-- source: internal/component/bgp/reactor/session_validation.go -- EBGP PrefixSID filtering --> <!-- rfc: rfc/short/rfc8669.md -- BGP Prefix-SID attribute (code 40) --> <!-- rfc: rfc/short/rfc9252.md -- SRv6 overlay services --> Ze receives BGP routes carrying SRv6 Prefix-SID attributes (RFC 8669, RFC 9252), extracts SRv6 SIDs, validates them, and programs encapsulation into the FIB. Operates as an ingress PE: consumes received SRv6 SIDs but does not originate them. | Feature | Description | |---------|-------------| | Attribute parsing | PrefixSID (code 40) stored as opaque bytes; SRv6 SID extracted lazily at best-path time | | L3/L2 Service TLVs | Types 5 (L3 Service) and 6 (L2 Service) per RFC 9252 Section 3.1 | | SID extraction | First SRv6 SID Information Sub-TLV (type 1) within the Service TLV | | Transposition | SID Structure Sub-Sub-TLV reconstructs full SID from NLRI label bits (VPN/EVPN) | | Path ineligibility | Route with SRv6 TLVs but no valid SID excluded from best-path selection | | SID resolvability | SRv6 SID must have a covering route in Loc-RIB before FIB installation | | EBGP filtering | PrefixSID from EBGP peers discarded unless `accept-srv6-prefix-sid` is set | | EBGP propagation | PrefixSID removed on every rail that writes an UPDATE unless `propagate-srv6-prefix-sid` is set: the two forward rails, the two origination rails, and the API/readvertise announce rail | | Validation | Malformed SRv6 Service TLVs trigger treat-as-withdraw (RFC 9252 Section 3.4) | | Propagation | PrefixSID preserved on zero-copy forward; stripped when the next-hop changes, and stripped at the SR domain boundary | | Linux FIB | SEG6 lwtunnel encap via netlink | | VPP FIB | SR steering policy via GoVPP `sr_steering_add_del` | ## Configuration EBGP peers require explicit opt-in, in each direction. IBGP peers accept and advertise PrefixSID by default. ``` bgp { peer pe1 { session { accept-srv6-prefix-sid true propagate-srv6-prefix-sid true } } } ``` | Option | Location | Default | Description | |--------|----------|---------|-------------| | `accept-srv6-prefix-sid` | `bgp/peer/session` | `false` | Accept PrefixSID attribute from this EBGP peer (RFC 8669 Section 4) | | `propagate-srv6-prefix-sid` | `bgp/peer/session` | `false` | Advertise PrefixSID attribute to this EBGP peer (RFC 8669 Section 8) | Both leaves say the same thing about one neighbor: it is inside ze's SR domain. RFC 8669 Section 8 puts the boundary at "a single SR/administrative domain that may include one or more ASes", so the boundary is not the AS boundary and ze cannot derive it from the ASN pair. Set both leaves on an EBGP neighbor that is part of the same SR domain, and leave both unset on every other EBGP neighbor. Set neither on an IBGP peer: the section governs propagation to other ASes, so it does not reach a peer in this one. No additional configuration is needed for IBGP sessions or for FIB programming. When an SRv6 SID is present on a best-path route and the SID is resolvable, the FIB backend programs the encapsulation automatically. ## Data Flow ``` BGP UPDATE with PrefixSID (attr 40) | v Wire parser: stores PrefixSID in OtherAttrs (opaque bytes) | v RFC 7606 validator: checks TLV structure, rejects malformed | v EBGP filter: discards attr 40 unless accept-srv6-prefix-sid is set | v RIB best-path: IsSRv6Ineligible() excludes routes with broken SRv6 TLVs | v Best-path emission: lookupSRv6SIDForBest() extracts SID from OtherAttrs | For VPN/EVPN: applies transposition (label bits -> SID function) | v sysrib: stores srv6SID on protocolRoute, tracks SID for resolution | Suppresses FIB emission if SID has no covering route in Loc-RIB | Cascade re-evaluates when SID reachability changes | v FIB backend: Linux: netlink.SEG6Encap{Mode: encap, Segments: [SID]} VPP: sr.SrSteeringAddDel{BsidAddr: SID, TrafficType: IPv4/IPv6} ``` The egress side is a separate decision, taken once per destination peer: ``` Route selected for a destination peer | v prefixSIDAllowedTo(isIBGP, propagate-srv6-prefix-sid) | true -> attr 40 goes out unchanged | false -> attr 40 is removed for this peer alone v Forward rails: applyFactsPrefixSID records an attribute suppression Origination rails: the configured PrefixSID and any raw attribute 40 are dropped ``` <!-- source: internal/component/bgp/reactor/forward_prefix_sid.go -- prefixSIDAllowedTo --> ### The API and readvertise announce rail The fifth rail asks the same question at the same site. `buildBatchAnnounceUpdate` (`internal/component/bgp/reactor/reactor_api_batch.go`) copies the caller's attribute block verbatim, so "say nothing" would emit attribute 40; it drops the code when `prefixSIDAllowedTo` answers no, beside the RFC 4271 Section 5.1.5 LOCAL_PREF drop. The destination's leaf joins `announceBuildKey`, so two peers of one update group that answer differently no longer share a built UPDATE. The rail carries an API announce, a grouped announce, and the RFC 9494 stale readvertise (`sendStaleReadvertise`), which replays a stored received block. <!-- source: internal/component/bgp/reactor/reactor_api_batch.go -- buildBatchAnnounceUpdate --> ## Transposition (VPN/EVPN) For SAFI 128 (VPN) and SAFI 70 (EVPN), RFC 9252 Section 3.2.1 specifies that part of the SRv6 SID function bits are encoded in the MPLS label field of the NLRI instead of in the SID Information Sub-TLV. Ze reconstructs the full SID: 1. Extract partial SID from SID Information Sub-TLV 2. Read transposition parameters from SID Structure Sub-Sub-TLV (offset, length) 3. Read label from NLRI (stored as side-data in PeerRIB) 4. OR label bits into SID at the specified bit offset When Transposition Length is 0, no reconstruction is needed (the full SID is in the Sub-TLV). This is the common case for IPv6 unicast. ## FIB Backends ### Linux Kernel Routes with SRv6 SID are installed with SEG6 lightweight tunnel encapsulation: ``` ip route add <prefix> via <nexthop> encap seg6 mode encap segs <SID> ``` Implemented via `netlink.SEG6Encap` in `buildRichRoute`. MPLS labels take precedence: if both Labels and SRv6SID are present, MPLS encap is used. ### VPP Routes with SRv6 SID are steered via VPP's SR policy infrastructure: ``` sr_steering_add_del bsid=<SID> prefix=<prefix> traffic_type=IPv4|IPv6 ``` Implemented via GoVPP `sr.SrSteeringAddDel`. Requires `go.fd.io/govpp/binapi/sr` (vendored). SRv6 steering takes precedence over both MPLS and plain routes in the VPP dispatch logic. ## RFC Compliance ### RFC 8669 (Prefix-SID Attribute) | Requirement | Section | Status | |-------------|---------|--------| | Attribute code 40, optional transitive | 3 | Implemented | | TLV format: 1B type + 2B length | 3 | Implemented | | Unknown TLVs preserved on propagation | 3 | Implemented (opaque forwarding) | | EBGP: discard unless configured to accept | 4 | Implemented (`accept-srv6-prefix-sid`) | | Propagation to other ASes explicitly configured | 8 | Implemented (`propagate-srv6-prefix-sid`): every rail that writes an UPDATE asks | | Malformed attribute: attribute-discard | 6 | Implemented (RFC 7606 validator) | ### RFC 9252 (SRv6 Overlay Services) | Requirement | Section | Status | |-------------|---------|--------| | L3 Service TLV (type 5) | 3.1 | Implemented | | L2 Service TLV (type 6) | 3.1 | Implemented | | SID Information Sub-TLV (type 1) | 3.2 | Implemented | | First SID Sub-TLV preferred | 3.2 SHOULD | Implemented | | SID Structure Sub-Sub-TLV | 3.2.1 | Implemented | | Transposition reconstruction | 3.2.1 | Implemented (VPN/EVPN) | | LBL+LNL+FL+AL <= 128 validation | 3.2.1 | Implemented (errata 7817) | | NH unchanged: preserve TLVs | 3.3 | Implemented (zero-copy forward) | | NH changed: strip PrefixSID | 3.3 | Implemented (AttrModSuppress) | | Malformed Service TLV: treat-as-withdraw | 3.4 | Implemented | | Path ineligibility (no valid SID) | 5 | Implemented | | SID resolvability check | 5 | Implemented (Loc-RIB LPM) | ## Limitations - **Ingress PE only.** Ze consumes SRv6 SIDs from received routes. It does not allocate or advertise local SRv6 SIDs. When re-advertising with a changed next-hop, PrefixSID is stripped. - **No SRv6 capability negotiation.** PrefixSID is optional-transitive, so it propagates without negotiation. Ze does not signal SRv6 support via capabilities. - **MPLS precedence.** If a route carries both MPLS labels and an SRv6 SID, MPLS encap is used (kernel backend). VPP dispatches SRv6 first. - **No SRv6 policy.** Ze programs single-SID encapsulation. SRv6 segment lists (multi-hop SR paths) are not supported. --- ### Page: Web Interface https://ze-software.net/features/web-interface/ # Web Interface Ze includes an HTTPS web interface for configuration viewing, editing, and runtime command execution through a browser. | Feature | Description | |---------|-------------| | YANG-driven UI | Config tree navigation generated from YANG schemas | | Finder navigation | macOS-style column browser; named containers above unnamed with separator | | List table view | Lists with YANG `unique` constraints shown as interactive tables with inline editing | | Config viewing | Browse the config tree with breadcrumb navigation | | Config editing | Set and delete leaf values with per-user draft sessions | | Inline diff | Review pending changes before committing | | Session authentication | Login page with session cookies; same user database as SSH | | JSON API | Content negotiation via `Accept` header or `?format=json` query parameter; Basic Auth for API clients | | CLI bar | Integrated command bar with the same grammar as the SSH CLI (edit, set, delete, show, commit, discard) | | Terminal mode | Full terminal mode in the browser with scrollback and prompt | | Tab completion | Autocomplete candidates served via JSON endpoint | | Live updates | SSE notifications when another user commits config changes | | HTTPS only | TLS 1.2 minimum; auto-generated ECDSA P-256 self-signed certificate when no cert is provided | | PKI certificate | `environment.web.certificate` names a `pki {}` store entry to serve instead, sending the leaf and every stored intermediate. A configured name that does not resolve makes ze exit at start. ze never falls back to self-signed for it. Rotates on reload without rebinding, so open SSE streams survive. See [TLS Certificates From the PKI Store](../../guides/configuration-model/index.md#tls-certificates-from-the-pki-store) | | Security headers | HSTS, CSP, X-Frame-Options DENY, no-store cache on all authenticated responses | | Version banner | `X-Ze-Version` carries the build fingerprint. `environment { hide-version true; }` keeps it off the web interface and the looking glass together; the default sends it | | YANG decorators | Leaves with `ze:decorate` extension show enriched display text (e.g., ASN numbers annotated with organization name via Team Cymru DNS) | | Workbench UI (default) | RouterOS-style operator workbench (default since Phase 2); row-level related-tool buttons declared via `ze:related` YANG extension dispatch through the standard CommandDispatcher; CLI available as separate `/cli` tab | | templ rendering | Every page, panel, fragment and out-of-band swap is written in a `.templ` source and compiled to Go. No Go file in the package builds markup, so a renamed view-model field is a compile error instead of a blank panel | | htmx 4 | htmx 4.0.0 is embedded, with `hx-sse.min.js` for the streams. Each page loads only the assets its component graph reaches | | Secret masking | No render path prints a value the schema marks `ze:sensitive` or `ze:bcrypt`. One predicate answers "does this leaf hold a secret", and both the display mask and the write guard read it | ## Browser Configuration Workflow The recording below signs in to a local Ze instance, edits a YANG-backed value, reviews the generated diff, commits the browser session's draft, and verifies the active value. The daemon and browser run locally during generation. ### Demo: Edit and commit configuration in the browser Change a YANG-backed setting, review the generated diff, commit the draft, and verify the active value. [Play the WebM recording](../../assets/demos/web-config.webm?v=20f53de68b) · [View the poster](../../assets/demos/web-config.png?v=dd42e3113f) · [Plain-text transcript](../../assets/demos/web-config.txt?v=a614767cf2) Recorded with Ze 26.08.31 on macOS and Linux using Playwright 1.55.0. Duration: 58 seconds. ```console Ze web configuration demo 1. Open the local Ze HTTPS interface. 2. Sign in as the local administrator. 3. Open System / Identity in configuration mode. 4. Change the hostname from ze-demo to edge-demo. 5. Save the draft and open Review & Commit. 6. Verify the diff contains `host edge-demo`. 7. Confirm the commit. 8. Reload the setting and verify the active hostname is edge-demo. Expected result: Ze commits the browser user's isolated draft and the active YANG-backed hostname reads `edge-demo`. ``` <!-- source: internal/component/web/server.go -- WebServer, TLS config, cert generation, UpdateTLSCertificate --> <!-- source: cmd/ze/hub/service_tls.go -- listenerTLSMaterial: PKI store or self-signed, fail closed --> <!-- source: internal/component/web/decorator.go -- Decorator registry and interface --> <!-- source: internal/component/web/decorator_asn.go -- ASN name decorator via Team Cymru DNS --> <!-- source: internal/component/web/auth.go -- SessionStore, authMiddleware, loginHandler --> <!-- source: internal/component/web/handler.go -- URL routing, content negotiation, three-tier scheme --> <!-- source: internal/component/web/handler_config.go -- Config view and edit handlers --> <!-- source: internal/component/web/handler_admin.go -- Admin command handlers --> <!-- source: internal/component/web/cli.go -- CLI bar and terminal mode --> <!-- source: internal/component/web/sse.go -- EventBroker SSE broadcast --> <!-- source: internal/component/web/editor.go -- EditorManager per-user sessions --> <!-- source: internal/component/web/render.go -- Renderer, renderComponent, AssetHandler --> <!-- source: internal/component/web/markup_check_test.go -- webMarkupExempt: the empty exemption table --> <!-- source: internal/component/config/mask.go -- LeafHoldsSecret, MaskSecrets, RejectMaskedSecretLeaves --> See [Web Interface Guide](../../guides/web-interface/index.md) for usage instructions. --- ### Page: How Ze compares https://ze-software.net/compare/ # How Ze compares Choose the comparison lens before jumping into the tables. The BGP page compares Ze with BGP daemon implementations. It also carries an OSPF table against FRR and BIRD. The Network OS page compares Ze with VyOS and freeRtr as full router operating systems. **BGP** ### [BGP daemon comparison](https://ze-software.net/compare/bgp/) Ze against BIRD, FRR, OpenBGPd, GoBGP, bio-rd, ExaBGP, RustyBGP, rustbgpd, and freeRtr across AFI/SAFI, core protocol, policy, security, observability, APIs, operations, and best-path behavior. Plus OSPF standards coverage against the two daemons that also implement it. - Best for protocol capability checks. - Includes where Ze is behind today. - Table filter can narrow by feature or implementation. **NOS** ### [Open Source Network OS comparison](https://ze-software.net/compare/nos/) Ze against VyOS and freeRtr across routing, interfaces, firewall, NAT, VPN, AAA, services, management APIs, automation, packaging, observability, tests, and implementation model. - Best for router/NOS product decisions. - Source-grounded from the local checkouts inspected for this comparison. - Table filter can limit long evidence rows by section and keyword. ## Reading the pages Each comparison is intentionally scoped. A `Not found` or `No` entry means the feature was not found in the inspected source roots or comparison source, not that no upstream branch or external daemon can ever provide it. The search box filters rows and sections locally in the browser, and wide matrices add product toggles so readers can hide columns they are not comparing. ## Evidence and fairness policy Comparisons are advice, not marketing. Capability claims should cite upstream code, official feature documentation, or the integration layer that provides the behavior. For integrated systems such as VyOS, that may mean citing VyOS config/templates and FRR, nftables, Linux, or another integrated project when it owns the runtime feature. `Unclear`, `Partial`, and `Not found` are intentional outcomes when the evidence does not support a stronger claim. --- ### Page: BGP implementation comparison https://ze-software.net/compare/bgp/ # BGP implementation comparison A feature comparison of open source BGP daemon implementations. This page keeps the BGP-specific matrix separate from the full Network OS comparison. It also carries an OSPF table against FRR and BIRD, the two other daemons here that implement it. > **Disclaimer and evidence:** this comparison was generated with AI assistance and is provided for informational purposes only. All listed projects are under active development and their capabilities change over time. Verify current features against each project's own documentation before making decisions. Rows should be read as evidence-backed advice rather than marketing: code paths link to upstream source where the site can map them, official feature pages are preferred when source links are not practical, and `No` or `Partial` means the cited evidence did not support a stronger claim. Corrections and updates are welcome via the issue tracker. Find in this page Section ## Overview | | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Language | Go | C | C | C | C | Go | Go | Python | Rust | Rust | Java | | License | AGPL 3.0 | GPL 2.0+ | GPL 2.0+ | GPL 2.0 | ISC | Apache 2.0 | Apache 2.0 | BSD 3-Clause | Apache 2.0 | MIT | Free | | Primary interface | CLI, SSH, REST, gRPC | CLI | CLI | CLI | CLI | gRPC | gRPC | CLI, API | gRPC | gRPC | CLI | | First release | 2026 | 2024 | 1998 | 2017 | 2004 | 2014 | 2018 | 2010 | 2019 | 2026 | 2012 | | Multithreaded | Yes | Yes | No | No | Yes | Yes | Yes | No | Yes | Yes | Yes | | Multithread model | Goroutines | Cooperative threads | -- | -- | 3-process | Goroutines | Goroutines | -- | Multi-core | Tokio | Per-peer | | Plugin architecture | Yes | No | No | No | No | No | No | No | No | No | No | | YANG-modeled config | Yes | No | No | Partial | No | No | No | No | No | No | No | ## Address Families | AFI/SAFI | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | IPv4 Unicast | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | IPv6 Unicast | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | IPv4 Multicast | Yes | Yes | Yes | Yes | No | Yes | No | No | No | No | Yes | | IPv6 Multicast | Yes | Yes | Yes | Yes | No | Yes | No | No | No | No | Yes | | IPv4 Labeled Unicast | Yes | No | No | Yes | No | Yes | No | Yes | No | No | Yes | | IPv6 Labeled Unicast | Yes | No | No | Yes | No | Yes | No | Yes | No | No | Yes | | VPNv4 (RFC 4364) | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | No | No | Yes | | VPNv6 | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | No | No | Yes | | L2VPN EVPN (RFC 7432) | Yes | Yes | Yes | Yes | No | Yes | No | Yes | No | No | Yes | | L2VPN VPLS | Yes | No | No | No | No | Yes | No | Yes | No | No | Yes | | IPv4 FlowSpec (RFC 8955) | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | No | Yes | Yes | | IPv6 FlowSpec | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | No | Yes | Yes | | VPN FlowSpec | Yes | No | No | No | No | Yes | No | No | No | No | Yes | | BGP-LS (RFC 7752) | Decode (40 TLVs) | No | No | No | No | Yes | No | Decode | No | No | Yes | | SR Policy | Yes | No | No | No | No | Yes | No | No | No | No | Partial | | IPv4/IPv6 MUP | Yes | No | No | No | No | No | No | No | No | No | Yes | | IPv4/IPv6 MVPN | Decode | No | No | No | No | No | No | No | No | No | Yes | | IPv4 RTC (RFC 4684) | Decode | No | No | No | No | No | No | Yes | No | No | Yes | ## Core Protocol | Feature | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | RFC 4271 FSM | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | 4-byte ASN (RFC 6793) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Capability negotiation | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Route Refresh (RFC 2918) | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | No | Yes | Yes | | Enhanced Route Refresh (RFC 7313) | Yes | Yes | Yes | Yes | Yes | No | No | Yes | No | Yes | Yes | | Graceful Restart (RFC 4724) | Yes | Yes | Yes | Yes | Yes | Yes | No | Partial | No | Yes | Yes | | Long-Lived GR (RFC 9494) | Yes | Yes | Yes | Partial | No | Yes | No | No | No | Yes | Yes | | Notification GR (RFC 8538) | No | No | No | No | Yes | Yes | No | No | No | Yes | No | | Add-Path (RFC 7911) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Rx only | Yes | Yes | | Paths-Limit (draft-abraitis) | Yes | No | No | Yes | No | No | No | Yes | No | No | No | | Extended Messages (RFC 8654) | Yes | Yes | Yes | Yes | Yes | No | No | Yes | No | Yes | Yes | | Extended Nexthop (RFC 8950) | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | No | Yes | Yes | | Route Reflector (RFC 4456) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | | Confederation (RFC 5065) | No | Yes | Yes | Yes | No | Yes | No | No | No | No | Yes | | Admin Shutdown (RFC 8203) | Yes | Yes | Yes | Yes | Yes | Yes | Partial | Yes | No | Yes | Partial | | BGP Roles (RFC 9234) | Yes | Yes | Yes | No | Yes | No | Yes | No | No | No | Partial | | Prefix Limit (RFC 4486) | Yes | Yes | Yes | Yes | Yes | Yes | No | No | No | Yes | Yes | ## Cross-Protocol Redistribute Ze advertises locally-originated routes from non-BGP protocols (connected, static, L2TP, IS-IS, OSPF) into BGP via the `redistribute-orchestrator` plugin. Operators enable it per-destination and per-source via `redistribute { destination <proto> { import <source> { family [...]; } } }`. The same config block also drives the intra-BGP `IngressFilter` ACL when the source is `ibgp` / `ebgp`. Per-peer NEXT_HOP substitution (`nhop self`) is automatic; explicit producer-supplied NEXT_HOP is passed through verbatim. The block is the whole configuration, which is the behavior FRR and BIRD have. `import bgp` names ze's own Loc-RIB and derives the peer-to-plugin delivery it depends on, so no second binding is written by hand. A consumer that registers after a producer emitted is replayed that producer's current set, so the outcome does not depend on which protocol started first. IS-IS meshes with BGP in **both** directions, matching the vendor IGP-BGP mutual-redistribution operators expect. IPv6 rides the same single-topology SPF tree -- matching one other implementation's single-topology IS-IS default (that implementation also offers RFC 5120 Multi-Topology, which Ze does not yet implement). OSPFv2 meshes with BGP in both directions like IS-IS, exports OSPF routes into BGP, and injects connected/static/BGP routes as Type 5 AS-External LSAs. Ze also implements stub, totally-stubby, and NSSA areas (RFC 3101) with Type 7 origination, translator election, and Type 7 to Type 5 translation. The RFC 3101 §2.4 border-router default is originated into every attached NSSA in both address families with no operator leaf, and the same install-side gates on a received Type 7 default (P-bit clear, summary import suppressed) apply to both; FRR gates the same origination on `default-information-originate`. Per-interface authentication covers simple password, keyed-MD5 (RFC 2328), HMAC-SHA (RFC 5709), and the RFC 7474 extended-sequence variant, with key chains for hitless rotation and sequence-number replay protection. ## Policy & Route Manipulation Ze takes a programmable approach to policy: external plugin filters manipulate routes via `filter { import [...] export [...] }` chains using named filter instances or explicit `<plugin>:<filter>` references. Filters chain as piped transforms (accept/reject/modify) with delta-only output. RFC-mandated checks run as default filters that can be selectively overridden. Built-in filter plugins shipped with Ze include prefix-list matching (ge/le bounds), AS-path regex filtering, community presence matching (standard/large/extended), route attribute modification (local-preference, MED, origin, next-hop, AS-path prepend), community tag/strip, and RFC 9234 role enforcement. | Feature | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Prefix matching (ge/le) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | Partial | Yes | Yes | | AS-path regex | Yes | Yes | Yes | Yes | Yes | Yes | No | No | No | Yes | Yes | | Standard communities | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Extended communities | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | No | Yes | Yes | | Large communities (RFC 8092) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | | Community add/remove/replace | Yes | Yes | Yes | Yes | Yes | Yes | Yes | API | No | Yes | Yes | | MED manipulation (set/inc/dec) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | API | No | Yes | Yes | | LOCAL_PREF set/inc/dec | Yes | Yes | Yes | Yes | Yes | Yes | Yes | API | No | Yes | Yes | | AS-path length filter | Yes | Yes | Yes | No | Yes | No | No | No | No | No | No | | AS-path prepend | Yes | Yes | Yes | Yes | Yes | Yes | Yes | API | No | Yes | Yes | | Next-hop set/self | Yes | Yes | Yes | Yes | Yes | Yes | Yes | API | No | Yes | Yes | | RPKI validation match | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | Yes | | Neighbor/peer matching | Yes | Yes | Yes | Yes | Yes | Yes | No | No | No | Yes | Yes | | Named policy definitions | Plugin | Yes | Yes | Yes | Yes | Yes | Yes | No | Partial | Yes | Yes | | Policy chaining | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | | Custom filter language | No | Yes | Yes | No | Yes | No | No | No | No | No | No | | External process policy | Yes | No | No | No | No | No | No | Yes | No | No | No | | Plugin-based policy | Yes | No | No | No | No | No | No | No | No | No | No | ## Security | Feature | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | TCP MD5 (RFC 2385) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | | TCP-AO (RFC 5925) | No | No | No | No | No | No | No | No | No | No | No | | GTSM / TTL Security | Yes | Yes | Yes | Yes | Yes | Yes | Partial | Yes | No | Yes | Yes | | RPKI/RTR (RFC 6810/8210) | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | Yes | | ASPA verification | Yes | Yes | Yes | No | Yes | No | No | No | No | Yes | No | | Private AS removal | Yes | Yes | Yes | Yes | Yes | Yes | No | No | No | Yes | Yes | | Privilege separation | No | No | No | No | Yes | No | No | No | No | No | No | | TACACS+ AAA (RFC 8907) | Yes | No | No | Yes | No | No | No | No | No | No | Yes | | Memory-safe language | Yes | No | No | No | No | Yes | Yes | Yes | Yes | Yes | Yes | ## Monitoring & Observability | Feature | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Prometheus metrics | Yes | No | No | Yes | No | Yes | Yes | No | No | Yes | No | | Structured logging (JSON) | Yes | No | No | No | No | No | Yes | No | No | Yes | No | | BMP (RFC 7854) | Yes | Yes | Yes | Yes | No | Yes | Yes | No | Partial | Yes | Yes | | MRT dump (RFC 6396) | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | Yes | | Writes a pcap of its BGP sessions | Yes | No | No | No | No | No | No | No | No | No | No | | Decodes a pcap of a BGP session | Yes | No | No | No | No | No | No | No | No | No | No | | Flow export (sFlow/NetFlow/IPFIX) | Yes | No | No | No | No | No | No | No | No | No | No | | Streaming route events | Yes | No | No | No | No | Yes | Yes | Yes | No | Yes | No | | JSON event protocol | Yes | No | No | No | No | No | No | Yes | No | No | No | | Built-in DNS resolver | Yes | No | No | No | No | No | No | No | No | No | No | | Static DNS name-servers | Yes | No | No | Yes | No | No | Yes | Yes | No | No | No | | Built-in PeeringDB/IRR/Cymru | Yes | No | No | No | No | No | No | No | No | No | No | | Unified operational reports | Yes | Partial | Partial | Partial | Partial | No | No | No | No | No | Partial | | SNMP agent (AgentX/MIB) | No | No | No | Yes | No | No | No | No | No | No | Yes | **Writes and decodes a pcap:** `show capture-raw dump bgp pcap` writes the recorded messages as a pcap Wireshark dissects as BGP, and `ze bgp decode pcap` reads one back, reassembling each TCP direction first. The reader takes a tcpdump capture too, so an operator decodes a colleague's file without ze having produced it. ExaBGP decodes hexadecimal from standard input, which `ze bgp decode -` now matches, but it reads no pcap. The other ten daemons are marked `No` because none documents a pcap writer or reader of its own; an operator captures with tcpdump and reads the file in Wireshark. Most BGP daemons expose operational issues through a mix of per-command output rather than a single aggregated view. Ze provides a cross-subsystem report bus: any subsystem can push warnings (state-based) or errors (event-based) onto a single place, and `ze show warnings` / `ze show errors` return the aggregate as structured JSON. The login banner reads the same source, so nothing is silently hidden. SNMP is a deliberate non-goal, not a gap: FRR and freeRtr both expose legacy AgentX/MIB agents, but Ze's operational surface (Prometheus, gNMI, gRPC, structured JSON events) already covers what those MIBs would carry, without maintaining a second protocol stack to do it. ## API & Programmability | Feature | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | gNMI | Yes | No | No | Partial | No | No | No | No | No | No | No | | gRPC API | Yes | No | No | Partial | No | Yes | Yes | No | Yes | Yes | No | | REST API | Yes | No | No | Partial | No | No | No | No | No | Partial | No | | YANG model | Yes | No | No | Partial | No | No | No | No | No | No | No | | Register-once operator surfaces | Yes | No | No | Partial | No | No | No | No | No | No | Partial | | CLI tool | Yes | Yes | Yes | Yes | Yes | Yes | Partial | Yes | No | Yes | Yes | | CLI JSON output | Yes | No | No | Yes | Yes | Yes | No | Yes | No | Yes | No | | Runtime route injection | Yes | No | No | No | No | Yes | No | Yes | Yes | Yes | Yes | | Hot reconfiguration (no restart) | Yes | Yes | Yes | Yes | Yes | Yes | Partial | Yes | No | Yes | Yes | | Embeddable library | No | No | No | No | No | Yes | Yes | No | No | No | No | | Plugin SDK | Yes | No | No | No | No | No | No | No | No | No | No | | External process protocol | Yes | No | No | No | No | No | No | Yes | No | No | No | | MCP (Model Context Protocol) server | Yes | No | No | No | No | No | No | No | No | No | No | | SSH CLI access | Yes | No | No | No | No | No | No | No | No | No | Yes | Ze's register-once row means a command, config node, plugin, RPC, or event can feed the CLI, web workbench, REST/gRPC, MCP, generated docs, completion, authorization, and audit paths. FRR has partial YANG/API evidence. freeRtr has integrated CLI, NETCONF, and help generation, but not the same all-surface pipeline in the inspected sources. ## Operations | Feature | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Crash capture (syslog + file) | Yes | No | No | No | No | No | No | No | No | No | No | | Config error diagnostics | Yes | No | No | No | No | No | Partial | No | No | Yes | Partial | | Runtime health monitoring | Yes | No | No | No | No | No | No | No | No | No | No | | Pre-start readiness checks | Yes | No | No | No | No | No | No | No | No | No | No | | Product doctor/debug workflow | Yes | No | No | No | No | No | No | No | No | No | Partial | | Docker image | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes | No | Yes | Yes | | Fuzz testing | Yes | No | No | No | No | No | Yes | No | No | Yes | No | | Interop test suite | Yes | No | No | No | No | No | Partial | No | No | Yes | Yes | | Static routes (ECMP+BFD) | Yes | Yes | Yes | Yes | Yes | No | No | No | No | No | Yes | | Policy-based routing (PBR) | Yes | No | No | Yes | No | No | No | No | No | No | Yes | | FIB/kernel integration | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | No | No | Yes | | Sysctl management | Yes | No | No | Partial | Partial | No | No | No | No | No | No | | Route server mode | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | | Dynamic neighbors | Yes | Yes | Yes | Yes | No | Yes | No | No | Yes | No | Yes | | Looking glass | Yes | Yes | Yes | No | Yes | No | Yes | No | No | Yes | Yes | | BFD integration | Partial | Yes | Yes | Yes | No | No | No | No | No | No | Yes | | Firewall (nftables) | Yes | No | No | Yes | Yes | No | No | No | No | No | Yes | | Config commit/rollback (candidate + active) | Yes | No | No | No | No | No | No | No | No | No | No | **Update groups:** Ze automatically groups peers by encoding context and builds each UPDATE once per group, fanning out the wire bytes to all members. No configuration needed -- one other implementation in this table requires explicit peer-group assignment for the same optimization. ## Best-Path Selection ExaBGP does not perform best-path selection -- it forwards all received routes to external processes and injects routes from them. It is a route injector/receiver, not a router. | Step | Ze | BIRD 3 | BIRD 2 | FRR | OpenBGPd | GoBGP | bio-rd | ExaBGP | RustyBGP | rustbgpd | freeRtr | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | LOCAL_PREF | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes | Yes | | AS-path length | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes | Yes | | ORIGIN | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes | Yes | | MED | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes | Yes | | eBGP over iBGP | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes | Yes | | CLUSTER_LIST length | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A | No | Yes | Yes | | ORIGINATOR_ID | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A | No | Yes | Yes | | Stale route demotion (GR) | Yes | Yes | Yes | Yes | Yes | Yes | No | N/A | No | Yes | Yes | | RPKI preference | Yes | Yes | Yes | Yes | Yes | Yes | No | N/A | Yes | Yes | Yes | | AIGP | Partial | No | No | Yes | No | Yes | No | N/A | No | No | Yes | | IGP cost to next-hop | Yes | Yes | Yes | Yes | Yes | No | No | N/A | No | No | Yes | | Recursive next-hop | Yes | Yes | Yes | Yes | Yes | No | No | N/A | No | No | Yes | | Multipath/ECMP | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A | No | Partial | Yes | ## OSPF Standards Coverage The tables above compare BGP. This one compares OSPF, against the two daemons that also implement it natively. Ze, FRR and BIRD are the whole population here: the other implementations in this table speak BGP only. **Version basis.** Ze at commit `35f84e060`. FRR at release tag `frr-10.7.1` (2026-08-31). BIRD 2.19.2 and BIRD 3.3.2 (both 2026-07-30), which carry an identical OSPF standards list, so one column serves both. **What each column asserts.** The three columns are not judged alike, so no cell here says a bare "yes". A Ze cell states what the code does with the value, in three grades. **Originates and consumes** means Ze builds it, parses it on receipt, and something acts on the parsed value. **Originates** means built and parsed, with no consumer beyond display. **No** means no producer exists. An FRR or BIRD cell names the evidence: the configuration command, or the source file that implements it, at the version above. That is what was checked. Neither daemon was run, and no cell here claims interop. | RFC | Ze | FRR 10.7.1 | BIRD 2.19.2 / 3.3.2 | | --- | --- | --- | --- | | 8362 OSPFv3 Extended LSAs | Originates 3 of the 7 LSA types, and only under segment routing. Decoded on receipt only to read Prefix-SIDs. No consumer in SPF. Non-conformant on the wire, see the note below | No. The OSPFv3 LS-type list in `ospf6d/ospf6_lsa.h` ends at `GRACE_LSA 0x000b` and bounds the handler table there | No. Absent from the maintainers' own standards list in `proto/ospf/ospf.c` | | 8666 OSPFv3 Segment Routing | Originates and consumes: installs labels to the MPLS FIB. 3 MUST-level gaps declared | No. `ospf6d/` holds no `ospf6_sr.*`, and OSPFv3 SR is defined over the Extended LSAs above | No. Same standards list | | 8665 OSPFv2 Segment Routing | Originates and consumes: installs labels. 14 MUST-level gaps declared | Yes: `segment-routing on`, `ospfd/ospf_sr.c`. Upstream marks it EXPERIMENTAL | No. Same standards list | | 5286 Loop-Free Alternate | Originates nothing on the wire, correctly. Computes LFA and TI-LFA, and the backup next hops reach the kernel with `RTNH_F_LINKDOWN` | TI-LFA only: `fast-reroute ti-lfa`, and the docs say it requires a Segment Routing configuration. FRR's RFC 5286 code is `isisd/isis_lfa.c`, which `ospfd` does not use | No. The OSPF grammar in `proto/ospf/config.Y` has no LFA, backup or alternate keyword | | 7684 Extended Prefix and Link | Originates and consumes: Segment Routing reads the prefix SIDs. A received Adj-SID is decoded and discarded | Yes: `ospfd/ospf_ext.c`, a direct RFC 7684 implementation | No. Same standards list | | 7770 Router Information | Originates and consumes: a remote SRGB read from these LSAs drives label computation | Yes: `router-info [as \| area]`, `ospfd/ospf_ri.c`. Its header cites the predecessor RFC 4970 | Listed as supported, but dormant: `ospf_originate_ri_lsa()` is commented out at its only call site, and `lsa_validate_ri` says "we do not really process RI LSAs" | | 3630 and 5392 Traffic Engineering | Originates both. The TE database feeds `show ospf te-database` and metrics. No CSPF or admission consumer reads it: `LookupLink` has no non-test caller | Yes: `mpls-te on`, `mpls-te router-address`, `mpls-te inter-as area \| as`, `ospfd/ospf_te.c` | No. Neither RFC is on the standards list | | 5250 Opaque LSAs | Originates and consumes, with a registry other features register consumers into | Yes: `ospf opaque-lsa`, `capability opaque`, `ospfd/ospf_opaque.c` | Carries and floods them, including unknown types, and originates none | | 3623 and 5187 Graceful Restart | Originates and consumes, both versions: Grace-LSA out, helper in, with self-LSA suppression, install suppression and FIB retention | Yes, both: `graceful-restart`, `graceful-restart helper enable`, `ospfd/ospf_gr.c` and `ospf6d/ospf6_gr.c` | Yes, both: `graceful restart on \| aware`. The default is `aware`, which is helper only | | 5443 LDP-IGP Synchronization | Originates and consumes: an LDP session event substitutes the advertised metric, and the Router-LSA is re-originated | Yes: `mpls ldp-sync` with a holddown, default VRF only | No, and BIRD has no LDP at all | | 4577 OSPF as PE/CE | No. No DN-bit originator, no VRF, no sham link | No. `OSPF_OPTION_DN` is defined in `ospfd/ospfd.h` and consumed nowhere, and the interface-type set in `lib/libospf.h` has no sham-link type | The DN bit only: `vpn pe` in the grammar, consumed in `rt.c`. No sham link in its interface-type set either | **Where the open ground is.** RFC 8362 and RFC 8666 travel together, because OSPFv3 Segment Routing is defined over the Extended LSAs. Neither FRR nor BIRD implements either, so this is the one part of the table where Ze is alone. **A caveat on RFC 8362, stated because the row above would otherwise mislead.** Ze's Extended LSAs carry the U-bit clear. RFC 8362 Section 2 requires it set, "so that the LSAs will be flooded by OSPFv3 routers that do not understand them". The same section assigns LS types 0xA021 through 0xA029, and Ze declares those types less 0x8000. With the U-bit clear, RFC 5340 Section 4.4.1 confines an unrecognized LSA to link-local scope. Ze's Prefix-SIDs therefore stop at the first router in a mixed area that does not support them. Having the feature and having it interoperate are different claims. Only the first is true today. ## BNG Capabilities Ze includes a production BNG stack with two access methods: L2TPv2 (RFC 2661) and PPPoE (RFC 2516), both with RADIUS integration (RFC 2865/2866). Most BGP daemons in the comparison table have no BNG functionality at all. L2TP and PPPoE run concurrently on the same daemon and share the same auth, pool, and shaper plugins through a transport-agnostic PPP driver. RADIUS accounting includes real per-subscriber traffic counters read from the kernel PPP interface. Control-plane scale test infrastructure validates 2000 concurrent L2TP sessions across 10 tunnels without requiring root, kernel modules, or Docker. ## Where Ze is behind today The detail tables above are useful. The gaps also need to be visible without reading thirteen tables. - **OSPF as PE/CE (RFC 4577) is absent.** No DN-bit originator, no VRF, and no sham link. FRR has none of it either; BIRD has the DN bit alone. - **OSPFv3 Extended LSAs (RFC 8362) do not interoperate.** Ze builds 3 of the 7 LSA types and sets the U-bit wrong on all of them, so they stop at the first OSPFv3 router that does not support them. Neither FRR nor BIRD implements RFC 8362 at all. - **BGP confederations (RFC 5065) are missing.** BIRD 3, bio-rd (partial), FRR, GoBGP, BIRD 2, and freeRtr support them. - **Privilege separation is missing.** At least one other implementation in this table has it. - **BFD integration is partial.** Several other implementations here have full support. - **Embeddable library mode is missing.** At least two other implementations in this table offer one. - **Custom filter language is missing.** Ze relies on plugin chains instead. - **Multi-Topology IS-IS (RFC 5120) is missing.** Ze's IS-IS matches the single-topology default other implementations ship, but not their optional multi-topology extension. - **Ze is pre-release.** The first release is planned for 2026, and this table includes implementations with years to decades of production hardening. - **Performance has not been benchmarked under route-server-size load.** Go carries an estimated 10-15% CPU overhead versus C/Rust implementations; this has not been measured under load. See [Performance](https://ze-software.net/performance/). These points are repeated here so visitors do not have to hunt for them. ## Positioning **Ze** is an open-source network operating system and the successor to ExaBGP. It runs as a daemon on any Linux, or as a gokrazy appliance where gokrazy init starts Ze with no general shell or package manager. It speaks BGP, manages network interfaces, installs routes into the kernel FIB or VPP plane, and serves a config editor over SSH and a web UI. The plugin architecture uses YANG-modelled schemas, so plugins can extend the engine without modifying it. It is also pre-release, with a first release planned for 2026. It sits beside implementations with years to decades of production hardening; one dates to 1998. Its Go runtime is expected to carry a 10-15% CPU cost compared with the C/Rust implementations in this table, and that estimate has not been benchmarked under real load. BGP confederations, privilege separation, and a custom filter language are still missing, while at least one other implementation has shipped each of them for years. Ze is strongest where it has chosen depth: plugin architecture, YANG-modelled configuration end to end, MCP integration, and a production BNG stack alongside BGP. **ExaBGP** is the automation specialist. It pioneered the external-process model where BGP events are delivered as JSON to stdin/stdout of user scripts in any language. Deployed worldwide for traffic engineering, DDoS mitigation, route injection, and SDN integration. It has broad address family support. It is single-threaded Python with no RIB, no best-path selection, and no route reflection by design. ExaBGP is a route injector and event source, not a router. **rustbgpd** is an API-first BGP daemon targeting IX route server and SDN controller use cases. It trades address family breadth for modern operational tooling (gRPC, Prometheus, structured logging, TUI, config diagnostics) and memory safety guarantees. **bio-rd** is a Go BGP library and daemon originating from DE-CIX. Designed as an embeddable library for building route servers and SDN controllers. It has strong route-server support with RFC 9234 (BGP Roles), BMP, and ECMP. IPv4/IPv6 unicast only: no VPN, EVPN, FlowSpec, or other address families. No Graceful Restart or Route Refresh. Apache-2.0 license. **RustyBGP** is an experimental Rust BGP daemon by the GoBGP team (OSRG). It offers a GoBGP-compatible gRPC API and multi-core design with low memory usage. Its own README describes it as having "very basic BGP features": limited address family and policy support. It is useful for research and multi-core experimentation, not yet production-ready. **FRR** is the most feature-complete open-source routing suite, covering BGP plus OSPF, IS-IS, PIM, and more. Best choice when you need a full routing stack with broad AFI/SAFI coverage and kernel FIB integration. **BIRD 2/3** dominates IXP route server deployments. It is known for low memory use and a powerful filter language. BIRD 3 (stable Dec 2024) adds multithreading for 5000+ peer scale. Management is CLI/config-file only. **GoBGP** pioneered the API-first model with gRPC as its primary interface. Broadest AFI/SAFI coverage. Higher memory and CPU usage than C implementations under large routing workloads. It is best used as an SDN controller or route injector rather than a high-performance router. **OpenBGPd** is security-focused with privilege separation and OpenBSD heritage. Deployed at major IXPs. Lean, reliable, and standards-compliant with strong RFC coverage including BGP Roles and Extended Messages. No programmatic API beyond the CLI socket. **freeRtr** is a full router OS written entirely in Java. It implements the full routing stack with its own TCP/IP forwarding plane that can be backed by DPDK, XDP, or P4 dataplanes. It has the broadest AFI/SAFI coverage of any implementation in this table, including MUP, MVPN, RTC, and VPN FlowSpec. It has been actively developed since 2012 with 4000+ functional test cases. It has no programmatic API, YANG model, or structured logging. ## FAQ **Ze is pre-release. Why trust it yet?** Do not take that on faith: it is backed by 29,800+ unit tests, 2,000+ end-to-end tests, 83 fuzz targets, and interop testing against 9 independent BGP implementations. That evidence can be checked. It is not a promise. Ze does not have operational mileage yet: real deployments over real time, on real networks. Use it in labs first. **Why no BGP confederations yet?** Not implemented yet. It's a real gap against implementations that have had it for years, and it's listed as one plainly above rather than left for you to find in a table. **Why no custom filter language?** Ze does not have a bespoke filter DSL like some implementations here do. Instead, filters are external plugins chained per peer/group: JSON events in, text commands out, over a TLS connect-back socket, in any language that can read lines. You can write a filter in Go, Python, or whatever you already know, instead of learning a new syntax. **Is Ze's performance actually competitive with C/Rust implementations?** Unknown under large route-server load. The current estimate is 10-15% CPU overhead from the Go runtime, but that number has not been benchmarked under real load. Treat it as an open question rather than a claim. See [Performance](https://ze-software.net/performance/) for the actual convergence and throughput numbers measured so far. **Does Ze implement every BGP feature in this table?** No. The BGP-specific gaps are listed above: no BGP confederations, partial BFD integration, no custom filter DSL, no privilege separation, and no embeddable library mode. The full-router comparison belongs on the [Open Source Network OS comparison](https://ze-software.net/compare/nos/). --- ### Page: Open Source Network OS comparison https://ze-software.net/compare/nos/ # Open Source Network OS comparison A source-backed comparison of Ze, VyOS, and freeRtr as router operating systems rather than only BGP implementations. > **Scope and evidence:** this page summarises the local research artefact produced from the checked-out Ze, VyOS `vyos-1x`, and freeRtr sources. Runtime protocol daemons external to `vyos-1x` are intentionally labelled that way, and VyOS rows may cite FRR or other integrated projects when those projects own the runtime behaviour. `Not found` means not found in the inspected source roots after targeted searches, not a universal upstream absence claim. The inline code paths link to upstream source where the site can map them. Find in this page Section ## Status legend | Mark | Meaning | | --- | --- | | `Yes` or `Present` | Direct source evidence found in inspected checkout. | | `Partial` | Source evidence exists, but scope is narrower than the comparable feature in at least one other product. | | `Unclear` | Related source evidence exists, but the exact feature or behaviour was not traced to a producer. | | `No` or `Not found` | Targeted and alternate searches did not find the feature in inspected sources. This is not a global claim about every version or external package. | ## Executive conclusion - **VyOS** has the widest Linux-router integration surface in the inspected source. Its own code covers CLI schema, config and operational scripts, firewall, NAT, VPNs, services, commit-confirm, rollback, archive, REST/OpenAPI, GraphQL, and platform integration. Runtime protocol implementation mostly comes from FRR and the Linux/VPP/system service stack. - **freeRtr** has the widest router-native protocol and service surface in the inspected source: many routing protocols, LSP/tunnel families, crypto/VPNs, NETCONF, Java CLI/config, internal services, and dataplane export paths. Most of that breadth sits in large Java registries and runtime classes. - **Ze** has the most integrated management plane among the three in the inspected source. Its BGP/control-plane architecture, YANG-modelled config and commands, generated plugin registration, CLI/web/REST/gRPC/MCP surfaces, built-in SSH management, HTMX web UI, minimal kernel/init, appliance runtime, diagnostics, and performance/testing tools are designed together. It is narrower than VyOS and freeRtr in legacy protocols and services today. ## Feature matrices by category These are the operator-facing feature matrices. Cells intentionally start with `Yes`, `No`, `Partial`, or `Unclear` so the table reads like the BGP comparison. The detailed source evidence remains in the appendices below. ## Routing and control plane matrix | Feature | Ze | VyOS | freeRtr | Notes | | --- | --- | --- | --- | --- | | IPv4/IPv6 static routing | Yes | Yes | Yes | Ze and VyOS include ECMP style static route surfaces; freeRtr has static route objects. | | BGP core | Yes | Yes | Yes | Ze and freeRtr implement BGP in source; VyOS exposes BGP through FRR config. | | BGP extended AFI/SAFI | Yes | Yes | Yes | Ze and freeRtr have broad in-tree AFI/SAFI support; VyOS exposes FRR AFI/SAFI config. | | BGP capabilities | Yes | Yes | Yes | ADD-PATH, route refresh, extended messages, extended nexthop and related capability surfaces are evidenced. | | BGP Graceful Restart / LLGR | Yes | Yes | Yes | VyOS evidence is config-surface level through FRR. | | Route server and route reflector | Yes | Yes | Yes | All three expose route-reflector-client and route-server-client modes; freeRtr has both as per-neighbor knobs. | | RPKI / ASPA | Yes | Yes | Yes | Ze and freeRtr include RPKI source; VyOS exposes RPKI config. | | Route maps, route policy, import/export chains | Yes | Yes | Yes | Ze uses plugin chains; VyOS uses policy and FRR templates; freeRtr has route-map and route-policy classes. | | Route redistribution / protocol import-export | Yes | Yes | Yes | Separate from IS-IS route leaking: all three expose redistribution, with Ze and freeRtr source-backed and VyOS documented for protocol route import/export. | | Prefix filters | Yes | Yes | Yes | All three have prefix-list style evidence. | | AS-path filters | Yes | Yes | Yes | All three have AS-path filter or manipulation evidence. | | Community filters | Yes | Yes | Yes | Standard, large, and extended community evidence exists for all three. | | OSPFv2 | Yes | Yes | Yes | Ze and freeRtr implement; VyOS exposes OSPF config. | | OSPFv3 | Yes | Yes | Yes | Evidence exists for all three. | | OSPF extensions: SR, TE, BFD, LDP sync | Yes | Yes | Yes | Scope differs by implementation and config surface. | | IS-IS L1/L2 | Yes | Yes | Yes | Evidence exists for all three. | | IS-IS route leaking | Yes | Unclear | Yes | Ze implements RFC 2966 L1/L2 leaking; VyOS documents IS-IS redistribution and attached-bit but no leak-specific command was found; freeRtr has source-visible IS-IS inter-level route handling tests. | | RIP / RIPng | No | Yes | Yes | Not found in Ze inspected source. | | EIGRP | No | Partial | Yes | VyOS schema exists, but generated templates are removed by Makefile in inspected source. | | Babel / OpenFabric / OLSR style IGPs | No | Partial | Yes | VyOS has Babel/OpenFabric files; freeRtr has broader IGP classes. | | BFD | Yes | Yes | Yes | Ze BFD attaches to BGP/OSPF/static; all three have evidence. | | RIB / FIB programming | Yes | Yes | Yes | Ze targets kernel and VPP, VyOS delegates through FRR/Linux/VPP, freeRtr has own forwarding core and dataplane exports. | | VRF / routing instances | No | Yes | Yes | Ze has named routing tables and some per-feature VRF fields, but no full product VRF/interface binding yet. | | Cross-VRF route leaking | No | Yes | Yes | Ze does not have VRF yet, so cross-VRF route leaking is not present. | | MPLS labels/dataplane | Yes | Yes | Yes | Evidence exists for all three. | | LDP | Yes | Yes | Yes | Evidence exists for all three. | | RSVP-TE | Yes | No | Yes | VyOS RSVP-TE was not found in inspected `vyos-1x`. | | Segment Routing / SRv6 | Yes | Yes | Yes | Evidence exists for all three. | | EVPN control plane | Yes | Yes | Yes | VXLAN dataplane integration differs and is covered under interfaces. | | Multicast routing: PIM, PIM6, IGMP, MSDP | No | Yes | Yes | Ze BGP multicast tooling is not a multicast routing daemon. | | Policy-based routing | Yes | Yes | Yes | Ze is experimental; VyOS has policy route/route6 interface-applied PBR with set table/VRF; freeRtr has interface and VRF PBR. | | BMP | Yes | Yes | Yes | VyOS evidence is BGP monitor config surface. | | MRT dump/import/export | Yes | No | Yes | VyOS MRT was not found in inspected source. | | Routing telemetry exports | Yes | Partial | Yes | All have some export hooks, with different scope. | ## Interfaces, L2, L3, and dataplane matrix | Feature | Ze | VyOS | freeRtr | Notes | | --- | --- | --- | --- | --- | | Interface management model | Yes | Yes | Yes | Ze uses typed Go config and backend selection; VyOS uses XML plus Python owners; freeRtr uses central Java interface config. | | Linux netdev backend | Yes | Yes | Partial | Ze has a netlink backend; VyOS manages Linux interfaces; freeRtr abstracts interfaces through Java and helpers rather than a Ze-like netlink backend. | | VPP interface backend or VPP interface surface | Yes | Yes | No | Ze registers a VPP iface backend; VyOS has VPP root config plus selected VPP interface definitions; freeRtr VPP interface support was not found. | | Ethernet adoption/config | Yes | Yes | Yes | All three have ethernet interface evidence. | | Loopback | Yes | Yes | Yes | Evidence exists for all three. | | Dummy interfaces | Yes | Yes | No | freeRtr normal interface type list did not show dummy. | | Bridge | Yes | Yes | Yes | Evidence exists for all three. | | Bonding / LAG | No | Yes | Yes | freeRtr calls this bundle. | | VLAN 802.1Q | Yes | Yes | Yes | Evidence exists for all three. | | QinQ / stacked VLAN | No | Yes | Yes | Ze QinQ was not found. | | Interface VRF binding | No | Yes | Yes | Ze has routing tables elsewhere, but no iface VRF binding was found. | | VXLAN | No | Yes | Yes | Ze EVPN NLRI exists, but VXLAN interface/dataplane integration was not found. | | GRE / GRETAP | Yes | Yes | Yes | Evidence exists for all three. | | IPIP / SIT / IPv6 tunnel | Yes | Yes | Partial | freeRtr evidence is protocol imports and tunnel surface, not exact CLI lines for every name. | | WireGuard interface/tunnel | Yes | Yes | Yes | Evidence exists for all three. | | OpenVPN | No | Yes | Yes | Ze OpenVPN was not found. | | PPP / PPPoE | Yes | Yes | Yes | Evidence exists for all three. | | L2TP | Yes | Yes | Yes | Ze focuses on L2TPv2/PPP BNG; VyOS evidence is L2TPv3 interface plus VPN L2TP; freeRtr has v2 and v3. | | Generic TUN/TAP | No | Yes | Partial | VyOS exposes TUN/TAP through OpenVPN; Ze and freeRtr tunnel abstractions do not prove a generic TUN/TAP feature. | | veth | Yes | Yes | Partial | freeRtr evidence is host helper integration, not a normal interface type. | | MACsec | No | Yes | Yes | Ze MACsec was not found. | | Wireless / WWAN | No | Yes | Partial | freeRtr has CAPWAP wireless-AP provisioning (servCapwap/clntCapwap), not a wireless interface type. | | MTU, MAC, addressing | Yes | Yes | Yes | Evidence exists for all three. | | DHCP client on interfaces | Yes | Yes | Yes | Evidence exists for all three. | | IPv6 Router Advertisements | Partial | Yes | Yes | Ze source evidence is BNG/PPP RA, not a general LAN RA sender. | | LLDP | No | Yes | Yes | Ze LLDP was not found. | | QoS, traffic policy, shaping | Yes | Yes | Yes | Ze supports tc/VPP backends and L2TP shaping; VyOS and freeRtr have broader policy surfaces. | | Netns/container interface binding | No | Yes | Partial | VyOS exposes netns/container integration; Ze did not in inspected iface source. | | Native/P4/XDP dataplane helpers | No | No | Yes | freeRtr has native, P4 and XDP helper sources. | ## Firewall, security, VPN, and AAA matrix | Feature | Ze | VyOS | freeRtr | Notes | | --- | --- | --- | --- | --- | | Firewall tables, chains, hooks, policies | Yes | Yes | Yes | Ze uses nftables; VyOS has firewall/zone model; freeRtr has ACL/filter surfaces. | | Rich match/action rules | Yes | Yes | Yes | Exact action vocabulary differs. | | Stateful firewall / conntrack | Yes | Yes | Partial | freeRtr has session-based `inspect` stateful tracking (`tabSession`) but no rule-level conntrack-state match. | | Conntrack operations and observability | Yes | Yes | Partial | Ze exposes Linux conntrack; VyOS has conntrack config/op files; freeRtr NAT state was found. | | NAT44 source/destination/static/masquerade | Yes | Yes | Yes | Evidence exists for all three. | | NAT66 / NPTv6 / NAT64 | Partial | Yes | Partial | Ze can express IPv6 NAT with nft chains, but no dedicated NPT/NAT64 model was found. | | Zone-based firewall | No | Yes | No | Explicit zone model was found only in VyOS. | | Address, network, port and interface groups | Yes | Yes | Yes | Scope differs by product. | | Dynamic firewall groups/sets | Yes | Yes | Partial | freeRtr ACL reflect is dynamic behavior, not a named dynamic group. | | Rate limiting and security policing | Yes | Yes | Yes | Evidence exists for all three. | | Generic IPsec/IKE | No | Yes | Yes | Ze has IPsec references for specific protocols, not a generic VPN IPsec/IKE subsystem. | | WireGuard VPN | Yes | Yes | Yes | Evidence exists for all three. | | OpenVPN | No | Yes | Yes | Ze OpenVPN was not found. | | L2TP/IPsec remote-access coupling | Partial | Yes | Partial | Ze and freeRtr have L2TP and IPsec-related pieces, but explicit L2TP/IPsec profile coupling was not found. | | PKI / certificates | Partial | Yes | Yes | Ze has self-cert and appliance cert replacement; VyOS and freeRtr expose broader PKI/config surfaces. | | SSH service | Yes | Yes | Yes | Evidence exists for all three. | | Local users/passwords/keys/OTP | Yes | Yes | Yes | OTP breadth differs. | | RADIUS | Partial | Yes | Yes | Ze RADIUS was source-confirmed for L2TP PPP sessions, not generic system login. | | TACACS+ | Yes | Yes | Yes | Evidence exists for all three. | | Command RBAC / authorization | Yes | Yes | Partial | Ze has explicit command authorization; freeRtr privilege behavior is less RBAC-like. | | Sensitive storage and redaction | Yes | Yes | Yes | Evidence exists for all three. | | Control-plane hardening/sysctls | Yes | Yes | Partial | Named CoPP is separate below. | | Named CoPP feature | No | No | No | Related controls exist, but named CoPP was not found in inspected sources. | ## Network services and operations matrix | Feature | Ze | VyOS | freeRtr | Notes | | --- | --- | --- | --- | --- | | DHCPv4 server | Yes | Yes | Yes | Evidence exists for all three. | | DHCPv4 client | Yes | Yes | Yes | Evidence exists for all three. | | DHCPv4 relay | No | Yes | Yes | Ze DHCPv4 relay was not found. | | DHCPv6 server | Partial | Yes | Yes | Ze source evidence is PPP/L2TP DHCPv6-PD, not a general LAN DHCPv6 server. | | DHCPv6 client | Yes | Yes | Yes | Evidence exists for all three. | | DHCPv6 relay | No | Yes | Yes | Ze DHCPv6 relay was not found. | | Recursive DNS forwarder/resolver service | No | Yes | Yes | Ze DNS lookup tooling and GeoDNS do not prove a recursive/forwarding DNS service. | | Authoritative DNS | Yes | Yes | Yes | Ze has GeoDNS authoritative-style service. | | GeoDNS | Yes | No | No | Ze-only in inspected sources. | | NTP | Partial | Yes | Yes | Ze NTP client found; NTP server not found. | | PTP | No | Yes | Yes | Ze PTP was not found. | | SNMP | No | Yes | Yes | Ze SNMP was not found. | | LLDP service | No | Yes | Yes | Repeated here because it is an operator service as well as L2 discovery. | | TFTP/image server | No | Yes | Yes | Ze can advertise PXE/TFTP options, but a TFTP server was not found. | | HTTP server / API | Yes | Yes | Yes | Scope differs: Ze REST/web/LG, VyOS API, freeRtr HTTP service. | | Web proxy | No | Yes | Yes | Ze proxy was not found. | | Load balancing | No | Yes | Yes | Ze load balancer was not found. | | Captive portal | No | Partial | No | VyOS advertises captive portal endpoints, not portal enforcement in inspected source. | | Remote syslog destination | No | Yes | Yes | Ze structured logging exists, but remote syslog destination was not found. | | Flow telemetry: NetFlow/sFlow/IPFIX | Yes | Yes | Partial | freeRtr has NetFlow; sFlow/IPFIX were not found. | | Ping | Yes | Yes | Yes | Evidence exists for all three. | | Traceroute | Yes | Yes | Yes | Evidence exists for all three. | | MTR | No | Yes | Yes | Ze MTR was not found. | | Packet capture | Yes | Yes | Yes | Evidence exists for all three. | | Traffic generation/statistics | Partial | Partial | Yes | Ze has traffic usage and BGP replay tools; VyOS has monitoring/iperf; freeRtr has generators. | | Route looking glass/viewer | Yes | Partial | Partial | Ze has a dedicated looking glass service; the others have route show surfaces. | | Update, config archive, backup | Yes | Yes | Yes | Scope differs by product. | | Diagnostic support bundle/archive | No | Yes | No | VyOS tech-support archive is the strongest inspected support bundle. | ## Management, automation, UI, and API matrix | Feature | Ze | VyOS | freeRtr | Notes | | --- | --- | --- | --- | --- | | Operational and configuration modes | Yes | Yes | Yes | Evidence exists for all three. | | Submode/context editing | Yes | Yes | Yes | Evidence exists for all three. | | Interactive completion and help | Yes | Yes | Yes | Evidence exists for all three. | | Generated command reference/cache | Yes | Yes | Partial | freeRtr generates NETCONF/YANG from help, but not a Ze/VyOS style command catalog. | | Register-once operator surfaces | Yes | Partial | Partial | Ze registers commands, config, plugins, RPCs, and events once, then exposes them through CLI, web, REST/gRPC, MCP, generated docs, completion, authorization, and audit. VyOS has generated CLI/config artifacts. freeRtr has broad CLI plus NETCONF/help generation. The inspected sources did not show the same Ze-style all-surface pipeline. | | Candidate/draft config model | Yes | Yes | Partial | freeRtr commit buffer exists, but the model differs from Ze/VyOS candidate storage. | | Commit/apply | Yes | Yes | Yes | Evidence exists for all three. | | Commit confirm | Partial | Yes | No | Ze supports `commit confirmed <N>` with timer, `confirm`, `confirm abort`, and timeout auto-revert in file mode; session mode still rejects it. VyOS supports commit-confirm. freeRtr has a session-drop auto-revert, not timed commit-confirm. | | Rollback/revert | Yes | Yes | Partial | freeRtr reverts running to startup and has an auto-revert session, but no numbered-revision rollback. | | Config diff/compare | Yes | Yes | Yes | Evidence exists for all three. | | Command/config history | Yes | Yes | Partial | freeRtr has command history; config revision history was not found. | | Persistent config store | Yes | Yes | Yes | Evidence exists for all three. | | Remote archive upload | Yes | Yes | Partial | freeRtr sets the upload target via `client config-server` credentials. | | Machine-readable config schema | Yes | Yes | Partial | Ze uses YANG; VyOS uses XML/RNG; freeRtr can generate YANG from help/config. | | REST API | Yes | Yes | Partial | freeRtr has HTTP API permissions, not a typed REST/OpenAPI surface. | | GraphQL | No | Yes | No | VyOS-only in inspected sources. | | OpenAPI docs | Yes | Yes | No | Ze and VyOS have OpenAPI evidence. | | gNMI | Yes | No | No | Ze-only in inspected sources. | | NETCONF | No | No | Yes | freeRtr-only among inspected sources. | | SSH CLI transport | Yes | Yes | Yes | Ze terminates SSH inside the daemon and lands operators in the Ze CLI/config editor. VyOS configures OpenSSH for device access. freeRtr has in-process SSH via `secSsh`, so it is also built in rather than OS-sidecar SSH. | | Management SSH without host shell account | Yes | No | Partial | Ze users can manage the router over SSH without Unix shell accounts. VyOS SSH is an OpenSSH/system-login path. freeRtr has built-in SSH and local users, but the inspected evidence did not show the same Ze AAA/RBAC/audit/accounting pipeline. | | Browser router UI | Yes | No | Partial | freeRtr has HTTP/web utilities, but a Ze-like router config UI was not found in inspected sources. | | HTMX frontend | Yes | No | No | Ze-only in inspected sources. | | MCP / AI integration | Yes | No | No | Ze-only in inspected sources. | | External plugin SDK/API | Yes | No | No | VyOS and freeRtr have extension mechanisms, not a Ze-like external SDK. | | JSON outputs | Yes | Yes | Yes | All three emit JSON; freeRtr exposes a `json` table-output mode. | | Automation hooks/scripts/schedulers | Yes | Yes | Yes | Shape differs by product. | ## Platform, packaging, and appliance matrix | Feature | Ze | VyOS | freeRtr | Notes | | --- | --- | --- | --- | --- | | Primary build automation | Yes | Yes | Partial | freeRtr README says no formal build system, but shell scripts exist. | | Compile-time feature selection | Yes | No | No | Ze build tags and feature gates are explicit. | | Generated registries/schema artifacts | Yes | Yes | No | Ze and VyOS generate runtime command/schema artifacts. | | Native OS package output | No | Yes | Yes | VyOS and freeRtr both ship full Debian package sources; Ze local install copies a binary. | | Jar/archive package | No | No | Yes | freeRtr Java artifact is first class. | | Appliance disk image | Yes | Yes | Yes | VyOS image build internals live in `vyos-build`, outside `vyos-1x`. | | In-tree installer ISO flow | Yes | No | Partial | Ze ISO flow is in-tree; VyOS points to external image builder; freeRtr has image recipes. | | Installer initrd/kernel tooling | Yes | Partial | Yes | VyOS uses live ISO/squashfs model in inspected source. | | Local host install | Yes | Partial | Yes | VyOS primary install is appliance/image oriented. | | Disk partition installer | Partial | Yes | Partial | VyOS has an interactive target-disk installer; freeRtr partitions/formats/bootstraps disks during image build but has no interactive installer. | | RAID install | No | Yes | No | VyOS-only in inspected sources. | | Multi-image boot management | No | Yes | No | VyOS-only in inspected sources. | | Upgrade download/signature/hash verification | Yes | Yes | Yes | Ze self-update downloads and verifies a SHA256 hash; VyOS uses minisign signatures; freeRtr verifies release hashes. | | Rollback or auto-revert update | Partial | Yes | Yes | VyOS auto-reverts to the previous image on a failed upgrade boot; freeRtr auto-revert was traced; Ze has gokrazy A/B. | | Upgrade compatibility checks | No | Yes | Partial | VyOS explicit arch/flavor checks were found. | | systemd support | Yes | Yes | Yes | Evidence exists for all three. | | Non-systemd appliance mode | Yes | No | Partial | Ze gokrazy mode uses gokrazy init to start Ze, with no systemd, package manager, or general shell; freeRtr has SysV compatibility but no no-systemd appliance claim. | | Runtime platform detection | Yes | Yes | Yes | Evidence exists for all three. | | Runtime env/config integration | Yes | Yes | Yes | Evidence exists for all three. | | Kernel command line tuning | Partial | Yes | Yes | Ze kernel tooling exists; exact sysctl/kernel tuning surface is broader in VyOS/freeRtr. | | Runtime sysctl management | Yes | Yes | Partial | Ze has a runtime sysctl component; freeRtr writes sysctl files at install time. | | Container image/runtime | Yes | Partial | Yes | VyOS inspected source focuses on managed podman containers more than a published image. | | Managed guest containers | No | Yes | No | VyOS-only in inspected sources. | | PXE/remote provisioning | Yes | Partial | No | Ze has the clearest in-tree PXE/TFTP/HTTP provisioning flow. | | Seed/bootstrap config database | Yes | Yes | Yes | Evidence exists for all three. | ## Observability, diagnostics, and testing matrix | Feature | Ze | VyOS | freeRtr | Notes | | --- | --- | --- | --- | --- | | Native metrics / Prometheus endpoint | Yes | Yes | Yes | VyOS uses exporters; Ze and freeRtr have in-source service evidence. | | OS metrics | Yes | Yes | Yes | Evidence exists for all three. | | Push telemetry | No | Yes | Yes | Ze push telemetry beyond Prometheus/events was not found. | | Live dashboard | Yes | No | Yes | Ze CLI/web dashboards and freeRtr dashboard objects were found. | | HTTP health endpoint | Yes | Partial | No | VyOS health checks are feature-specific, not one generic endpoint. | | Doctor/check framework | Yes | No | Partial | Ze has `ze doctor`, health registry checks, a warning/error report bus, support bundles, crash capture, and built-in runtime probes. freeRtr has checks/consistency references, but not a Ze-like doctor registry. | | Symptom-based diagnostics | Yes | Yes | Yes | Evidence exists for all three. | | Structured logging | Yes | Yes | Yes | Evidence exists for all three. | | Runtime debug switches | Yes | Yes | Yes | Evidence exists for all three. | | Panic/core/crash capture | Yes | Partial | Partial | Ze crashlog is explicit; the others have support/core or exception handling evidence. | | MRT/BMP/log export tooling | Yes | Partial | Yes | VyOS BMP/flow exports found; MRT tooling not found. | | Packet capture diagnostics | Yes | Yes | Yes | Evidence exists for all three. | | Unit tests | Yes | Yes | Yes | Evidence exists for all three. | | Functional/smoketests | Yes | Yes | Yes | Evidence exists for all three. | | Topology/interop tests | Yes | Partial | Yes | VyOS broader topology harness may live outside `vyos-1x`. | | Performance benchmark harness | Yes | Partial | Yes | Ze has `ze-perf`; VyOS inspected source has `iperf` operational testing. | | Verify/release evidence gates | Yes | Yes | Partial | Ze and VyOS have more explicit CI/release gates. | | Lint/static gates | Yes | Yes | Partial | freeRtr runs CodeQL static analysis on push/PR (C/Java/Python); no style linter found. | | Mutation testing | Yes | No | No | Ze-only in inspected sources. | | Support bundle | Yes | Yes | No | Ze has `ze support` with doctor, host, platform, sanitized config, crashes, disk, interfaces, routes, neighbors, DNS, firewall, and runtime modules. VyOS has support archive evidence. freeRtr support bundle evidence was not found. | ## Architecture and extensibility matrix | Feature | Ze | VyOS | freeRtr | Notes | | --- | --- | --- | --- | --- | | Explicit module/dependency manifest | Yes | Yes | No | Ze has `go.mod`; VyOS has Debian/Python package metadata; freeRtr has shell scripts and JDK assumptions. | | Component/plugin modularity | Yes | Partial | Partial | Ze has registries and generated imports; VyOS has XML owner scripts; freeRtr has Java registries. | | Internal and external plugin process model | Yes | No | No | Ze-only in inspected sources. | | Startup/command registration ownership | Yes | Yes | Yes | Mechanisms differ strongly. | | Generated composition roots | Yes | Yes | No | Ze and VyOS generate key wiring. | | Schema validation | Yes | Yes | Partial | Ze validates YANG; VyOS validates XML/RNG; freeRtr CLI/help schema is embedded and can generate YANG. | | Protobuf/gRPC API boundary | Yes | Partial | No | VyOS has protobuf-over-Unix-socket IPC (vyconf), not a Ze-like gRPC API; freeRtr has neither. | | External SDK boundary | Yes | Partial | No | VyOS Python APIs work inside VyOS; Ze publishes plugin SDK APIs. | | Daemon plus child process supervision | Yes | Yes | Partial | Ze supervises plugins; VyOS relies on systemd; freeRtr has JVM loop and service start/stop. | | Source-owned protocol implementations | Yes | Partial | Yes | VyOS mostly wraps external daemons for routing. | | Allocation/zero-copy tuning patterns | Yes | No | Yes | Ze and freeRtr have explicit packet/text buffer patterns. | | Test seams and injectable components | Yes | Yes | Yes | Evidence exists for all three, with different shapes. | | Config verify/apply/rollback model | Yes | Yes | Partial | freeRtr config mutation model is more immediate/in-process. | ## Scope FAQ **Does Ze support everything VyOS, FRR, or freeRtr does?** No. VyOS with FRR, and freeRtr, still cover many features Ze does not: RIP/RIPng, EIGRP, Babel/OpenFabric/OLSR, multicast routing, bonding, QinQ, VXLAN dataplane integration, MACsec, wireless, DHCP relay, SNMP, LLDP, PTP, proxy, load balancing, TFTP, support bundles, NETCONF, GraphQL, multi-image boot management, and broader package or installer flows. Ze has source-confirmed `commit confirmed` in file mode, but not yet in ZeFS session mode. It also has strong source-confirmed coverage in BGP, OSPF, IS-IS, LDP, RSVP-TE, RPKI, BMP, MRT, YANG-modelled config, REST/OpenAPI, gNMI, MCP, plugin SDKs, VPP/netlink backends, PPPoE/L2TP BNG, appliance tooling, testing, and diagnostics. Ze is deeper in selected modern control-plane and API areas. VyOS and freeRtr are broader router OSes today. This question belongs on this page, not the BGP daemon page, because it asks about non-BGP router OS breadth. ## Product model snapshot | Area | Ze | VyOS | freeRtr | Evidence notes | | --- | --- | --- | --- | --- | | Product identity | Present. Go module `github.com/ze-software/ze`, own Network OS architecture, Go 1.26, OpenConfig/YANG, netlink, govpp, gNMI, gokrazy dependencies. | Present. `vyos-1x` is the command definitions, configuration scripts, data, Python libraries, validators, templates, and tests package. Image build lives in `vyos-build`. | Present. Free/open router process, speaks routing protocols, re-encapsulates packets, can export FIBs to external dataplanes. | Ze: `go.mod:1-40`, `docs/architecture/core-design.md:1-40`. VyOS: `README.md:1-45`. freeRtr: `readme.md:1-15`. | | Implementation style | Small Go core plus registration/plugin pattern, generated plugin import root, YANG schemas, typed components. | XML schema plus Python conf/op scripts, Jinja2 templates, external daemons, generated old-backend `node.def` and op caches. | Java router process with large config/runtime registries, protocol classes, built-in services, native/P4/XDP helpers. | Detailed evidence: Architecture and Extensibility appendix. | | Routing breadth | Present for BGP, BFD, OSPFv2/v3, IS-IS, LDP, RSVP-TE, static, policy route, RPKI, BMP, MRT, SR/SRv6, route server/reflector. Not found for RIP/RIPng/EIGRP/Babel/PIM/MSDP in inspected source. | Present for BGP, static, OSPF, OSPFv3, IS-IS, RIP, RIPng, EIGRP schema caveat, Babel/OpenFabric files, BFD, RPKI, MPLS/LDP, SRv6/SR, PIM/PIM6/IGMP proxy. RSVP-TE and MRT not found in inspected source. | Broadest protocol list in inspected source. BGP, OSPF, IS-IS, RIP, EIGRP, Babel, OLSR, PIM, MSDP, LDP, RSVP, RPKI, EVPN, MRT, BMP, BFD, SR/SRv6, MPLS labels, many AFI/SAFI classes. | Routing and Control Plane appendix. | | BGP depth | Native BGP engine, capabilities, ADD-PATH, ExtNH, extended messages, route server/reflector, RPKI/ASPA, BMP, MRT, filters, RIB plugins, many NLRI plugins. | FRR-oriented BGP schema/include tree with AFI/SAFI, route-maps, EVPN, VPN/VRF leak controls, route server/reflector, BMP policy leaves. | Native BGP classes with broad AFI/SAFI and attributes, EVPN, MRT, BMP, RPKI, communities, SR attrs. | Routing and Control Plane appendix. | | Interfaces and L2/L3 | Present. Ze has a typed interface backend abstraction with `netlink` as the default backend and a registered `vpp` backend; apply loads the configured backend and dispatches interface lifecycle, addressing, link state, tunnel, VLAN, bridge, MAC, MTU and route queries through it. Some interface kinds remain Linux/netlink-gated, so Ze is narrower than VyOS/freeRtr in appliance breadth, but interface control is not netlink-only. | Present and broad. Many first-class XML interface nodes, Python owners, common includes for addressing, DHCP, VRF, MTU, IPv6 ND, mirror, VLAN, QoS, containers, selected VPP interface definitions. | Present and broad. Central `cfgIfc` model, VPDN/tunnel types, bridge/VLAN/PPP/LLDP/MACsec classes, native/P4/XDP dataplane support. | Ze: `internal/component/iface/backend.go:56-59`, `internal/component/iface/register.go:198-214,413-418`, `internal/plugins/iface/netlink/register.go:10-12`, `internal/plugins/iface/vpp/register.go:13-15`, `internal/plugins/iface/vpp/ifacevpp.go:8-10`. | | Firewall, NAT, security, VPN, AAA | Present for nft/VPP firewall, NAT, dynamic sets, conntrack, IRR source validation, IPsec/IKE, PKI, SSH, AAA/RADIUS/TACACS, L2TP PPP auth/accounting. Some VPN/service gaps remain. | Present and broad. Firewall/NAT, zone/group model, conntrack helpers, WireGuard, OpenVPN, IPsec, L2TP, SSTP/PPTP, PPPoE server, PKI, system login, RADIUS/TACACS, HTTPS API auth. | Present and broad. ACL/QoS/NAT/PBR in readme, IPsec/IKE/OpenVPN/WireGuard/MACsec/SGT, RADIUS/TACACS/local auth, SSH/TLS/DTLS service code, RPKI. | Security, VPN, AAA appendix. | | Network services | Present for DHCPv4 server, DHCP clients, GeoDNS, NTP client, flow export, looking glass, REST/gRPC/web, diagnostics, config archive/update. Not found for general DHCP relay, SNMP, LLDP, PTP, proxy, load balancer, TFTP server, captive portal. | Present and broad. DHCPv4/v6 server/relay, DNS forwarding/authoritative records, NTP/PTP, SNMP, LLDP, TFTP, webproxy, HAProxy/WAN load balancing, syslog, sFlow/IPFIX, support archive. | Present and broad. DHCPv4/v6 server/relay, DNS, NTP/PTP, SNMP, LLDP, HTTP/TFTP, proxy/client helpers, load balancer, syslog, NetFlow, upgrade/archive tools. | Network Services and Ops appendix. | | Management and automation | Strong in inspected source. Config editor, ZeFS active/draft/history store, completion/ghost text, JSON diff, rollback, archive, REST/OpenAPI, gNMI, MCP, HTMX web UI, plugin SDK, and file-mode `commit confirmed` with auto-revert. No NETCONF found. | Strong in inspected source. XML config/op definitions, config session API, commit-confirm, rollback, archive, REST/OpenAPI, GraphQL, API keys/CORS/introspection, config management scripts, and OpenSSH service for device access. No gNMI/NETCONF/web UI/HTMX/MCP found in this root. | Strong CLI and NETCONF. Exec/config modes, help/completion, commit buffer, XML, NETCONF RFC 6241, HTTP API permissions, scripts/schedulers/aliases. No gNMI/OpenAPI/GraphQL/HTMX/MCP found. | Management, Automation, UI appendix. | | Observability and testing | Strong for diagnostics, logs, profiles, packet capture, BGP MRT/analyze/perf/chaos/functional tests, lint/mutation inventory in repo. | Strong for smoketest CLI/system suite, op-mode diagnostics, support archive, syslog/SNMP/flow exports, Python tests. | Strong for `.tst` topology tests, userTester, many diagnostic commands, Prometheus/server sensors, logging/debugging, packet capture. | Observability and Testing appendix. | | Platform and packaging | Strong appliance focus. Go build tags, host/target binary distinction, gokrazy image generation, installer/initrd/ISO/PXE, QEMU evidence, systemd install docs, update/export/import. | VyOS package layer. Debian package metadata, systemd/sysctl integration, image install/add-image commands, GRUB/squashfs management, containers, QEMU smoketest via vyos-build context. | Java jar plus native helpers. Shell build/package flows, Docker/Debian/systemd artifacts, VM image recipes, signed upgrade, backup/auto-revert, native/P4/XDP dependencies. | Platform and Packaging appendix. | | Extensibility | Strong explicit plugin SDK/registry and YANG/RPC/event registration. | Strong source extension pattern via XML definitions plus conf/op Python scripts, templates, validators, tests. No standalone external plugin SDK found. | Strong internal extensibility via Java registries, scripts/schedulers/aliases, NETCONF YANG generation, HTTP script/API permissions. No Ze-like external plugin SDK found. | Architecture and Extensibility appendix. | ## Product strengths and limits ### Ze - Ze is strongest where the product owns the protocol/control implementation and exposes structured APIs: BGP wire/RIB/capabilities, YANG schemas, plugin registration, REST/OpenAPI, gNMI, MCP, HTMX web UI, appliance/update flows. - Ze is narrower than VyOS/freeRtr in traditional router breadth today: no source evidence was found for RIP/RIPng/EIGRP/Babel/PIM/MSDP, general DHCP relay, SNMP, LLDP, PTP, load balancer, TFTP server, captive portal, NETCONF, GraphQL, or multi-image boot management in inspected sources. `commit confirmed` exists in file mode, but not ZeFS session mode. - Best fit from this evidence: a modern network OS core with strong BGP/control-plane architecture, API-first management, and appliance integration. ### VyOS - VyOS has the broadest operator-facing Linux router surface in `vyos-1x`: schemas and Python owners for protocols, interfaces, services, VPN, firewall/NAT, QoS, VPP, management APIs, platform config, commit-confirm, rollback, and support archive. - Source root is the CLI/config/runtime integration layer. The image builder and protocol daemon implementations are outside this checkout. The README says image builds belong in `vyos-build`, and protocol runtime commonly delegates to FRR, nftables, strongSwan, systemd, Linux, VPP, and related packages. - Best fit from this evidence: mature Linux distribution router UX and integration surface with very broad feature exposure. ### freeRtr - freeRtr has the broadest router-native protocol and service catalogue in inspected source: many routing protocols, tunnels, LSP mechanisms, crypto/VPNs, services, NETCONF, Java-based CLI/config, test topology corpus, and optional native/P4/XDP dataplanes. - Feature evidence is concentrated in large Java registries and config/runtime classes. That gives huge breadth but less small-file discoverability than Ze or VyOS. - Best fit from this evidence: all-in-one router process and protocol laboratory with exceptional feature breadth and multiple dataplane export paths. ## Source roots inspected | Product | Inspected root | Primary evidence surface | | --- | --- | --- | | Ze | `ze/` checkout | Go components, plugins, YANG modules, command/RPC declarations, docs, tests, appliance tooling. | | VyOS | `vyos-1x/` checkout | XML config/op definitions, Python `conf_mode` and `op_mode`, API services, tests, Debian metadata. | | freeRtr | `freeRtr/` checkout | Java router, config, routing, service, interface, auth, packet and user packages, plus `cfg/` tests and `misc/` platform files. | ## Evidence appendix: routing and control plane ### Feature inventory | Area | Feature | Ze evidence | VyOS evidence | freeRtr evidence | Notes | | --- | --- | --- | --- | --- | --- | | Static routing | IPv4/IPv6 static routes | Present. Static schema says prefixes validate `ipv4-prefix\|ipv6-prefix`, grouped under named tables, with metric/tag, ECMP gateway/interface next-hops, blackhole/reject: `internal/plugins/static/yang/ze-static-conf.yang:1-15`, `:24-43`, `:59-132`. User guide says static routes support ECMP, weighted load balancing, BFD failover, blackhole/reject, kernel/VPP programming: `docs/guide/static-routes.md:1-8`. | Present. CLI node `protocols static` includes `static-route.xml.i` and `static-route6.xml.i`, plus non-main kernel `table`: `interface-definitions/protocols_static.xml.in:7-13`, `:55-76`. Also static multicast route `mroute`: `:13-23`. | Present. Static route object has prefix, next-hop, interface, distance, metric, tag, route-map, route-policy, MPLS mode: `src/org/freertr/ip/ipFwdRoute.java:29-88`. | All three have IPv4/IPv6 static route support. Ze and VyOS expose schema-level named/non-main tables. freeRtr object model is direct forwarding core evidence. | | BGP core | BGP daemon, peers, ASN, router ID | Present. BGP config root with mandatory router-id and local ASN, peer groups and standalone peers: `internal/component/bgp/yang/ze-bgp-conf.yang:10-31`, `:75-82`, `:107-182`. Plugin registry imports BGP plugin: `internal/component/plugin/all/all.go:146-147`. | Present. `protocols bgp` node owned by `protocols_bgp.py`: `interface-definitions/protocols_bgp.xml.in:3-13`. | Present. `rtrBgp extends ipRtr implements prtServS` on TCP port 179, with local AS, address families and router ID fields: `src/org/freertr/rtr/rtrBgp.java:70-103`. | All three implement BGP. VyOS is schema/template wrapper around FRR. | | BGP extensions | AFI/SAFI, multiprotocol, labeled, VPN, EVPN, flowspec, MVPN, RTC, SR policy, MUP, VPLS | Ze supports extensible AFI/SAFI via registered-address-family and plugin imports for EVPN, flowspec, labeled, link-state, MUP, MVPN, RTC, SR policy, VPLS, VPN: `internal/component/bgp/yang/ze-bgp-conf.yang:552-561`; `internal/component/plugin/all/all.go:168-178`. BGP attributes include labels, RD, path-information, Prefix-SID and SRv6 Prefix-SID: `internal/component/bgp/yang/ze-bgp-conf.yang:229-244`. | VyOS BGP include tree has IPv4/IPv6 unicast and multicast, flowspec, l2vpn EVPN, link-state, route-map, route server and route reflector includes. Evidence: multicast AFI nodes in `include/bgp/protocol-common-config.xml.i:152-157`, `:485-490`; neighbor includes for IPv4/IPv6 multicast, l2vpn EVPN, link-state: `include/bgp/protocol-common-config.xml.i:1055-1059`; EVPN common route-target/RD: `include/bgp/afi-l2vpn-common.xml.i:1-15`. | freeRtr has many AFI classes: IPv4/IPv6 unicast, labeled, VPNv4/v6 unicast/multicast, link-state, MUP, EVPN, VPLS, MSPW: `src/org/freertr/rtr/rtrBgpAfi.java:658-838`, `:901-938`, `:1222-1285`. Attributes include AS_PATH, communities, extended/large/IPv6 communities, PMSI, Prefix-SID/SRv6: `src/org/freertr/rtr/rtrBgpAttr.java:411-493`, `:736-858`, `:936-953`, and Prefix-SID/SRv6 in class `rtrBgpAttrPrefSid` `src/org/freertr/rtr/rtrBgpAttr.java:1127-1191`. | freeRtr and Ze have source-level plugin/code evidence for broad AFI/SAFI. VyOS exposes FRR CLI schemas. | | BGP capability | ADD-PATH, route refresh, extended message, extended nexthop, 4-byte ASN, prefix limits | Present. Capability container covers ASN4, route-refresh, ADD-PATH with per-family limit, extended-message, extended-nexthop: `internal/component/bgp/yang/ze-bgp-conf.yang:593-669`. Prefix limits per family: `:562-585`. | Present for many capabilities in include tree, including prefix-list ORF: `interface-definitions/include/bgp/afi-capability-orf.xml.i:7-10`; route refresh and capabilities found in BGP include tree via includes, route-server/reflector at `afi-route-reflector-client.xml.i:1-5`, `afi-route-server-client.xml.i:1-5`. ADD-PATH is a per-neighbor `addpath-tx-all`/`addpath-tx-per-as` leaf: `include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i:2-10`. | Present in BGP implementation and AFI/attribute structures. freeRtr exposes per-family ADD-PATH rx/tx: `addpathRmode`/`addpathTmode` fields `src/org/freertr/rtr/rtrBgpParam.java:489-494`, render `:2499-2500`. 4-byte AS support in AS_PATH reader/writer uses `peer32bitAS`: `src/org/freertr/rtr/rtrBgpAttr.java:411-493`. | Additional capabilities exist in all three; only the cited ones are enumerated here. | | BGP GR | Graceful Restart, LLGR | Present. GR plugin configures restart-time and long-lived-stale-time, augments peer/group capability: `internal/component/bgp/plugins/gr/yang/ze-graceful-restart.yang:1-44`. | Present. Global BGP graceful-restart node with `stalepath-time`: `include/bgp/protocol-common-config.xml.i:1520-1538`; per-neighbor graceful-restart leaf (enable/disable/restart-helper): `include/bgp/neighbor-graceful-restart.xml.i:2-24`. | Present. freeRtr default filters set `graceful-restart 60000` and `longlived-graceful 0` for BGP: `src/org/freertr/cfg/cfgRtr.java:377-378`; capability encode with restart/long-lived timers is source-visible: `src/org/freertr/rtr/rtrBgpSpeak.java:211-216`, `:984`, `:995`. | All three have source evidence, with VyOS at the config-surface (FRR) level. | | Route server and route reflector | RS and RR | Ze supports route-server client and route-reflector client in core schema: `internal/component/bgp/yang/ze-bgp-conf.yang:524-542`; plugin registry also imports `rr` and `rs`: `internal/component/plugin/all/all.go:187-188`. | VyOS BGP includes route-reflector-client and route-server-client leaves: `interface-definitions/include/bgp/afi-route-reflector-client.xml.i:1-5`, `interface-definitions/include/bgp/afi-route-server-client.xml.i:1-5`; also included in AFI common flowspec and neighbor common: `include/bgp/afi-common-flowspec.xml.i:4-5`, `neighbor-afi-ipv4-ipv6-common.xml.i:172-175`. | Present. Per-neighbor route-reflector-client (`reflectClnt` field `src/org/freertr/rtr/rtrBgp.java:116`, param `rtrBgpParam.java:319`, CLI `:2246`, render `:2608`) and route-server-client (`serverClnt` param `rtrBgpParam.java:654`, CLI `:2294` "unmodified attributes to this client", render `:2557`), plus a dedicated route-server peer type `peerServr`→`routeServerClient`: `src/org/freertr/rtr/rtrBgpUtil.java:623`, `:1448-1449`. | All three have route-reflector and route-server evidence. | | RPKI | RPKI origin validation, RTR, ASPA | Present. RPKI plugin augments BGP with `rpki`, cache-server address/port/source, invalid/not-found policy, ASPA validation and policy: `internal/component/bgp/plugins/rpki/yang/ze-rpki.yang:1-67`. | Present. `protocols rpki` node owned by `protocols_rpki.py`: `interface-definitions/protocols_rpki.xml.in:1-14`; dependency file ties service rpki to protocols_rpki: `data/config-mode-dependencies/vyos-1x.json:64-65`. | Present. `rtrRpki` is "resource public key infrastructure (rfc6810) protocol", holds IPv4/IPv6 ROAs, ASPAs, keys: `src/org/freertr/rtr/rtrRpki.java:1-37`, `:72-91`, backed by `src/org/freertr/tab/tabRpkiRoa.java`, `tabRpkiAspa.java`, `tabRpkiKey.java`, `tabRpkiUtil.java`. | All three have RPKI evidence. | | Route policy | Route maps, route policies, import/export chains | Ze has BGP policy container and import/export filter chains at global/group/peer levels: `internal/component/bgp/yang/ze-bgp-conf.yang:85-106`. Filter plugin imports include as-path, community, family, IRR, modify, prefix, remove-private-as: `internal/component/plugin/all/all.go:150-157`. | VyOS has route-map includes under BGP AFIs and policy route-map definitions. Evidence: `include/bgp/afi-route-map.xml.i:1-10`, `afi-route-map-vrf.xml.i:1-14`, `afi-route-map-vpn.xml.i:1-14`; `interface-definitions/policy.xml.in` contains community and route-map match/set sections such as `:963-972`, `:1337-1360`. | freeRtr has `tabRtrmapN`, `tabRtrplc`, `tabRtrplcN`, `tabListing`, prefix list classes: `src/org/freertr/tab/tabRtrmapN.java:26-28`, `tabRtrplc.java:15-16`, `tabRtrplcN.java:20-21`, `tabListing.java:28-29`, `tabPrfxlstN.java:15-16`. CLI route-map/route-policy match/set includes AS path, RD, VRF, SRv6, segment routing: `src/org/freertr/cfg/cfgRoump.java:284-355`, `:414-428`; `cfgRouplc.java:198-274`, `:335-349`. | All three have route policy mechanisms. Ze names policy as filter chains rather than traditional route-map. | | Route redistribution | Protocol import/export | Present. Ze uses the protocol-agnostic `redistribute` framework; IS-IS guide documents `redistribute { destination bgp { import isis } }` and `destination isis { import connected/static/bgp }`: `docs/guide/isis.md:135-189`; IS-IS source/consumer code registers source `isis` and injects accepted routes into TLV 135/236 LSP entries: `internal/plugins/isis/redistribute/source.go:41-55`, `:79-107`; `internal/plugins/isis/redistribute/consumer.go:6-18`, `:147-158`. | Present. VyOS official docs document IS-IS redistribution into Level-1 and Level-2 with route-map policy: `https://docs.vyos.io/en/rolling/configuration/protocols/isis.html#route-redistribution`; local XML includes redistribution in the IS-IS include tree: `include/isis/protocol-common-config.xml.i:493-616`. | Present. freeRtr IS-IS calls generic redistribution help/config and supports per-level route-map/route-policy in/from directions: `src/org/freertr/rtr/rtrIsis.java:1282`, `:1551-1554`, `:1975-2034`. | Redistribution is route import/export policy and must not be used as proof of IS-IS inter-level leaking. | | Prefix filters | Prefix lists | Ze prefix-list filter has ordered entries, IPv4/IPv6 CIDR, ge/le, accept/reject: `internal/component/bgp/plugins/filter_prefix/yang/ze-filter-prefix.yang:1-59`. | VyOS BGP prefix-list imports for IPv4 and IPv6: `interface-definitions/include/bgp/afi-ipv4-prefix-list.xml.i:1-39`, `afi-ipv6-prefix-list.xml.i:1-39`; RIP also has prefix-list includes: `include/rip/prefix-list.xml.i:1-5`, `prefix-list6.xml.i:1-5`. | freeRtr prefix-list class `tabPrfxlstN`: `src/org/freertr/tab/tabPrfxlstN.java:15-16`; route-map/policy match network in `cfgRoump.java:301-302` and `cfgRouplc.java:215-216`. | All three have prefix filtering evidence. | | AS path filters | AS-path list or regex | Ze AS-path regex filter with ordered entries and accept/reject: `internal/component/bgp/plugins/filter_aspath/yang/ze-filter-aspath.yang:1-61`. | VyOS BGP AS-path filter list under AFI filter-list references `policy as-path-list`: `interface-definitions/include/bgp/afi-filter-list.xml.i:1-22`. | freeRtr AS_PATH read/write in BGP attrs: `src/org/freertr/rtr/rtrBgpAttr.java:411-493`; route-map/route-policy `set aspath`: `src/org/freertr/cfg/cfgRoump.java:340-343`, `cfgRouplc.java:261-264`. | All three have AS-path evidence. | | Community filters | Standard, extended, large communities | Ze community plugin defines named standard, large, extended communities and tag/strip ingress/egress filters: `internal/component/bgp/plugins/filter_community/yang/ze-filter-community.yang:1-84`. BGP session community send control: `internal/component/bgp/yang/ze-bgp-conf.yang:475-501`. | VyOS policy has `extcommunity-list`, `large-community-list`, large community match and set: `interface-definitions/policy.xml.in:277-279`, `:326-368`, `:963-972`, `:1337-1360`. | freeRtr BGP attributes read/write standard, extended, large, IPv6 communities: `src/org/freertr/rtr/rtrBgpAttr.java:736-858`. Route-map/policy community set: `src/org/freertr/cfg/cfgRouplc.java:260-274`. | All three have community support. | | OSPF | OSPFv2 | Present. Ze OSPF container described as OSPFv2 routing instance: `internal/plugins/ospf/yang/ze-ospf-conf.yang:206-223`. Plugin registry imports OSPF: `internal/component/plugin/all/all.go:260-261` and OSPF files present in `internal/plugins/ospf`. | Present. `protocols ospf` node owned by `protocols_ospf.py`: `interface-definitions/protocols_ospf.xml.in:1-14`. | Present. OSPF classes found in freeRtr routing package: `rtrOspf4` and interface classes in `src/org/freertr/rtr`, plus config defaults for `router ospf[46]`: `src/org/freertr/cfg/cfgRtr.java:283-298`. | All three have OSPFv2 evidence. | | OSPF | OSPFv3 | Present. Ze OSPF schema has reusable OSPFv3 AF topology, OSPFv3 areas/interfaces, virtual-link, IPv6 BFD, SR: `internal/plugins/ospf/yang/ze-ospf-conf.yang:14-111`, `:175-204`. | Present. `protocols ospfv3` node owned by `protocols_ospfv3.py`: `interface-definitions/protocols_ospfv3.xml.in:1-14`. | Present. freeRtr config defaults match `router ospf[46]`, covering OSPF4 and OSPF6: `src/org/freertr/cfg/cfgRtr.java:283-298`; interface defaults also `router ospf[46]`: `src/org/freertr/cfg/cfgIfc.java:1876-1880`. | All three have OSPFv3 evidence. | | OSPF extensions | SR, TE, BFD, LDP sync, GR | Ze OSPF schema has BFD container, LDP sync, IPsec, SR MPLS with SRGB/SRLB/prefix-sid, and OSPF GR files in package. Evidence: `internal/plugins/ospf/yang/ze-ospf-conf.yang:94-111`, `:149-173`, `:175-204`; OSPF GR files listed in glob including `gr.go`, `gr_helper.go`, `gr_lsa.go`. | VyOS OSPF include has segment-routing and redistribution: `include/ospf/protocol-common-config.xml.i:675-678`, `:759-833`; traffic-engineering protocol has TE metric/bandwidth/admin-groups: `interface-definitions/protocols_traffic_engineering.xml.in:1-91`. | freeRtr OSPF defaults include traffic engineering, segrout, srv6 and bier at router/area and interface levels: `src/org/freertr/cfg/cfgRtr.java:283-298`; `src/org/freertr/cfg/cfgIfc.java:1876-1880`. | All have OSPF extensions. RSVP-TE is separate below. | | IS-IS | IS-IS L1/L2 | Present. Ze IS-IS config root with NET, System ID, L1/L2/L1-L2, interfaces and metrics: `internal/plugins/isis/yang/ze-isis-conf.yang:1-45`, `:65-120`. Registration says native link-state IGP ISO/IEC 10589, RFC 1195: `internal/plugins/isis/register.go:122-130`. | Present. `protocols isis` node owned by `protocols_isis.py`: `interface-definitions/protocols_isis.xml.in:1-14`. Include supports multi-topology and redistribution: `include/isis/protocol-common-config.xml.i:169-198`, `:493-616`. | Present. `rtrIsis extends ipRtr`: `src/org/freertr/rtr/rtrIsis.java:60-61`. IS-IS neighbor implements BFD client: `rtrIsisNeigh.java:30-31`. Config defaults include IS-IS segrout/bier/srv6/TE: `src/org/freertr/cfg/cfgRtr.java:316-333`. | All three have IS-IS. | | IS-IS route leaking | L1/L2 leaking | Present. Ze `spf/leak.go` implements L1<->L2 inter-level route leaking with RFC 2966 up/down bit for IPv4 and IPv6: `internal/plugins/isis/spf/leak.go:1-20`, `:69-110`. | Unclear. VyOS documents L1/L2 operation, attached-bit, and redistribution into Level-1/Level-2 with route-map policy, but the checked docs/source did not show a leak-specific command: `https://docs.vyos.io/en/rolling/configuration/protocols/isis.html#route-redistribution`; `interface-definitions/include/isis/protocol-common-config.xml.i:493-616`. | Present as IS-IS inter-level route handling. freeRtr includes an `isis inter-level routes` test with level2, both, and level1 routers and IPv4/IPv6 route checks: `cfg/rout-isis017.tst:1-19`, `:45-55`, `:90-100`, `:120-140`; IS-IS config exposes `level1`, `level2`, and `both`: `src/org/freertr/rtr/rtrIsis.java:1271-1274`. | Route leaking and redistribution are separate features. | | RIP | RIPv2 | not found in inspected sources. Searches over Ze `internal` and `docs/guide` for `package rip`, `Routing Information Protocol`, and alternate `rtrRip` found only kernel protocol labels/port names/docs catalogue, not a protocol implementation: `internal/plugins/iface/netlink/route_linux.go:41-43`, `internal/core/portname/services_table.go:855-857`. | Present. `protocols rip` node owned by `protocols_rip.py`, with distribute-list, interfaces, MD5 auth, passive interface, redistribution: `interface-definitions/protocols_rip.xml.in:1-83`. | Present. freeRtr routing package contains RIP classes, including `rtrRip4` and `rtrRip6` in the routing package; `cfgRtr.java` imports `rtrRip6`: `src/org/freertr/cfg/cfgRtr.java:35`. | Ze lacks RIP implementation in inspected source. VyOS and freeRtr have it. | | RIPng | RIPng | not found in inspected sources. Same Ze absence searches as RIP. | Present. `protocols ripng` node owned by `protocols_ripng.py`, with aggregate-address, distribute-list, interface, network, passive-interface, redistribute: `interface-definitions/protocols_ripng.xml.in:1-83`. | Present. freeRtr has `rtrRip6` class found in protocol class search and imported by cfgRtr: `src/org/freertr/cfg/cfgRtr.java:35`. | Ze lacks RIPng implementation in inspected source. | | Other IGPs | EIGRP | not found in inspected sources. Ze alternate searches for `eigrp`, `Enhanced Interior Gateway` found only kernel route protocol labels and external comparison docs: `internal/plugins/iface/netlink/route_linux.go:41-43`. | Present. `protocols eigrp` node owned by `protocols_eigrp.py`: `interface-definitions/protocols_eigrp.xml.in:1-14`. Note: Makefile removes EIGRP templates during build as support caveat: `Makefile:46-49`. | Present. `rtrEigrp extends ipRtr implements Runnable`: `src/org/freertr/rtr/rtrEigrp.java:33-34`; neighbor implements BFD client: `rtrEigrpNeigh.java:22-23`. | VyOS exposes EIGRP schema but Makefile suggests generated templates are removed, so final should characterize carefully. | | Other IGPs | Babel, OpenFabric, OLSR, PVRP/LSRP | Ze: Babel/OpenFabric not found as protocols in inspected source. | VyOS has Babel and OpenFabric protocol files in glob: `interface-definitions/protocols_babel.xml.in`, `protocols_openfabric.xml.in`; not read in detail for table. | freeRtr has Babel and OLSR classes: `rtrBabel extends ipRtr`: `src/org/freertr/rtr/rtrBabel.java:37-38`; `rtrOlsr extends ipRtr`: `src/org/freertr/rtr/rtrOlsr.java:34-35`; PVRP/LSRP config defaults: `src/org/freertr/cfg/cfgRtr.java:232-263`. | Non-goal protocols are noted because user asked "other IGPs". | | BFD | BFD core and protocol clients | Present. BFD schema says RFC 5880/5881/5883, profiles, explicit and protocol-driven sessions from BGP/OSPF/static: `internal/component/bfd/yang/ze-bfd-conf.yang:1-16`, `:47-183`. BGP peer BFD attachment: `internal/component/bgp/yang/ze-bgp-conf.yang:326-395`; static next-hop BFD: `internal/plugins/static/yang/ze-static-conf.yang:91-100`; OSPF BFD: `internal/plugins/ospf/yang/ze-ospf-conf.yang:159-172`. | Present. `protocols bfd` node with peer IPv4/IPv6, source address/interface, common timers, multihop, VRF, profiles: `interface-definitions/protocols_bfd.xml.in:1-76`. BGP neighbor BFD include: `interface-definitions/include/bgp/neighbor-bfd.xml.i:1-5`; static route BFD includes: `include/static/static-route.xml.i:55-59`, `static-route6.xml.i:54-58`. | Present. `rtrBfdIface` implements RFC5880 interface with UDP handler, timers, multiplier, key/password, neighbors: `src/org/freertr/rtr/rtrBfdIface.java:19-83`. Protocol neighbor classes implement `rtrBfdClnt`, for example BGP speaker: `rtrBgpSpeak.java:37-38`, EIGRP neighbor: `rtrEigrpNeigh.java:22-23`, IS-IS neighbor: `rtrIsisNeigh.java:30-31`. | All three have BFD. | | RIB/FIB | RIB, FIB, kernel/VPP/P4 | Ze has BGP RIB plugin tracking Adj-RIB-In/Out with ADD-PATH: `internal/component/bgp/plugins/rib/yang/ze-rib.yang:1-43`; static routes program kernel/VPP per schema: `internal/plugins/static/yang/ze-static-conf.yang:1-11`; FIB plugins imported for kernel/P4/VPP: `internal/component/plugin/all/all.go:245-247`. | VyOS uses FRR templates for routing protocols and staticd, bgpd, ospfd, zebra etc in `data/templates/frr` glob, including `staticd.frr.j2`, `bgpd.frr.j2`, `zebra.route-map.frr.j2`, `zebra.segment_routing.frr.j2`. | freeRtr has `ipFwd` forwarding core with VRF name/number, route distinguisher and RT import lists: `src/org/freertr/ip/ipFwd.java:52-143`; `tabRoute` route table class: `src/org/freertr/tab/tabRoute.java:18-20`; route entries: `tabRouteEntry.java:18-22`. | Ze and freeRtr own FIB/RIB source. VyOS delegates to FRR/Linux via templates/scripts. | | VRF | VRF/routing instances | Ze has no full VRF feature yet. Ze has named routing-table config and some per-feature VRF fields, including BFD loop/session VRF strings, but the comparison treats those as not equivalent to product VRF/interface binding: `internal/plugins/routingtable/yang/ze-routing-table-conf.yang:1-35`; `internal/component/bfd/yang/ze-bfd-conf.yang:177-228`. | VyOS dependencies and BGP include tree support VRF import/export and route leaking: `data/config-mode-dependencies/vyos-1x.json:3-5`; `include/bgp/afi-export-import.xml.i:25-35`; `include/bgp/afi-route-map-vrf.xml.i:7-13`; `include/bgp/afi-label.xml.i:3-15`; `include/bgp/afi-rd.xml.i:13-20`. | freeRtr has global list of VRFs: `src/org/freertr/cfg/cfgAll.java:507-508`, VRF creation in config parser: `src/org/freertr/cfg/cfgConfig.java:5195-5205`; `cfgVrf` class: `src/org/freertr/cfg/cfgVrf.java:51-53`; forwarding core has VRF name/number/RD/RTs: `src/org/freertr/ip/ipFwd.java:52-143`. | Ze named tables and protocol-local VRF fields are not counted as full VRF. | | Route leaking | VRF route leaking | Not present for Ze because full VRF is not present yet. IS-IS inter-level route leaking is tracked separately above and should not be conflated with cross-VRF route leaking. | Present in BGP VPN/VRF includes: export/import VRF: `include/bgp/afi-export-import.xml.i:25-35`; route-map between current AF and VRF: `include/bgp/afi-route-map-vrf.xml.i:7-13`; labels/RD/RT for leaked VPN routes: `include/bgp/afi-label.xml.i:13-39`, `afi-rd.xml.i:13-20`, `afi-route-target-vpn.xml.i:1-20`. | Present. freeRtr route-map/route-policy can set VRF: `src/org/freertr/cfg/cfgRoump.java:354-355`, `cfgRouplc.java:273-274`, and `cfgRouplc.java:901-904`; BGP VRF classes `rtrBgpVrf.java:9-10`, `rtrBgpVrfRtr.java:5-9`. | Cross-VRF leaking is No for Ze until a full VRF feature exists. | | MPLS | MPLS dataplane/labels | Ze has MPLS command plugin imported: `internal/component/plugin/all/all.go:132` and component command import `:283`; OSPF SR config uses shared `mpls-fib`: `internal/plugins/ospf/yang/ze-ospf-conf.yang:177-204`; LDP depends on FIB/kernel. | Present. `protocols mpls` node owned by `protocols_mpls.py`: `interface-definitions/protocols_mpls.xml.in:1-11`; LDP subtree: `:12-23`. | Present. Label table classes `tabLabel`, `tabLabelEntry`, etc: `src/org/freertr/tab/tabLabel.java:16-17`, `tabLabelEntry.java:20-21`; interface imports `ipMpls`: `src/org/freertr/cfg/cfgIfc.java:125-126`; BGP labeled AFIs: `src/org/freertr/rtr/rtrBgpAfi.java:688-716`. | All have MPLS evidence. | | LDP | Label Distribution Protocol | Present. Ze LDP package says RFC 5036, downstream unsolicited mode; registration name `ldp`, config root `ldp`, dependencies FIB/kernel/sysctl: `internal/plugins/ldp/register.go:1-12`, `:76-91`, `:190-223`. | Present. MPLS `ldp` node with router-id, allocation IPv4/IPv6, neighbor, discovery timers: `interface-definitions/protocols_mpls.xml.in:12-183`. | Present. LDP interface and neighbor classes: `src/org/freertr/rtr/rtrLdpIface.java:32-33`, `rtrLdpNeigh.java:36-37`, `rtrLdpTrgtd.java:21-22`. | All have LDP. | | RSVP-TE | RSVP Traffic Engineering | Present. Ze RSVP-TE registration describes RFC 3209, config root `rsvp-te`, dependencies FIB/kernel/sysctl, raw socket doctor: `internal/plugins/rsvpte/register.go:440-462`. | Not found in inspected sources. Alternate VyOS search for `rsvp`, `RSVP`, `resource reservation` over interface definitions, conf mode, templates found only generic packet protocol enum in NAT include: `interface-definitions/include/nat-rule.xml.i:75`, `:182-183`, not routing protocol RSVP-TE. | Present. RSVP interface class: `src/org/freertr/rtr/rtrRsvpIface.java:27-28`; config interface imports `rtrRsvpIface`: `src/org/freertr/cfg/cfgIfc.java:166-167`; MPLS TE client classes found in cfgIfc imports: `src/org/freertr/cfg/cfgIfc.java:35-37`. | Ze and freeRtr have RSVP-TE. VyOS RSVP-TE not found in inspected source. | | Segment Routing | SR-MPLS and SRv6 | Ze supports OSPF SR-MPLS with SRGB/SRLB/prefix-SID: `internal/plugins/ospf/yang/ze-ospf-conf.yang:175-204`; BGP update attrs include Prefix-SID and SRv6 Prefix-SID: `internal/component/bgp/yang/ze-bgp-conf.yang:239-244`; OSPFv3 SRv6 wire files exist: `internal/plugins/ospf/sr_origination_v6.go:1-4`, `sr_reception_v6.go:1-4`. | Present. `protocols segment-routing` has SRv6 interface and global locator/encapsulation/TE DB import: `interface-definitions/protocols_segment-routing.xml.in:1-183`; OSPF and IS-IS include segment-routing nodes: `include/ospf/protocol-common-config.xml.i:675-678`, `include/isis/protocol-common-config.xml.i:280-283`. | Present. BGP has segment routing fields/labels: `src/org/freertr/rtr/rtrBgp.java:120-136`; BGP Prefix-SID/SRv6 attrs parse/write: `src/org/freertr/rtr/rtrBgpAttr.java:1127-1191`; SRH interface and SRv6 translation exist: `src/org/freertr/rtr/rtrSrhIface.java:17-18`, `src/org/freertr/prt/prtSrv6.java:20-25`; OSPF/ISIS SR config defaults: `src/org/freertr/cfg/cfgRtr.java:284-297`, `:317-333`. | All have Segment Routing evidence, including SRv6. | | EVPN/VXLAN | EVPN control plane, VXLAN data/tunnel | Ze BGP plugin imports `nlri/evpn`: `internal/component/plugin/all/all.go:168`; EVPN NLRI plugin source exists. VXLAN data-plane support not found in inspected Ze routing/control-plane source. | Present. BGP EVPN common includes advertise default-gw/SVI MAC-IP, RD, route-target: `interface-definitions/include/bgp/afi-l2vpn-common.xml.i:1-15`; neighbor includes l2vpn EVPN: `include/bgp/protocol-common-config.xml.i:1058`; VXLAN interface supports multicast group: `interface-definitions/interfaces_vxlan.xml.in:29-43`. | Present. BGP EVPN classes: `src/org/freertr/rtr/rtrBgpEvpn.java:34-35`, `rtrBgpEvpnPeer.java:21-22`, `rtrBgpEvpnVpws.java:14-15`; EVPN AFI class: `src/org/freertr/rtr/rtrBgpAfi.java:938-973`; VXLAN service imported/listed in cfgAll: `src/org/freertr/cfg/cfgAll.java:108-109`, `:702-705`. | Ze has EVPN NLRI support, but VXLAN integration not found in inspected source. VyOS/freeRtr have both EVPN and VXLAN evidence. | | Multicast routing | PIM, PIM6, IGMP/MLD, MSDP, multicast RIB/BGP multicast | Ze: No PIM/MSDP multicast routing daemon found in inspected sources. BGP multicast AFI support appears in chaos/test/MRT handling but routing daemon multicast feature not source-confirmed: `internal/analyze/mrt.go:29-32`, `internal/chaos/peer/simulator_reader.go:195-200`. | Present. PIM IPv4 node: `interface-definitions/protocols_pim.xml.in:1-8`; PIM6/MLD node: `protocols_pim6.xml.in:1-8`; IGMP proxy: `protocols_igmp-proxy.xml.in:1-8`; static multicast route: `protocols_static.xml.in:13-17`; BGP IPv4/IPv6 multicast AFI nodes: `include/bgp/protocol-common-config.xml.i:152-157`, `:485-490`. | Present. PIM interface class: `src/org/freertr/rtr/rtrPimIface.java:28-29`; MSDP router and neighbor classes: `src/org/freertr/rtr/rtrMsdp.java:37-38`, `rtrMsdpNeigh.java:31-32`; BGP VPN multicast AFIs in `rtrBgpAfi.java:807-837`; PMSI tunnel attr: `rtrBgpAttr.java:936-953`. | VyOS/freeRtr have multicast routing. Ze control-plane multicast routing not found, aside from BGP multicast family tooling. | | Policy routing | Policy based routing / table steering | Present. Ze policy routing schema steers packets to alternate tables or next-hops using nftables marks and ip rule table selection: `internal/plugins/policyroute/yang/ze-policyroute-conf.yang:1-13`; match criteria and then table/next-hop/drop/accept actions: `:24-112`. | Present. VyOS exposes `policy route` and `policy route6` rule sets via `policy_route.py`: `interface-definitions/policy_route.xml.in:245-313`; shared rule config includes packet match criteria and `set table`/`set vrf`: `include/policy/route-common.xml.i:1-73`; `include/firewall/set-packet-modifications-table-and-vrf.xml.i:1-30`; apply installs nftables policy and `ip rule` fwmark table steering: `src/conf_mode/policy_route.py:190-241`; official docs confirm route policies attach to interfaces and set table/VRF: `https://docs.vyos.io/en/latest/configuration/policy/route.html`. | Present. freeRtr has interface PBR help `configure policy based routing`, target VRF/interface/nexthop parsing, and forwarding through target interface/VRF/next-hop: `src/org/freertr/ip/ipFwdIface.java:809-819`, `:1449-1466`; `src/org/freertr/tab/tabPbrN.java:35-48`, `:67-120`; `src/org/freertr/ip/ipFwd.java:2125-2168`. | All three have PBR/table-steering evidence, with different models. | | MRT | MRT dumps / import / export | Present. Ze MRT config has RFC 6396, BGP4MP_ET, peer filter, direction, updates/all/routes dump streams: `internal/plugins/mrt/yang/ze-mrt-conf.yang:1-93`. Ze analyze tools can export BMP and parse MRT, but runtime config is enough. | Not found in inspected sources as a VyOS routing config feature. Search focused on BGP and templates did not yield MRT lines. | Present. `rtrBgpMrt` is "multi-threaded routing toolkit" with MRT header/read/dump functions: `src/org/freertr/rtr/rtrBgpMrt.java:15-20`, `:156-168`, `:197-207`, `:428-451`; BGP CLI dump setup: `src/org/freertr/rtr/rtrBgp.java:2116-2120`, `:3312-3323`; `servMrt2bgp` serves MRT into BGP: `src/org/freertr/serv/servMrt2bgp.java:25-43`, `:75-82`, `:148-158`. | Ze/freeRtr confirmed. VyOS MRT not found in inspected sources. | | Telemetry exports | Flow/export, routing telemetry | Ze has BMP, MRT, and flow export plugins imported: `internal/component/plugin/all/all.go:129`, `:245-247`, `:248-251`; BMP/MRT schemas cited above. | VyOS has op-mode standardized entries for BGP, EVPN, multicast, VRF: `data/op-mode-standardized.json:3`, `:14`, `:21`, `:37`. Flow exports (NetFlow/sFlow/IPFIX) are covered in the services matrix. | freeRtr has BMP2MRT, MRT2BGP, Netflow service imports/listing in cfgAll: `src/org/freertr/cfg/cfgAll.java:23-64`, `:607-660`; servGenList exposes `bmp2mrt`, `mrt2bgp`: `src/org/freertr/serv/servGenList.java:235-253`, `:428-475`. | All three have export/ops hooks, with different scope. | ### Gaps and unclear items - Ze RIP/RIPng/EIGRP/Babel: not found in inspected sources. Searches found only kernel route protocol names and port-name tables, not protocol implementations: `internal/plugins/iface/netlink/route_linux.go:41-43`, `internal/core/portname/services_table.go:855-857`. - Ze multicast routing: PIM/MSDP/IGMP routing daemons not found in inspected sources. BGP multicast family tooling exists in chaos/analyze code, but that is not a multicast routing daemon. - Ze VXLAN integration: EVPN NLRI plugin present, VXLAN control/data-plane integration not found in inspected routing/control-plane source. - Ze VRF and cross-VRF route leaking: not present yet. Named routing tables and protocol-local VRF fields exist, but those are not counted as full VRF/interface binding or cross-VRF leaking. - VyOS RSVP-TE: not found in inspected routing sources. Alternate search found only generic NAT protocol enum for `rsvp`, not an RSVP-TE protocol config: `interface-definitions/include/nat-rule.xml.i:75`, `:182-183`. - VyOS EIGRP: the CLI schema is present (`interface-definitions/protocols_eigrp.xml.in`), but the build removes the generated EIGRP templates (`Makefile:46-49`, T2472 and T2773). Treat as schema-present rather than production-shipped. - VyOS policy routing: present in local `vyos-1x` source via `policy route`/`policy route6`, nftables policy rendering, and `ip rule` fwmark table steering. - freeRtr route server: source-visible per-neighbor `route-server-client` mode (`src/org/freertr/rtr/rtrBgpParam.java:654`, `:2294`, `:2557`) and a dedicated `peerServr`→`routeServerClient` peer type (`src/org/freertr/rtr/rtrBgpUtil.java:623`, `:1448-1449`), distinct from route-reflector-client. Both route-reflector and route-server are Present. - freeRtr IS-IS L1/L2 route leaking: Present via the `inter-level` command "advertise inter-level routes" (`src/org/freertr/rtr/rtrIsis.java:1344`, parser `:1940-1941`) with `interLevels` handling (`src/org/freertr/rtr/rtrIsisLevel.java:108`, `:277`, `:774`) and RFC 2966 up/down-bit processing in LSP parse (`src/org/freertr/rtr/rtrIsis.java:688`, `:708`, `:818`); the `cfg/rout-isis017.tst` inter-level test corroborates it. - freeRtr graceful restart: BGP defaults show graceful restart and long-lived graceful (`src/org/freertr/cfg/cfgRtr.java:377-378`), and the capability encode with restart/long-lived timers is source-visible (`src/org/freertr/rtr/rtrBgpSpeak.java:211-216`, `:984`, `:995`). ## Evidence appendix: interfaces, L2, L3, and dataplane Architecture notes for the final comparison: - Ze uses a typed Go interface backend abstraction. The iface component parses config into typed entries, then dispatches lifecycle, addressing, routes, link state, tunnels, VLANs, bridge operations, MAC, MTU and route queries through the active Backend. `netlink` is the default backend, `vpp` is also registered, and OnConfigure loads the configured backend. Some interface leaves and kinds are still explicitly Linux/netlink-gated, including DHCP client, mirror, veth, bridge, tunnel, WireGuard, XFRM and PPPoE, so Ze is narrower than VyOS/freeRtr in appliance breadth, but its interface control plane is not netlink-only. - VyOS is schema-first around interface-definitions XML and Python conf_mode owners. Feature breadth is high in the inspected source: many discrete interface kinds are first-class CLI nodes, with includes for common addressing, DHCP, VRF, MTU, IPv6 ND, mirror, VLAN, QoS and containers. VPP has parallel interface definitions for selected kinds. - freeRtr centralizes a very large router-native interface model in cfgIfc.java and related cfg classes, with broad protocol handlers and custom dataplane backends. It exposes many interface and tunnel functions through cfgIfc commands, VPDN types, service daemons and native/P4/XDP dataplane code. It is broad and router-specific, but feature evidence often lives in large Java config surfaces rather than small per-feature files. - Paths likely worth citing in the final comparison are the files listed above, especially Ze `backend.go`, `register.go`, netlink and VPP backend registrations, `ifacevpp.go`, VyOS `interface-definitions/interfaces_*.xml.in` and `include/interface/vif-s.xml.i`, and freeRtr `cfgIfc.java`, `cfgVpdn.java`, `cfgInit.java`, `misc/native/p4emu_*.h` and `p4xdp_*.c`. ## Evidence appendix: security, firewall, NAT, VPN, and AAA ### Feature inventory | Area | Feature | Ze evidence | VyOS evidence | freeRtr evidence | Notes | | --- | --- | --- | --- | --- | --- | | Firewall primitives | Tables, chains, hooks, policies | Ze firewall guide says Ze manages nftables packet filter and NAT from one `firewall {}` YANG section, with tables, base chains, type, hook, priority, policy: `docs/guide/firewall.md:1-24`. YANG enumerates families `inet`, `ip`, `ip6`, `arp`, `bridge`, `netdev`, chain types `filter`, `nat`, `route`, hooks `input`, `output`, `forward`, `prerouting`, `postrouting`, `ingress`, `egress`, policies `accept/drop`: `internal/component/firewall/yang/ze-firewall-conf.yang:16-51`. | VyOS firewall root and flowtable present: `interface-definitions/firewall.xml.in:1-30`; IPv4, IPv6, bridge hook includes: `interface-definitions/firewall.xml.in:361-394`. | freeRtr ACL object is `access-list`: `src/org/freertr/cfg/cfgAceslst.java:63-73`; ACL help covers permit/deny, protocol, source/target address and ports, flags, TOS, DSCP, TTL, SGT, length, log: `src/org/freertr/cfg/cfgAceslst.java:81-146`. Interface attaches access groups: `src/org/freertr/cfg/cfgIfc.java:1697-1700`. | Ze and VyOS expose nft/netfilter concepts. freeRtr exposes router-style ACLs and interface attachments rather than nft-style tables. | | Firewall primitives | Match/action richness | Ze match table includes source/destination address, ports, protocol, interfaces, ICMP/ICMPv6, conntrack state, marks, DSCP, TCP flags, named sets: `docs/guide/firewall.md:59-83`; actions include accept, drop, reject, jump, goto, return, SNAT, DNAT, masquerade, redirect, notrack, flow-offload, marks, DSCP set, TCP MSS, counter, log, limit-rate, exclude: `docs/guide/firewall.md:104-129`. | VyOS firewall group/match model includes address, domain, remote, interface, IPv4/IPv6 network, MAC and port groups: `interface-definitions/firewall.xml.in:30-334`; group and zone roots are present (rule-include files not expanded here). | freeRtr ACL help covers object groups for source and target address and ports: `src/org/freertr/cfg/cfgAceslst.java:105-126`; packet fields include flags, TOS, flow, DSCP, precedence, length, TTL, SGT and log: `src/org/freertr/cfg/cfgAceslst.java:127-146`. | freeRtr has broad packet match support but not inspected as a stateful nft-style ruleset language. | | Stateful firewall | Conntrack state match | Ze YANG has `connection-state` pattern `new\|established\|related\|invalid` and marks it conntrack-driven: `internal/component/firewall/yang/ze-firewall-conf.yang:74-80` and `:154-166`. | VyOS has a dedicated `system conntrack` model for table sizing and ignore rules: `interface-definitions/system_conntrack.xml.in:1-44`; ignore rules cover IPv4 and IPv6 rule nodes with protocol, source, destination, interfaces, TCP flags: `interface-definitions/system_conntrack.xml.in:45-211`. | freeRtr provides stateful inspection via interface `inspect`, which builds a bidirectional session table: `src/org/freertr/ip/ipFwdIface.java:150`, `:1594` (`inspect = new tabSession(true, 180000)`), with `tabSession` bidir collecting plus `dropRx`/`dropTx` (allow established, drop unsolicited new): `src/org/freertr/tab/tabSession.java:62-105`; service security workers can also drop inbound connections: `src/org/freertr/serv/servGeneric.java:1028-1089`. There is no rule-level `established/related/invalid` match keyword. | Ze and VyOS are conntrack-backed. freeRtr has session-based stateful inspection (`inspect`/`tabSession`) but no rule-level state-match keyword. | | Stateful firewall | Conntrack operations and observability | Ze show command reads `/proc/sys/net/netfilter/nf_conntrack_count`, max, buckets, expectation max, accounting, timestamp, checksum, log-invalid, modules and per-protocol timeouts: `internal/component/firewall/cmd_show_conntrack.go:18-66`. | VyOS op and conf files exist for conntrack: `src/op_mode/clear_conntrack.py`, `src/op_mode/conntrack.py`, `src/op_mode/conntrack_sync.py`, `src/conf_mode/system_conntrack.py`, `src/conf_mode/service_conntrack-sync.py`. Config model evidence above. | freeRtr NAT processing keeps current translation state in `ipFwd.natTrns`, parses NAT config, finds current NAT, updates last-used and packet: `src/org/freertr/ip/ipFwd.java:259-267`, `:2220-2233`. | Ze explicitly shows Linux conntrack. VyOS has full subsystem files. freeRtr has NAT translation state plus `inspect` session tracking. | | NAT/NPT | IPv4 source, destination, static, masquerade, redirect | Ze actions: `snat`, `dnat`, `masquerade`, `redirect`, `exclude`: `docs/guide/firewall.md:113-129`; NAT exclude example and SNAT ranges: `docs/guide/firewall.md:131-173`. | VyOS IPv4 destination NAT, source NAT, static one-to-one NAT, destination redirect, source masquerade: `interface-definitions/nat.xml.in:10-44`, `:62-104`, `:106-132`. | freeRtr NAT config entry models original source/target address and ports, new source/target address and ports, source pools, randomization, session/rate limits, filter and logging: `src/org/freertr/tab/tabNatCfgN.java:21-119`, `:120-183`; VRF emits IPv4/IPv6 NAT rules: `src/org/freertr/cfg/cfgVrf.java:456-460`. | All three have NAT. Ze models NAT in firewall chains. VyOS has explicit NAT config trees. freeRtr uses `ipv[46] nat <vrf>` entries. | | NAT/NPT | NAT66/NPTv6 and NAT64 | Ze: not found in inspected sources as dedicated NPTv6 or NAT64. It can use nft `ip6`/`inet` NAT chain actions, but dedicated NPT/NAT64 model not found. | VyOS NAT66/NPTv6 model help says `Network Prefix Translation (NAT66/NPTv6)` and has source and destination NAT66 rules plus masquerade: `interface-definitions/nat66.xml.in:3-5`, `:15-24`, `:93-111`, `:128-145`. NAT64 source translation: `interface-definitions/nat64.xml.in:3-16` (owner node, source rule, translation). | freeRtr: IPv4/IPv6 NAT rules are emitted from VRF for both families: `src/org/freertr/cfg/cfgVrf.java:456-460`, `:696-699`. NAT64 translation is supported and tested via the family-agnostic `ipv[46] nat` (`src/rtr.ftr:988`, test at `src/rtr.csv:992`), but there is no dedicated `nat64` keyword and NPTv6 is absent. | VyOS has the clearest dedicated NPTv6/NAT64 configuration surface. | | Zones | Zone-based firewall | Ze: not found in inspected sources. Search for `zone`, `zone-policy`, `security-zone` found no Ze firewall zone model beyond unrelated DNS zones and AS112 zone docs. | VyOS has `firewall zone` with default-action, default-firewall, `from` zones, member interface/VRF, intra-zone filtering and local-zone: `interface-definitions/firewall.xml.in:397-568`. | freeRtr: search for `zone\|zone-policy\|security-zone\|firewall zone\|inspect .*zone` found no freeRtr zone firewall model in inspected source, only DNS/test zones. | Only VyOS has explicit firewall zones in inspected sources. | | Groups | Address, network, port, interface, domain, remote groups | Ze named sets support source-address `@set` and set elements with optional timeout: `docs/guide/firewall.md:82-83`, `:179-199`; YANG set types include IPv4, IPv6, ether, inet-service, mark, ifname: `internal/component/firewall/yang/ze-firewall-conf.yang:53-68`. | VyOS firewall group supports address-group with include, domain-group, dynamic address and IPv6 address groups, remote-group URL, interface-group, IPv6 address/network groups, MAC group, IPv4 network group, port group: `interface-definitions/firewall.xml.in:30-334`. | freeRtr ACL supports object group matching for source address, source port, target address, target port: `src/org/freertr/cfg/cfgAceslst.java:105-126`. | Ze named sets are closer to nft sets. VyOS has named group taxonomy. freeRtr object-group matching is in ACL help: `src/org/freertr/cfg/cfgAceslst.java:103-119`. | | Groups | Dynamic groups | Ze sets have `flags-dynamic` and per-element timeouts: `internal/component/firewall/yang/ze-firewall-conf.yang:637-672`; model includes `SetFlagDynamic` and timeout: `internal/component/firewall/model.go:594-603`; parser handles per-element timeout: `internal/component/firewall/config.go:1183-1248`. | VyOS has `firewall group dynamic-group address-group` and `ipv6-address-group`: `interface-definitions/firewall.xml.in:103-132`. | freeRtr: not found in inspected sources as a named dynamic firewall group. ACL reflect can create forward entries on match with timeout: `src/org/freertr/cfg/cfgAceslst.java:94-97`, which is dynamic behavior but not a named dynamic group. | Ze and VyOS have explicit dynamic set/group surfaces. | | QoS/security policing | Rate limiting and security-relevant policing | Ze firewall action `limit-rate` supports packet or byte rate plus burst: `internal/component/firewall/yang/ze-firewall-conf.yang:354-363`; guide lists `limit-rate { rate 10/second; burst 5; }`: `docs/guide/firewall.md:128-129`. Ze L2TP RADIUS extracts Filter-Id, Cisco, Juniper, Nokia, Huawei CoS/rate VSAs: `internal/component/l2tp/plugins/authradius/extract.go:64-91`, `extract_vsa.go:18-70`; L2TP guide states RADIUS Access-Request, accounting and CoA/DM: `docs/guide/l2tp.md:171-179`. | VyOS QoS class police include found in `interface-definitions/qos.xml.in:289-307`; RADIUS rate-limit for PPP/VPN accel-ppp uses Filter-Id default and enable bandwidth shaping: `interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i:1-23`. | freeRtr interfaces have `rate-limit-in`, `rate-limit-out`, `service-policy-in`, `service-policy-out`: `src/org/freertr/cfg/cfgIfc.java:1576-1622`; NAT can use a policy-map as max session rate: `src/org/freertr/tab/tabNatCfgN.java:176-187`. | All have security-relevant rate control. Scope did not cover general QoS beyond security-relevant evidence. | | IPsec/IKE | Site-to-site IPsec/IKE | Ze: not found as a first-class IPsec/IKE subsystem in inspected source. Alternate search found OSPF IPsec files and interop plan/test references, but no generic VPN IPsec config component. | VyOS full IPsec/IKE: PSK and post-quantum preshared key: `interface-definitions/vpn_ipsec.xml.in:13-77`; ESP group lifetimes, bytes, packets, mode, PFS, proposals: `:83-263`; IKE group DPD, reauth, IKEv1/IKEv2, lifetime, MOBIKE, mode, DH, PRF, proposals: `:288-700`. | freeRtr IPsec profile: transform group, cipher, hash, PRF, seconds, random, bytes, preshared key, role, protected IPv4/IPv6, ISAKMP version, replay window: `src/org/freertr/cfg/cfgIpsec.java:83-96`, `:118-160`, `:192-195`. | VyOS has strongest inspected IPsec/IKE surface. freeRtr has native crypto IPsec profile. Ze lacks generic VPN IPsec in inspected sources. | | WireGuard | WireGuard tunnels | Ze: not found in inspected sources. Alternate search found vendor `golang.zx2c4.com/wireguard` in Ze repo and test filename `tx-iface-wireguard`, but no source-owned WireGuard config/daemon component in inspected code. | VyOS WireGuard interface with private-key, peer public-key, preshared-key, allowed-ips, endpoint address/host-name/port, keepalive, fwmark: `interface-definitions/interfaces_wireguard.xml.in:1-145`. | freeRtr imports and fields for `clntWireguard`, tunnel type `wireguard`: `src/org/freertr/cfg/cfgIfc.java:53-55`, `:977-985`, `:1519-1526`, `:2614-2618`, `:2810-2815`; implementation is `clntWireguard` with protocol class and UDP packetConnect: `src/org/freertr/clnt/clntWireguard.java:30-40`, `:375-377`. | VyOS and freeRtr have WireGuard. Ze not found as product feature. | | OpenVPN | OpenVPN tunnels | Ze: not found in inspected sources. Alternate search found no Ze-owned OpenVPN component. | VyOS OpenVPN supports tun/tap, ciphers, hashes, mode `site-to-site/client/server`, UDP and TCP variants, local and remote hosts/ports, server clients/pools, reject-unconfigured-clients, TOTP MFA, shared-secret, TLS certs/CA/DH, tls-auth/crypt, peer fingerprint, TLS version and active/passive TLS role: `interface-definitions/interfaces_openvpn.xml.in:1-263`, mode site-to-site/client/server `:297-312`, server node `:452`, TOTP MFA `:745`, shared-secret-key `:828`. | freeRtr imports and fields for `clntOpenvpn`, tunnel type `openvpn`: `src/org/freertr/cfg/cfgIfc.java:41-42`, `:977-980`, `:1519-1522`, `:2614-2617`, `:2810-2812`; implementation is `clntOpenvpn` and connects UDP as `openvpn`: `src/org/freertr/clnt/clntOpenvpn.java:26-36`, `:408-410`. | VyOS has broad OpenVPN server/client surface. freeRtr has client/tunnel implementation. Ze not found. | | L2TP/IPsec | L2TP and L2TP/IPsec | Ze native L2TPv2 LNS/LAC guide says terminates L2TP tunnels, PPP auth, IP assignment, kernel dataplane via `l2tp_ppp`: `docs/guide/l2tp.md:1-15`; YANG has auth-method, no-auth opt-in, shared-secret, hello retries and listener defaults: `internal/component/l2tp/yang/ze-l2tp-conf.yang:13-107`, `:198-229`. No IPsec coupling found. | VyOS remote-access L2TP VPN includes authentication, local users, auth mode/protocols, RADIUS, RADIUS rate-limit, IPsec settings with PSK or X.509 auth and ESP group: `interface-definitions/vpn_l2tp.xml.in:5-63`, `:66-92`. | freeRtr supports L2TPv2 and L2TPv3 servers and clients: `src/org/freertr/serv/servL2tp2.java:27-33`, `:89-93`, `:190-195`; `src/org/freertr/serv/servL2tp3.java:29-35`, `:101-106`, `:201-231`; client/tunnel evidence in `cfgVpdn.java:783-785` and `cfgIfc.java:4942-4950`. L2TP over IPsec specific coupling not found. | VyOS explicitly has L2TP/IPsec. Ze has L2TP without inspected IPsec. freeRtr has L2TP but IPsec coupling not found in inspected sources. | | PKI/certificates | PKI, certificates, TLS secrets | Ze self-cert generates self-signed ECDSA P-256 certificates and TLS config min TLS 1.2: `internal/core/selfcert/selfcert.go:41-133`, `:167-183`; appliance cert replacement supports CA-signed cert/key or self-signed regeneration and writes key as secret: `internal/appliance/cmd_cert.go:20-83`, `:93-106`. | VyOS PKI supports CA certs/private keys, CRLs, system install, certificates, ACME with RSA 2048/3072/4096, DH params, key pairs, OpenSSH keys, OpenVPN shared secrets, X.509 defaults: `interface-definitions/pki.xml.in:1-263`. | freeRtr has crypto certificate and key config storing imported PEM through secret encoding: `src/org/freertr/cfg/cfgCert.java:74-77`, `src/org/freertr/cfg/cfgKey.java:69-72`; server security wrapper accepts RSA, DSA, ECDSA, MLDSA keys and certs for TLS/SSH: `src/org/freertr/sec/secServer.java:24-55`. | VyOS has the broadest PKI model. Ze has web/appliance certificate machinery. freeRtr has crypto key/cert plumbing. | | SSH | SSH server and management access | Ze SSH server built under `ze_ssh` with listen addresses, host key/cert paths, idle timeout, max sessions, users, authenticator, audit recorder, command executor, streaming authorization and accounting: `cmd/ze/hub/service_ssh.go:1-67`, `:84-137`. | VyOS SSH service supports allow/deny users/groups, ciphers, disable password auth, FIDO2 PIN/touch, dynamic protection, hostkey and pubkey algorithms, KEX, MAC, port, rekey, client keepalive, trusted user CA, VRF: `interface-definitions/service_ssh.xml.in:1-263`, `:264-315`. | freeRtr generic secure server supports `protoSsh`, `proto2string` maps `ssh`, help says `select secure shell`, and server config has `security protocol`: `src/org/freertr/serv/servGeneric.java:221-259`, `:392-416`, `:1189-1193`, `:1276-1279`; `secServer.openSec` starts `secSsh` with auth and key material: `src/org/freertr/sec/secServer.java:24-55`. | VyOS exposes most SSH hardening knobs. Ze SSH is integrated with AAA dispatcher. freeRtr SSH appears as generic server security mode. | | Users/authentication | Local users, passwords, keys, OTP | Ze local AAA backend uses built-in bcrypt user authentication according to comments and is registered priority 200 fallback: `internal/component/authz/register.go:43-72`; appliance init hashes password with bcrypt and writes `password.hash` as a secret: `internal/appliance/cmd_init.go:128-146`. | VyOS login supports operator groups, local users, encrypted-password regex for DES/MD5/SHA/yescrypt-like formats, OTP with rate-limit/rate-time/window/key, plaintext-password for encryption, SSH principals, public keys and key types: `interface-definitions/system_login.xml.in:10-234`. | freeRtr local user entries render password, secret, otpseed, hidata, pubkey and decode password/secret on config: `src/org/freertr/auth/authLocalEntry.java:170-180`, `:259-298`; authenticator supports local: `src/org/freertr/cfg/cfgAuther.java:116-143`. | All support local accounts. VyOS has explicit OTP model. freeRtr stores OTP/HID data. Ze bcrypt local auth is present; its full local-user config model is not detailed here. | | AAA | RADIUS | Ze L2TP RADIUS plugin handles Access-Request, accounting and CoA/DM per docs: `docs/guide/l2tp.md:171-179`; code has config fields servers, timeout, retries, accounting interval, NAS identifier, source address, CoA port: `internal/component/l2tp/plugins/authradius/config.go:17-31`; handler sends async RADIUS auth: `handler.go:17-67`; accounting and CoA evidence: `acct.go:41-87`, `coa.go:26-55`. System RADIUS for SSH/login not found. | VyOS system login RADIUS servers with mandatory/optional mode and auth port: `interface-definitions/system_login.xml.in:260-275`; include supports IPv4/IPv6 server, key, auth port, source address, security-mode: `interface-definitions/include/radius-server-ipv4-ipv6.xml.i:1-44`. L2TP RADIUS and CoA rate additions: `interface-definitions/vpn_l2tp.xml.in:21-30`; `include/accel-ppp/radius-additions.xml.i:119-143`. | freeRtr AAA supports RADIUS authenticator and `authRadius` stores port, secret, server, proxy, privilege: `src/org/freertr/cfg/cfgAuther.java:9-11`, `:83-104`, `:116-143`; `src/org/freertr/auth/authRadius.java:61-89`. | Ze RADIUS is L2TP PPP-focused in inspected sources, not general management AAA. VyOS and freeRtr have management login RADIUS. | | AAA | TACACS+ | Ze TACACS+ guide says SSH login TACACS+ with local fallback, authentication, accounting START/STOP, command authorization with strict fallback option: `docs/guide/tacacs.md:1-21`; YANG server key sensitive, timeout, source-address, authorization, strict-fallback, accounting, privilege profile mapping: `internal/component/tacacs/yang/ze-tacacs-conf.yang:17-82`. | VyOS TACACS+ login servers with key, port 49, source address, security-mode mandatory/optional, timeout, VRF: `interface-definitions/system_login.xml.in:276-325`. | freeRtr AAA supports TACACS and `authTacacs` stores port, secret, server, proxy, privilege: `src/org/freertr/cfg/cfgAuther.java:9-11`, `:83-104`, `:116-143`; `src/org/freertr/auth/authTacacs.java:61-89`. | All three have TACACS+ in inspected sources. Ze documents command accounting and per-command authorization in detail. | | RBAC/authz | Command authorization and RBAC | Ze authz has profile-based command authorization, ordered allow/deny entries, run/edit sections, prefix or regex matching: `internal/component/authz/authz.go:1-25`, `:32-80`, `:184-205`; TACACS maps priv levels to local profiles: `docs/guide/tacacs.md:38-50`, `:77-100`. | VyOS operator-group command-policy allow and users restricted to operator groups: `interface-definitions/system_login.xml.in:10-36`, `:45-67`. | freeRtr user entries can have privilege and autocommand defaults are filtered: `src/org/freertr/cfg/cfgAuther.java:84-89`; local entry supports `privilege`: `src/org/freertr/auth/authLocalEntry.java:170-180`, config parse at `:255-300`. | Ze has explicit allow/deny RBAC. VyOS has operator command policies. freeRtr has privilege levels plus autocommand/filter authz (`cfgAuther.java:84-99`) but no rich allow/deny RBAC. | | Secrets/password storage | Sensitive storage and redaction | Ze YANG marks L2TP shared-secret as `ze:sensitive`: `internal/component/l2tp/yang/ze-l2tp-conf.yang:67-76`; TACACS server key `ze:sensitive`: `internal/component/tacacs/yang/ze-tacacs-conf.yang:32-38`; TACACS guide says shared secrets are stored as `$9$` ciphertext, CLI strips private data: `docs/guide/tacacs.md:141-148`; appliance warns `database.zefs` contains plaintext secrets and auto-deletes unless kept: `internal/appliance/cmd_assemble.go:20-67`; password hash uses bcrypt: `internal/appliance/cmd_init.go:136-146`. | VyOS encrypted passwords accepted via regex, plaintext-password exists to generate encrypted password: `interface-definitions/system_login.xml.in:77-89`, `:154-158`; default config includes a SHA-512 style `$6$` encrypted password and empty plaintext-password: `data/config.boot.default:38-39`; RADIUS/TACACS include shared secret key: `interface-definitions/include/radius-server-key.xml.i:1-4`. | freeRtr encodes passwords/secrets via `authLocal.passwdEncode` in many configs, and documents a `2=hide secrets` filter mode: `src/org/freertr/cfg/cfgGeneric.java:23-26`; local entries encode password, secret, otpseed, hidata: `src/org/freertr/auth/authLocalEntry.java:170-180`; RADIUS/TACACS secrets encoded: `src/org/freertr/auth/authRadius.java:61-89`, `authTacacs.java:61-89`. | Ze has explicit sensitive markers in YANG. freeRtr has encoded/hide-secrets rendering. VyOS stores encrypted passwords and separate plaintext inputs. | | Control-plane protection | Control-plane policy, DoS/DDoS, hardening | Ze global-options map to sysctls: all-ping, broadcast-ping, syn-cookies, redirects, source-validation, log-martians, IPv6 redirects/source-route: `docs/guide/firewall.md:224-248`; YANG describes syn-cookies as SYN flood protection and source-validation as rp_filter: `internal/component/firewall/yang/ze-firewall-conf.yang:406-441` and `:458-472`. IRR per-interface source validation drops packets not in AS-SET prefixes: `docs/guide/firewall.md:276-308`. Dedicated CoPP not found. | VyOS firewall global options include all-ping, broadcast-ping, log-martians, source-validation, syn-cookies, IPv6 source validation: `interface-definitions/include/firewall/global-options.xml.i:7-27`, `:195-196`, `:275-344`, `:412-415`; template maps log_martians and syn_cookies sysctls: `data/templates/firewall/sysctl-firewall.conf.j2:9-13`; SSH dynamic-protection blocks source IPs with score threshold/block-time/detect-time/allow-from: `interface-definitions/service_ssh.xml.in:83-143`. Dedicated CoPP not found in inspected sources. | freeRtr service generic checks `srvIpInf` and can drop inbound service connections: `src/org/freertr/serv/servGeneric.java:1029-1085`; interface source verification exists: `src/org/freertr/cfg/cfgIfc.java:1705-1709`; interface rate limits and inspect exist: `cfgIfc.java:1576-1578`, `:1738-1739`. Dedicated CoPP not found in inspected sources. | None had an explicit CoPP feature in inspected sources. All have some control-plane hardening or service filtering hooks. | | Route filters only | Route filters, access-lists, prefix-lists | Ze firewall IRR prefix-list filtering resolves ASN/AS-SET to nft interval sets and can bind per-interface source validation: `docs/guide/firewall.md:250-308`; BGP RPKI config below. General route policy intentionally out of scope. | VyOS policy model has access-list, access-list6, as-path-list, route-filter communities, and other policy objects: `interface-definitions/policy.xml.in:8-36`, `:81-97`, `:140-152`, `:204-248`. | freeRtr route filters exist via access-list, prefix-list, route-map, route-policy in startup/defaults and router/interface filtering: `src/org/freertr/cfg/cfgInit.java:208-214`; route-policy can match access-list and prefix-list: `src/org/freertr/cfg/cfgRouplc.java:227-234`, `:435-448`. | Route policy beyond filters was not expanded per non-goal. | | RPKI | RPKI validation/servers | Ze RPKI config parses RTR cache servers with address, port 323 default, preference, source-address, validation-timeout, ASPA validation and invalid/unknown actions: `internal/component/bgp/plugins/rpki/rpki_config.go:13-45`, `:47-132`. | VyOS has protocols RPKI node and includes common config: `interface-definitions/protocols_rpki.xml.in:1-14`; PKI dependency mentions RPKI under PKI dependencies in `data/config-mode-dependencies/vyos-1x.json:58-65`. | freeRtr has `servRpki`, documented as RFC6810 server with configured IPv4/IPv6 ROAs, ASPA and keys: `src/org/freertr/serv/servRpki.java:27-80`, `:112-183`; `rtrRpki` protocol has neighbors, accepted ROAs, ASPAs, keys: `src/org/freertr/rtr/rtrRpki.java:33-94`. | All three have RPKI-related source evidence. Ze RPKI appears under BGP plugin, not firewall. | ### Gaps and unclear items Explicitly unclear or not found in inspected sources: 1. **Ze generic VPNs**: WireGuard, OpenVPN, and generic VPN IPsec/IKE were not found as first-class Ze components in inspected sources. Searches did find vendor WireGuard code and IPsec-related OSPF/interoperability references, but no source-owned generic VPN config subsystem. 2. **Ze firewall zones**: no explicit zone-policy/security-zone model found. 3. **Ze NPTv6/NAT64**: no dedicated NPTv6 or NAT64 model found. Ze can express NAT in nft `ip6/inet` chains, but dedicated NPT/NAT64 was not found. 4. **Ze system RADIUS AAA**: RADIUS was found for L2TP PPP sessions. Management login RADIUS was not found in inspected sources. 5. **freeRtr stateful firewall**: interface `inspect` builds a bidirectional `tabSession` (`src/org/freertr/ip/ipFwdIface.java:150`, `:1594`; `src/org/freertr/tab/tabSession.java:62-105`) giving CBAC/reflexive-style state; there is no rule-level `established/related` match keyword. 6. **freeRtr firewall zones**: no explicit zone-policy/security-zone model found after alternate searches. 7. **freeRtr dynamic groups**: ACL reflect with timeout was found, but named dynamic firewall groups were not found. 8. **freeRtr NPTv6/NAT64**: IPv4/IPv6 NAT support was found, but dedicated NPTv6 or NAT64 was not found in inspected sources. 9. **freeRtr L2TP/IPsec coupling**: L2TPv2/v3 and IPsec exist separately. An explicit L2TP/IPsec remote-access profile was not found. 10. **CoPP**: explicit control-plane policing as a named feature was not found in Ze, VyOS, or freeRtr inspected sources. Related controls exist, such as sysctls, SSH dynamic protection, service filters, rate limits, and source validation. ## Evidence appendix: network services and operations ### Feature inventory | Area | Feature | Ze evidence | VyOS evidence | freeRtr evidence | Notes | | --- | --- | --- | --- | --- | --- | | Address services | DHCPv4 server | Present. `service dhcp-server` YANG has `enabled`, `listen-interface`, shared-network, subnet ranges, lease time, default router, DNS servers, domain name, static MAC mappings: `ze/internal/plugins/dhcpserver/yang/ze-dhcp-server-conf.yang:11-118`. PXE boot option injection supports TFTP server, BIOS/UEFI bootfiles, iPXE HTTP boot-script URL: same file `:21-45`. | Present. CLI node `service dhcp-server` owned by `service_dhcp-server.py`, help says DHCP server: `vyos-1x/interface-definitions/service_dhcp-server.xml.in:1-15`. | Present. `servDhcp4` comment says "dynamic host config protocol (rfc2131) server and relay"; mode enum includes `server` and `relay`: `freeRtr/src/org/freertr/serv/servDhcp4.java:29-55`. Registered as DHCP4 daemon: `freeRtr/src/org/freertr/cfg/cfgAll.java:502-510`. | All three have DHCPv4 server support in inspected sources. | | Address services | DHCPv4 client | Present per interface. `interface ... ipv4 dhcp enabled`, client-id, hostname: `ze/internal/component/iface/yang/ze-iface-conf.yang:319-343`. Runtime uses `nclient4.New`, DORA Request, installs lease, renews at T1/T2: `ze/internal/plugins/iface/dhcp/dhcp_v4_linux.go:16-84`. | Present via interface DHCP options include. `include/interface/dhcp-options.xml.i` defines DHCP client settings/options: `vyos-1x/interface-definitions/include/interface/dhcp-options.xml.i:2-4`. DNS forwarding can use DHCP client nameservers: `vyos-1x/interface-definitions/service_dns_forwarding.xml.in:29-36`. | Present. `cfgIfc` imports `clntDhcp4` and has `public clntDhcp4 dhcp4c`: `freeRtr/src/org/freertr/cfg/cfgIfc.java:13-14`, `freeRtr/src/org/freertr/cfg/cfgIfc.java:1224-1232`. | Ze and freeRtr runtime client code inspected. VyOS evidence is CLI/schema level from interface include and dependent DNS forwarding. | | Address services | DHCPv4 relay | not found in inspected sources. Searches for `dhcp relay`, `relay`, `dhcpserver`, `service dhcp` found DHCP server and client but no Ze DHCPv4 relay implementation or YANG. | Present. `service dhcp-relay` node, listen/upstream interfaces, relay options: `vyos-1x/interface-definitions/service_dhcp-relay.xml.in:1-83`. | Present. `servDhcp4` is "server and relay" and has mode enum `server`, `relay`: `freeRtr/src/org/freertr/serv/servDhcp4.java:29-55`. Interface has `public servDhcp4 dhcp4r` relay service field: `freeRtr/src/org/freertr/cfg/cfgIfc.java:1008-1015`. | Ze absence is based on inspected source searches, not a product claim beyond sources inspected. | | Address services | DHCPv6 server | Partial, PPP/L2TP DHCPv6-PD server present, general service DHCPv6 server not found. L2TP PPP `IPv6Service` manages RA and DHCPv6-PD server: `ze/internal/component/l2tp/ppp/ipv6_service.go:40-54`; handles DHCPv6 Solicit, Request, Renew, Release: same file `:113-230`; Linux server socket starts `startDHCPv6Server`: `ze/internal/component/l2tp/ppp/dhcpv6_linux.go:22-72`. | Present. `service dhcpv6-server` node, help "DHCP for IPv6 (DHCPv6) server": `vyos-1x/interface-definitions/service_dhcpv6-server.xml.in:1-14`. | Present. `servDhcp6` comment says "dynamic host config protocol (rfc3315) server", mode enum includes server and relay: `freeRtr/src/org/freertr/serv/servDhcp6.java:29-55`. Registered as DHCP6 daemon: `freeRtr/src/org/freertr/cfg/cfgAll.java:512-520`. | Ze DHCPv6 server is tied to PPP/L2TP prefix delegation in inspected sources. | | Address services | DHCPv6 client | Present per interface. `ipv6 dhcpv6 enabled`, PD length, DUID: `ze/internal/component/iface/yang/ze-iface-conf.yang:377-424`. Runtime has `dhcp_v6_linux.go` listed and searched. | Present via `include/interface/dhcpv6-options.xml.i`, DHCPv6 client settings/options: `vyos-1x/interface-definitions/include/interface/dhcpv6-options.xml.i:2-4`. | Present. `clntDhcp6` client class and socket binding to DHCPv6 client/server ports: `freeRtr/src/org/freertr/clnt/clntDhcp6.java:27-28`, `:237-243`; interface config emits `ipv6 dhcp-client enable`: `freeRtr/src/org/freertr/cfg/cfgIfc.java:6925-6929`. | All three have DHCPv6 client evidence. | | Address services | DHCPv6 relay | not found in inspected Ze service sources. L2TP PPP DHCPv6 server and interface DHCPv6 client found, but no general relay. | Present. `service dhcpv6-relay` node, help "DHCPv6 Relay Agent parameters": `vyos-1x/interface-definitions/service_dhcpv6-relay.xml.in:6-8`. | Present. `servDhcp6` mode enum includes `relay`: `freeRtr/src/org/freertr/serv/servDhcp6.java:40-55`; interface has `public servDhcp6 dhcp6r`: `freeRtr/src/org/freertr/cfg/cfgIfc.java:1013-1020`. | | | DNS | Resolver and forwarder | DNS lookup tooling present, resolver service not found except GeoDNS authoritative. Diagnostics include `show dns lookup <name>`: `ze/docs/guide/production-diagnostics.md:12-16`, `:31-34`. Dedicated recursive/forwarding DNS service not found in inspected Ze sources. | Present. `service dns forwarding` node, cache size, DHCP-derived upstreams, DNS64 prefix, DNSSEC modes: `vyos-1x/interface-definitions/service_dns_forwarding.xml.in:20-83`. Conf mode uses PowerDNS recursor paths: `vyos-1x/src/conf_mode/service_dns_forwarding.py:33-39`. | Present. `servDns` is RFC1035 DNS server with `zones`, `resolvs`, `rcrsvia`, `recursEna`: `freeRtr/src/org/freertr/serv/servDns.java:29-72`. Registered DNS daemon: `freeRtr/src/org/freertr/cfg/cfgAll.java:517-520`. | Ze has command DNS lookup and GeoDNS server, not a general resolver/forwarder in inspected sources. | | DNS | Authoritative DNS | GeoDNS authoritative-style server present. YANG describes DNS answers selected by client source IP and zones served: `ze/internal/plugins/geodns/yang/ze-geodns-conf.yang:7-13`, `:65-72`. SOA fields and synthesized glue: same file `:74-126`. Host records A/AAAA/SRV and source prefix mapping: same file `:128-223`. | Present inside forwarding service. `authoritative-domain`, records, A/AAAA/CNAME/MX and more: `vyos-1x/interface-definitions/service_dns_forwarding.xml.in:109-180`, `:208-254`; conf mode builds `authoritative_zones` and records: `vyos-1x/src/conf_mode/service_dns_forwarding.py:66-88`. | Present. `servDns` has `zones` and RFC1035 server: `freeRtr/src/org/freertr/serv/servDns.java:29-47`. | VyOS authoritative records are integrated into DNS forwarding/recursor path. | | DNS | GeoDNS | Present. Explicit GeoDNS server, EDNS0 client-subnet and packet source selection, client-source prefix mapping: `ze/internal/plugins/geodns/yang/ze-geodns-conf.yang:7-13`, `:49-63`, `:207-223`. | not found in inspected sources. EDNS Client Subnet forwarding options exist (`vyos-1x/interface-definitions/service_dns_forwarding.xml.in:744-780`) but no source-IP-based answer selection (GeoDNS). | not found in inspected sources. Search for `geodns`, `geo dns`, `client-subnet`, `captive` only found EDNS0 option type in packet codec, not a GeoDNS service: `freeRtr/src/org/freertr/pack/packDnsRec.java:280-283`. | GeoDNS is a Ze differentiator in inspected network services. | | Time | NTP client/server | Client present. NTP plugin queries configured and DHCP option 42 servers, sets system clock, writes RTC and persists time: `ze/internal/plugins/ntp/ntp.go:1-10`. Config has servers, interval, max step, persist path: same file `:27-47`. No NTP server found in inspected Ze sources. | Present. `service ntp` node, allow/listen includes, timestamping, servers and pools: `vyos-1x/interface-definitions/service_ntp.xml.in:1-18`, `:113-177`. Default config enables NTP allow-client and servers: `vyos-1x/data/config.boot.default:5-23` (syslog at `:46-55`); conf mode renders chrony and manages `chrony.service`: `vyos-1x/src/conf_mode/service_ntp.py:34-36`, `:142`, `:152`. | Present. Registered NTP daemon: `freeRtr/src/org/freertr/cfg/cfgAll.java:574-575`. `servNtp` accepts connections and replies: `freeRtr/src/org/freertr/serv/servNtp.java:24-31`, `:63-64`, `:152-172`. | Ze evidence is client only; VyOS renders chrony and also has an NTP server. | | Time | PTP | not found in inspected Ze sources. | Present as NTP PTP transport and hardware timestamp filters. `service ntp ptp` and server `ptp` leaf: `vyos-1x/interface-definitions/service_ntp.xml.in:15-73`, `:168-172`. | Present. Interface PTP handler (`public ifcPtp ptp`) and config defaults: `freeRtr/src/org/freertr/cfg/cfgIfc.java:388-390`, `:96`, `:1555-1562`, `:1739-1741`; Ethernet PTP handler parses IEEE1588 and adjusts time: `freeRtr/src/org/freertr/ifc/ifcPtp.java:15-20`, `:71-85`; routed UDP PTP registers PTP ports: `freeRtr/src/org/freertr/rtr/rtrPtpIface.java:81-90`. | | | Management services | SNMP | not found in inspected Ze sources. Search found no SNMP service or YANG, except unrelated conntrack protocol name constants. | Present. `service snmp` node with community authorization and client/network controls: `vyos-1x/interface-definitions/service_snmp.xml.in:1-60`. | Present. SNMP service imported and registered: `freeRtr/src/org/freertr/cfg/cfgAll.java:88-90`, `:599-600`; `servGenList` maps `server snmp` to `servSnmp`: `freeRtr/src/org/freertr/serv/servGenList.java:459-460`. | | | Discovery | LLDP | not found in inspected Ze sources. | Present. `service lldp` node with per-interface mode disable/rx-tx/tx/rx and LLDP-MED location: `vyos-1x/interface-definitions/service_lldp.xml.in:1-73`, per-interface mode disable/rx-tx/tx/rx: `:32-58`; LLDP SNMP database toggle: same file `:209-211`. | Present. Interface LLDP handler field and defaults: `freeRtr/src/org/freertr/cfg/cfgIfc.java:372-375`, `:1555-1557`, `:6585-6587`. LLDP implementation stores neighbors and transmits advertisements: `freeRtr/src/org/freertr/ifc/ifcLldp.java:27-48`, `:157-164`, `:387-390`. | | | NAT and conntrack | NAT helpers or ALGs | NAT implementation present, helper list found in system conntrack. Ze `internal/component/config/system/conntrack.go` lists helper/protocol names including `snmp`, `sqlnet`, `netbios-ns`: `ze/internal/component/config/system/conntrack.go:121-124`. VPP firewall NAT Linux code exists: `ze/internal/plugins/firewall/vpp/nat_linux.go`. A full ALG/helper service inventory beyond the listed names is not established from inspected source. | Present. CLI has helpers for FTP, H.323, PPTP, SIP, TFTP: `vyos-1x/interface-definitions/system_conntrack.xml.in:272-320`. Conf mode maps ALGs to kernel modules and nft helper rules for FTP, H323, NFS, PPTP, RTSP, SIP, SQLNet, TFTP: `vyos-1x/src/conf_mode/system_conntrack.py:38-73`. | NAT support present and PPTP protocol handling present, but explicit generic NAT ALG/helper list not found in inspected sources. NAT config defaults exist: `freeRtr/src/org/freertr/cfg/cfgVrf.java:239-242`; PPTP client/server protocol classes are imported and registered: `freeRtr/src/org/freertr/cfg/cfgAll.java:77-78`, `:792-795`; PPTP client uses TCP 1723: `freeRtr/src/org/freertr/clnt/clntPptp.java:289-300`. | VyOS has the clearest explicit ALG inventory. freeRtr has protocol services and NAT config, but no source-grounded "NAT helper list" beyond protocol implementations found. | | File and boot services | TFTP/image server | DHCP PXE option injection present, TFTP server service not found. PXE config points clients at TFTP server and bootfiles: `ze/internal/plugins/dhcpserver/yang/ze-dhcp-server-conf.yang:21-45`. Appliance installer supports HTTP/PXE in plans, but a general TFTP server was not found in inspected sources. | Present. `service tftp-server` node, directory, allow-upload, port 69, listen address/VRF: `vyos-1x/interface-definitions/service_tftp-server.xml.in:1-29`. | Present. TFTP daemon registered: `freeRtr/src/org/freertr/cfg/cfgAll.java:557-560`; service imported: same file `:97-99`; client default supports `client tftp-proxy`: same file `:1532`. | Ze can advertise TFTP/PXE settings but no TFTP server found. | | HTTP and API | HTTP server or API | Present. Feature-gated REST HTTP API server starts listeners and OpenAPI spec: `ze/cmd/ze/hub/service_rest.go:1-83`. Web service feature-gated, default `0.0.0.0:3443`, registers portal services, starts web server: `ze/cmd/ze/hub/service_web.go:1-83`. gRPC also present: `ze/cmd/ze/hub/service_grpc.go:1-78`. | Present. `service https` node with HTTP API keys, REST API, GraphQL API: `vyos-1x/interface-definitions/service_https.xml.in:1-83`. | Present. HTTP daemon registered: `freeRtr/src/org/freertr/cfg/cfgAll.java:490`; `servHttp` is HTTP RFC2616 server with clear/secure ports 80/443 and host list: `freeRtr/src/org/freertr/serv/servHttp.java:22-48`. | All three have HTTP server/API evidence, with different scope. | | HTTP and API | Proxy | not found in inspected Ze sources. | Present. `service webproxy` node with safe ports, SSL safe ports, authentication settings: `vyos-1x/interface-definitions/service_webproxy.xml.in:1-83`. System proxy config also exists: `vyos-1x/interface-definitions/system_proxy.xml.in`. | Present. `client http-proxy`, `client name-proxy`, `client tftp-proxy`, generic client proxy defaults: `freeRtr/src/org/freertr/cfg/cfgAll.java:1528-1534`; `clntProxy` class: `freeRtr/src/org/freertr/clnt/clntProxy.java`. | | | HTTP and API | Load balancing | not found in inspected Ze sources. | Present. HAProxy load balancing node, service frontend, backend members, listen address, mode, HTTP compression, redirect HTTP to HTTPS: `vyos-1x/interface-definitions/load-balancing_haproxy.xml.in:1-83`. WAN load balancing also present: `vyos-1x/interface-definitions/load-balancing_wan.xml.in`. | Present. Load balancer daemon registry: `freeRtr/src/org/freertr/cfg/cfgAll.java:452-460`; `servLoadBalancer` imported: same file `:56`. | | | Captive portal | Captive portal service or options | not found in inspected Ze sources. | DHCP/RA captive portal option present, not a portal implementation. `include/dhcp/captive-portal.xml.i` defines "Captive portal API endpoint": `vyos-1x/interface-definitions/include/dhcp/captive-portal.xml.i:1-10`; included in DHCP option-v4/v6 and router-advert: `vyos-1x/interface-definitions/include/dhcp/option-v4.xml.i:6-8`, `vyos-1x/interface-definitions/include/dhcp/option-v6.xml.i:6-8`, `vyos-1x/interface-definitions/service_router-advert.xml.in:18-19`. | not found in inspected sources. Search for `captive`, `portal`, `hotspot` found no freeRtr captive portal service. | VyOS has endpoint advertisement, not captive portal enforcement based on inspected sources. | | Logging | Syslog and logging destinations | Structured logging present, remote syslog destination not found in inspected Ze sources. Logging commands exist under `show log` YANG anchor: `ze/internal/component/cmd/log/yang/ze-cli-log-cmd.yang:4-8`. | Present. System syslog has console, remote host, port 514, TCP/UDP, source address, VRF, TLS auth modes, local messages: `vyos-1x/interface-definitions/system_syslog.xml.in:1-123`. | Present. Syslog daemon registered: `freeRtr/src/org/freertr/cfg/cfgAll.java:452-455`; syslog client implements RFC3164 and UDP to `servSyslog` port: `freeRtr/src/org/freertr/clnt/clntSyslog.java:10-15`, `:91-96`. Logging defaults include buffered, monitor, syslog, file, IRC: `freeRtr/src/org/freertr/cfg/cfgAll.java:1492-1501`. | | | Flow telemetry | NetFlow, sFlow, IPFIX | Present. Flow-export YANG explicitly covers "sFlow, NetFlow v9, IPFIX", collector protocol enum, packet sampling, conntrack export, BGP enrichment: `ze/internal/plugins/flowexport/yang/ze-flowexport-conf.yang:5-43`, `:73-119`, `:121-141`. | Present. sFlow config with agent, polling, sampling, server port 6343, VPP sampling, egress: `vyos-1x/interface-definitions/system_sflow.xml.in:1-123`. VPP IPFIX config requires interfaces and has op show collectors/interfaces/table: `vyos-1x/src/conf_mode/vpp_ipfix.py:1-83`, `vyos-1x/op-mode-definitions/vpp_ipfix.xml.in:1-31`. | NetFlow present. `clntNetflow` says "netflow (rfc3954) client", exports sessions over UDP: `freeRtr/src/org/freertr/clnt/clntNetflow.java:11-16`, `:94-100`. `servNetflow` says "netflow (rfc3954) server" with sample/rate/mac/before/after/dropped options: `freeRtr/src/org/freertr/serv/servNetflow.java:17-23`, `:34-60`. sFlow/IPFIX not found in inspected freeRtr sources. | Ze and VyOS cover all three named protocols in inspected sources. freeRtr inspected source evidence supports NetFlow only. | | Diagnostics | Ping | Present. Diagnostics guide lists `monitor ping <target>`; CLI model supports monitor ping parser, defaults and rendering: `ze/docs/guide/production-diagnostics.md:12-17`; `ze/internal/component/cli/model_ping.go:97-121`, `:180-218`. | Present. Makefile explicitly wires recursive op command templates for `ping`: `vyos-1x/Makefile:64-70`; op mode `ping.py` exists. | Present. CLI help has `ping`, implementation calls `doPing`, output line "pinging …": `freeRtr/src/org/freertr/user/userExec.java:2309-2312`, `:3396-3398`, `:4788-4955`. | | | Diagnostics | Traceroute | Present. Diagnostics guide lists `show traceroute` and `monitor traceroute`: `ze/docs/guide/production-diagnostics.md:8-15`, `:31-34`. | Present. Makefile wires `traceroute` and `monitor/traceroute`: `vyos-1x/Makefile:64-71`; op mode `traceroute.py` exists. | Present. CLI help has `traceroute` and `mtr`; implementation uses `prtTraceroute` and prints "tracing …": `freeRtr/src/org/freertr/user/userExec.java:2279-2284`, `:4400-4420`, `:4594-4615`. | | | Diagnostics | MTR | not found as MTR command in inspected Ze sources. Ze has monitor ping and traceroute. | Present. Makefile wires `mtr`: `vyos-1x/Makefile:64-70`; op mode `mtr.py` and `mtr_execute.py` exist. | Present. CLI help includes `mtr` next to traceroute: `freeRtr/src/org/freertr/user/userExec.java:2279-2282`; mtracker status is also exposed: same file `:1605-1608`. | | | Diagnostics | Packet capture | Present. Diagnostics guide: `show capture interface eth0 ... format text/pcap`, `show capture raw start/dump`, "replaces tcpdump": `ze/docs/guide/production-diagnostics.md:35-49`, `:62-65`. MRT conversion to pcap exists: `ze/internal/analyze/convert.go:18-30`, `:115-131`. | Present. Makefile comment says "tcpdump, ping, traceroute and mtr" recursive templates and wires `monitor traffic interface`: `vyos-1x/Makefile:64-71`; op mode traffic/capture tooling appears in `src/op_mode` glob and `monitor traffic interface`. | Present. Packet command class exists: `freeRtr/src/org/freertr/user/userPacket.java:91-120`; tester issues `packet capture <ifc> <file>.pcap`: `freeRtr/src/org/freertr/user/userTester.java:2423-2429`, `:2480-2482`. | | | Diagnostics and stats | Traffic generation and traffic statistics | Traffic statistics present, BGP traffic replay/generation present. Traffic usage eBPF counters and `show traffic usage`: `ze/docs/guide/traffic-usage.md:1-17`, `:104-119`. MRT inject/replay/serve tools send BGP UPDATE traffic: `ze/internal/analyze/register.go:23-25`; `inject: sent` and `serve: sent`: `ze/internal/analyze/inject.go:146-147`, `ze/internal/analyze/serve.go:169-170`. General L3 traffic generator not found in inspected sources. | Traffic monitoring present, plus an iperf-based bandwidth generator via `execute bandwidth-test initiate`: `vyos-1x/op-mode-definitions/execute-bandwidth-test.xml.in:30-52`, `vyos-1x/src/op_mode/execute_bandwidth_test.sh:32`. Makefile wires `monitor traffic interface`: `vyos-1x/Makefile:64-71`. | Traffic stats present, testing tping present. `show interface traffic/hwtraffic/swtraffic/ptraffic` commands: `freeRtr/src/org/freertr/user/userShow.java:2197-2229`; interface show table mode documents traffic, packet traffic and totals: `freeRtr/src/org/freertr/cfg/cfgIfc.java:6354-6359`. Tests use `tping`: `freeRtr/cfg/basic01.tst:19`. | "Traffic generation" is strongest in Ze for BGP replay and freeRtr test harness `tping`; VyOS provides an iperf bandwidth generator, not a generic packet crafter. | | Route visibility | Route looking glass or route viewer | Present. Looking-glass service starts HTTP server, dispatches commands, binds listeners, TLS optional: `ze/cmd/ze/hub/service_lg.go:1-37`, `:39-103`. Env keys include `ze.looking-glass.enabled/listen/tls`: `ze/internal/component/config/environment.go:87-91`. BGP RIB show routes command exists: `ze/internal/component/bgp/plugins/rib/rib_commands.go:130-146`. | Dedicated looking glass not found in inspected sources. Route op-mode exists, `src/op_mode/route.py`, but no "looking glass" server or route viewer found. | Dedicated web looking glass not found in inspected sources. CLI route show tables are present: `show ipx` route tables and `show route` style help in `userExec`: `freeRtr/src/org/freertr/user/userExec.java:360-390`; BGP packet/route dump tools exist in `userPacket`. | Ze has the clearest actual looking-glass service. VyOS and freeRtr have CLI route visibility, not an inspected looking-glass service. | | Lifecycle | Update, config archive, backup | Present for appliances. Encrypted `ze appliance export`, `export --all`, archives always encrypted: `ze/internal/appliance/cmd_export.go:20-78`, `:107-132`. OTA push via gokrazy updater with `--testboot`, `--no-reboot`, `--all`: `ze/internal/appliance/cmd_push.go:1-80`. Import validates restored config: `ze/internal/appliance/cmd_import.go:88-120`. | Present. Config management op-mode exposes commit diff/file/log: `vyos-1x/src/op_mode/config_mgmt.py:20-68`. Config-sync service definition exists: `vyos-1x/interface-definitions/service_config-sync.xml.in:6-7`. Tech support archive and upload support: `vyos-1x/src/op_mode/generate_tech-support_archive.py:31`, `:55-103`. | Present. Config backup/archive defaults and variables: `freeRtr/src/org/freertr/cfg/cfgAll.java:1457-1465`, `:1545-1560`. Flash archive/extract/clean backup/upgrade/simulate/backup/revert: `freeRtr/src/org/freertr/user/userFlash.java:310-380`. Software upgrade backup: `freeRtr/src/org/freertr/user/userUpgrade.java:354-356`. | | | Supportability | Diagnostics and support bundle | Present. Production diagnostics guide covers tcp-check, traceroute, kernel logs, CPU/heap profiles, file descriptors, goroutines, DNS lookup, ping, netlink, packet capture, sockets, memory: `ze/docs/guide/production-diagnostics.md:1-65`, `:70-183`. Doctor checks registered for appliances: `ze/internal/appliance/doctor_checks.go:13-26`, `:37-56`. No single "support bundle" archive command found in inspected Ze sources. | Present. `generate_tech-support_archive.py` creates tech-support archive, runs `show tech-support report`, archives `/var/log`, `/tmp`, `/home`, config dirs with filters: `vyos-1x/src/op_mode/generate_tech-support_archive.py:28-31`, `:43-63`, `:65-103`. | Partial. Diagnostics are CLI based, no single tech-support bundle found. Packet tools, `show backup-config`, traffic, netflow, mtracker, packet decode exist: `freeRtr/src/org/freertr/user/userShow.java:804`, `:1355`, `:2094`, `:2197`; archive/backup tools in `userFlash`: `freeRtr/src/org/freertr/user/userFlash.java:310-380`. | VyOS has explicit support archive. Ze has broad built-in diagnostics but no inspected bundle. freeRtr has operational diagnostics and archive/backup, no inspected bundle. | ### Gaps and unclear items - Ze DHCPv6 server: source evidence confirms DHCPv6-PD server for PPP/L2TP sessions, not a general LAN DHCPv6 server. General `service dhcpv6-server` was not found in inspected Ze sources. - Ze DHCP relay, SNMP, LLDP, PTP, proxy, load-balancer, TFTP server, captive portal, remote syslog destination and MTR: not found in inspected sources after targeted and alternate searches. - Ze NAT helpers: NAT and conntrack files exist, but a full ALG/helper inventory is not established from inspected source. - VyOS captive portal: only DHCP/RA option advertisement of a captive portal API endpoint was found, not an implementation of portal enforcement. - VyOS GeoDNS and dedicated route looking-glass service: not found in inspected sources. DNS authoritative records and route op-mode tools exist. - VyOS traffic generation: an iperf bandwidth generator exists (`execute bandwidth-test initiate`), but no generic packet-crafting generator. - freeRtr sFlow/IPFIX: NetFlow client/server found, but sFlow and IPFIX were not found in inspected sources. - freeRtr captive portal and dedicated support bundle: not found in inspected sources. - freeRtr NAT helpers: NAT config and protocol services found, but explicit ALG/helper inventory was not found in inspected sources. ## Evidence appendix: management, automation, UI, and API ### Feature inventory | Area | Feature | Ze evidence | VyOS evidence | freeRtr evidence | Notes | | --- | --- | --- | --- | --- | --- | | CLI modes | Operational and configuration mode split | `ze config edit` help lists config editor commands and mode switching: `command` switches to operational mode and `edit` switches back, `ze/internal/component/config/cli/cmd_edit.go:343-380`. `ze config` dispatcher separates storage-aware config commands from plain validation/migration/completion handlers, `ze/internal/component/config/cli/main.go:31-61`. | Repo layout explicitly separates configuration interface definitions, operational command definitions, `src/conf_mode`, and `src/op_mode`, `vyos-1x/README.md:15-26`. Config session API wraps CLI shell config mode, `vyos-1x/python/vyos/configsession.py:34-82`. | `userExec.getHelping()` defines exec commands including `show`, `watch`, `netconf`, `xml`, `terminal`, `freeRtr/src/org/freertr/user/userExec.java:2108-2185`. `userConfig` has config mode prompt `(cfg)` or `(cfg-<submode>)` via `getPrompt()`, `freeRtr/src/org/freertr/user/userConfig.java:179-185`. | All three have CLI modes. Ze and freeRtr evidence shows mode switching in source. VyOS source root has definitions and APIs, while full shell implementation is outside this repo. | | CLI modes | Submode/context editing | Ze editor help lists `edit <path>`, `edit <list> *`, `top`, `up`, `ze/internal/component/config/cli/cmd_edit.go:356-361`. | XML interface definitions model hierarchical configuration commands, and README says conf-mode definitions are generated from unified XML format, `vyos-1x/README.md:34-43`. | `userConfig` routes commands to `submode.doCfgStr(cmd)` when submode is set, `freeRtr/src/org/freertr/user/userConfig.java:286-294`, and `getPrompt()` reflects submode, `freeRtr/src/org/freertr/user/userConfig.java:179-185`. | All support hierarchical config context. | | Completion and help | Interactive completion and `?` help | Ze editor help documents Tab completion, dropdown cycling, and ghost text, `ze/internal/component/config/cli/cmd_edit.go:374-379`. Ze also has non-interactive completion with `--json` and `--ghost`, `ze/internal/component/config/cli/cmd_completion.go:1-132`. | VyOS has completion helpers under `src/completion` in README layout, `vyos-1x/README.md:23-27`, and XML `completionHelp` is used, for example commit-confirm action values `reload reboot`, `vyos-1x/interface-definitions/system_config-management.xml.in:66-86`. | `userReader` stores help context and handles tab completion via `help.guessLine(curr)`, `freeRtr/src/org/freertr/user/userReader.java:1213-1215`, and `?` help via `cmdShowHelp()` and `help.getHelp(curr, false)`, `freeRtr/src/org/freertr/user/userReader.java:1505-1510,1734-1735`. | All have completion/help. Ze is most explicit about machine-queryable completions. | | Completion and help | Generated command reference | Ze `ze help command [filter] [--json]` walks YANG verbs plus offline local commands and emits command catalog entries, `ze/cmd/ze/help_command.go:1-54,73-127`. | VyOS Makefile generates config node templates and op-mode templates from XML, and exports `op_cache.json`, `vyos-1x/Makefile:29-63`. README says all raw `node.def` files are generated from unified XML, `vyos-1x/README.md:34-43`. | `userHelp` builds help trees and generates YANG via `getYang`, `freeRtr/src/org/freertr/user/userHelp.java:1249`; `userNetconf.makeYang()` builds YANG from config help/context, `freeRtr/src/org/freertr/user/userNetconf.java:87-139` (`makeYang` at `:104`). | VyOS generates command implementation templates and op cache, not necessarily final human docs in this repo. Ze directly exposes a command catalog. | | Candidate config | Candidate/draft model | ZeFS namespaces include `file/active/`, `file/draft/`, and dated history, `ze/docs/architecture/zefs-format.md:177-183`. Startup clears stale candidate on boot via `storage.ClearCandidate`, `ze/cmd/ze/hub/main.go:127-165`. | ConfigSession creates a session with session env and uses `_session_config`/`_running_config` in `ConfigMgmt`, `vyos-1x/python/vyos/config_mgmt.py:184-193`; ConfigSession has `set`, `delete`, `commit`, `discard`, `vyos-1x/python/vyos/configsession.py:265-368`. | `userConfig` has an optional `commits` buffer. If `commits != null`, config commands are stored instead of applied, and `commit` executes buffered commands, `freeRtr/src/org/freertr/user/userConfig.java:286-306`. `userExec` tracks rollback-protected and committed config sessions, `freeRtr/src/org/freertr/user/userExec.java:158-166`. | Ze and VyOS clearly expose candidate/session semantics. freeRtr has a commit buffer mechanism in source, but default execution path also applies directly when `commits == null`. | | Commit | Commit/apply | Ze web commit handler calls `mgr.Commit(username)` and handles conflicts, `ze/internal/component/web/handler_config_commit.go:112-160`. Ze editor help documents `commit` as saving changes and creating backup, `ze/internal/component/config/cli/cmd_edit.go:365-366`. API setup wires session commit hook to reload hook, `ze/cmd/ze/hub/api.go:64-68`. | ConfigSession `commit()` invokes `my_commit` or `vyconf_session.commit()`, `vyos-1x/python/vyos/configsession.py:345-352`. ConfigMgmt has post-commit hooks for commit revision and archive, `vyos-1x/python/vyos/config_mgmt.py:59-64`. | `userConfig` global help includes `commit` and `executeCommand` executes buffered commits through `cfgInit.executeSWcommands(commits, false)`, `freeRtr/src/org/freertr/user/userConfig.java:290-298` (commit help item at `:315`). | All support commit/apply in inspected code. | | Commit | Commit confirm | Ze `commit confirmed <N>` validates, saves a `.live.conf`, writes active config, starts a confirm timer, and exposes `confirm` / `confirm abort`; timeout auto-reverts via `rollbackConfirmed()`, `ze/internal/component/cli/model_load.go:30-46,72-125,128-219`. Command dispatch accepts `commit [force] confirmed <N>` but rejects it in ZeFS session mode, `ze/internal/component/cli/model_commands.go:84-105`. | `system config-management commit-confirm action` supports `reload` and `reboot`, `vyos-1x/interface-definitions/system_config-management.xml.in:61-91`; ConfigSession has `commit_confirm()` and `confirm()`, `vyos-1x/python/vyos/configsession.py:353-361`; ConfigMgmt documents timer behavior, `vyos-1x/python/vyos/config_mgmt.py:196-260`. | No timed `commit confirmed <N>`. freeRtr has a connectivity-triggered auto-revert session (`configure rollback`) that freezes a checkpoint and reverts running config if the session drops abnormally: `freeRtr/src/org/freertr/user/userLine.java:445-448`, `:479-483`. | Ze has file-mode commit-confirm with auto-revert; ZeFS session mode is not supported yet. freeRtr has a session-drop auto-revert but no timed commit-confirm. | | Rollback | Rollback/revert | Ze `ze config rollback <N> <file>` restores from backup revision, `ze/internal/component/config/cli/cmd_rollback.go:1-80`. Ze has health revert to previous/seed config, `ze/cmd/ze/health_revert.go:97-112`. | ConfigMgmt has `rollback(rev)` and `rollback_soft(rev)`, `vyos-1x/python/vyos/config_mgmt.py:333-368`. It stores rollback and pre-rollback files, `vyos-1x/python/vyos/config_mgmt.py:74-75`. | freeRtr reverts running to startup via `configure revert`, `freeRtr/src/org/freertr/user/userExec.java:3759-3772` (help at `:2658`), and offers a session-drop auto-revert, `freeRtr/src/org/freertr/user/userLine.java:445-483`; `show rollback-config` shows running-to-startup differences, `:1715-1723`. No numbered-revision rollback was found. | Ze and VyOS have explicit numbered restore. freeRtr reverts to startup and has an auto-revert session, but no numbered-revision rollback. | | Diff | Config diff/compare | Ze `ze config diff` compares two configs or revision N against current; the answer is registered as `show config diff`, so ` | json`,` | yaml`and` | table`each render it,`ze/internal/component/config/cli/cmd_diff.go`,`ze/internal/component/config/cli/config_data.go`. | | History | Command/config history | Ze `ze config history <file>` lists rollback revisions and shows draft state, `ze/internal/component/config/cli/cmd_history.go:1-77`. | Op-mode `show history` reads bash history and supports `brief` and last N, `vyos-1x/op-mode-definitions/show-history.xml.in:1-27`. ConfigMgmt rotates archived config revisions, `vyos-1x/python/vyos/config_mgmt.py:556-569`. | `userReader` maintains command history with configurable size and `getHistory()`, `freeRtr/src/org/freertr/user/userReader.java:224-246,279-305`. | Ze history is config rollback history. VyOS evidence includes both command history and commit revisions. freeRtr evidence is command-line history, not config revision history. | | Config storage/archive | Persistent config store | ZeFS stores active/draft/historical entries in one `.zefs` blob, `ze/docs/architecture/zefs-format.md:104-183`; daemon owns blob and terminal commands connect over SSH, `ze/docs/architecture/zefs-format.md:139-173`. | ConfigMgmt paths include `/config/config.boot`, archive directory, commit log, rollback files, `vyos-1x/python/vyos/config_mgmt.py:68-76`; `data/config.boot.default` sets default `commit-revisions "100"`, `vyos-1x/data/config.boot.default:26-27`. | `cfgAll` renders `client config-backup`, `client config-save`, and `client config-archive` lines, `freeRtr/src/org/freertr/cfg/cfgAll.java:4018-4025`; config files are run by `cfgInit.executeSWcommands`, `freeRtr/src/org/freertr/cfg/cfgInit.java:1119-1163`. | Ze uses custom blob storage. VyOS uses config.boot plus archive directory. freeRtr has config save/archive flags and startup execution model. | | Config storage/archive | Remote/archive upload | Ze `ze config archive <name>` triggers `request config archive <name>` via daemon and requires `system { archive { } }`, `ze/internal/component/config/cli/cmd_archive.go:1-76`. | `system config-management commit-archive location` supports http, https, ftp, sftp, scp, tftp, and git+ URLs, `vyos-1x/interface-definitions/system_config-management.xml.in:11-49`; ConfigMgmt `commit_archive()` uploads to remote archive, `vyos-1x/python/vyos/config_mgmt.py:570-573`. | `client config-archive` and `client config-backup` defaults exist (`freeRtr/src/org/freertr/cfg/cfgAll.java:1560-1562,4021-4024`); the upload target is set via `client config-server`/`config-username`/`config-password` (`:1557-1559`). | All have archive hooks/settings; freeRtr sets its upload target through `client config-server` credentials. | | Schema/model | Config schema | Ze builds config schema from YANG and plugin YANG, `ze/internal/component/config/yang_schema.go:87-143`. | VyOS uses XML definitions conforming to `schema/interface_definition.rng` and `schema/op-mode-definition.rng`, `vyos-1x/README.md:34-43`; Makefile validates XML against RNG while generating templates, `vyos-1x/Makefile:38-63`. | freeRtr builds help/config trees in Java and can generate YANG via `userNetconf.makeYang()` and `userHelp.getYang`, `freeRtr/src/org/freertr/user/userNetconf.java:87-139`; `misc/netconf/global.txt` seed starts with `config global`, `freeRtr/misc/netconf/global.txt:1-4`. | Ze has native YANG config. VyOS uses custom XML schemas, not YANG in inspected repo. freeRtr has generated YANG artifacts for NETCONF, not a primary source model. | | REST/HTTP API | REST API | Ze REST server exposes shared API engine as RESTful JSON API and handles routing, JSON, auth, CORS, OpenAPI, `ze/internal/component/api/rest/server.go:1-60,93-141`. | FastAPI REST router defines `/configure`, `/configure-section`, `/retrieve`, `/config-file`, `/image`, `/container-image`, `/generate`, `/show`, `/reboot`, `/renew`, `/reset`, `/import-pki`, `/poweroff`, `/traceroute`, `vyos-1x/src/services/api/rest/routers.py:291` (endpoints `:576-962`). HTTPS service config has API keys and REST strict/debug settings, `vyos-1x/interface-definitions/service_https.xml.in:11-54`. | HTTP host help allows `api exec`, `api show`, `api script`, `api config`, and `ipinfo`, `freeRtr/src/org/freertr/serv/servHttp.java:436-445`. Dedicated REST/OpenAPI implementation not found in inspected sources. | Ze and VyOS have first-class JSON HTTP APIs. freeRtr exposes HTTP API permissions, but a typed REST contract was not found. | | HTTP API | GraphQL | not found in inspected sources | GraphQL README shows endpoint `/graphql`, config mutations, `SaveConfigFile`, `LoadConfigFile`, and `Show` query, `vyos-1x/src/services/api/graphql/README.graphql:1-139`; router registers GraphQL ASGI app at `/graphql`, `vyos-1x/src/services/api/graphql/routers.py:1-64`. | not found in inspected sources | GraphQL is VyOS-only among inspected sources. | | HTTP API | OpenAPI/docs | Ze REST server has an `OpenAPIProvider` for `/openapi.json`, `ze/internal/component/api/rest/server.go:61-63,93-96`. | VyOS HTTP API server regenerates docs routes `/openapi.json`, `/docs`, `/redoc`, `vyos-1x/src/services/vyos-http-api-server:158-168`; FastAPI app imported in same server, `vyos-1x/src/services/vyos-http-api-server:27-30`. | not found in inspected sources | Ze and VyOS have OpenAPI evidence. freeRtr OpenAPI/swagger search returned no inspected source evidence. | | gNMI | gNMI server/API | Ze gNMI server implements OpenConfig gNMI service over gRPC, supports token and TLS, and registers with `gpb.RegisterGNMIServer`, `ze/internal/component/gnmi/server.go:1-22,115-150`. | not found in inspected sources | not found in inspected sources | No management gNMI implementation exists in the inspected VyOS or freeRtr paths. | | NETCONF | NETCONF server/API | not found in inspected sources | not found in inspected sources. Search found only a netlink event formatter comment/class named NetconfFormatter, not a NETCONF management server, `vyos-1x/src/services/vyos-network-event-logger:1054-1075`. | `userNetconf` is an RFC 6241 handler, declares NETCONF port 830, base 1.0/1.1, writable-running, startup capabilities, get-config, edit-config, `freeRtr/src/org/freertr/user/userNetconf.java:17-59,140-176,224-320`. | freeRtr has NETCONF. Ze has gNMI but no NETCONF found. VyOS repo search did not find NETCONF API implementation in inspected root. | | SSH CLI | SSH CLI access | Ze config editor probes daemon SSH and executes commands via SSH, `ze/internal/component/config/cli/cmd_edit.go:224-242,421-474`. ZeFS doc states terminal commands connect to daemon over SSH and ephemeral daemon starts when needed, `ze/docs/architecture/zefs-format.md:139-173`. | VyOS has a `service ssh` config node owned by `service_ssh.py`, `vyos-1x/interface-definitions/service_ssh.xml.in:1-10`; the service script generates OpenSSH host keys, renders `sshd_config`, validates it with `/usr/sbin/sshd -t`, and reloads or restarts `ssh@<vrf>.service`, `vyos-1x/src/conf_mode/service_ssh.py:122-197`. | `servTelnet` default includes `protocol` setting, `freeRtr/src/org/freertr/serv/servTelnet.java:52-56`; tests configure `server telnet ssh` and `security protocol ssh`, `freeRtr/cfg/crypt-ssh01.tst:22-33`. | All three expose SSH device access; Ze's is embedded daemon/editor transport, VyOS uses OpenSSH service integration, freeRtr has an SSH protocol server path. | | Web UI | Web UI | Ze has HTTPS web server with TLS and route registration, `ze/internal/component/web/server.go:1-25,84-139`; config view handler renders HTML or JSON and supports HTMX partial requests, `ze/internal/component/web/handler_config.go:132-180`. | not found in inspected sources for a browser UI. The inspected root has HTTP API service, not a web UI. | HTTP server supports directory listing, scripts, API, markdown, etc., `freeRtr/src/org/freertr/serv/servHttp.java:428-445`; README lists miscellaneous web apps such as sniffer, mailer, pastebin, gallery, motion, player, thermostat, monitoring, `freeRtr/readme.md:70-88`. A router config web UI comparable to Ze was not found in inspected sources. | Ze has clear router web UI. freeRtr has web server and web utilities. VyOS web UI not present in inspected repo. | | Frontend implementation | HTMX/frontend | Ze web source uses HTMX headers and attributes, including `HX-Request`, `HX-Redirect`, `hx-post`, `hx-target`, and bundled `htmx.min.js`, `ze/internal/component/web/auth.go:170-274`, `ze/internal/component/web/cli_terminal.go:895-901`, `ze/internal/component/web/assets/htmx.min.js:1`. | not found in inspected sources | not found in inspected sources | HTMX was found only in Ze. | | MCP/AI | MCP / AI integration | Ze `help_ai.go` generates AI help from plugin registry, YANG schemas, and RPC registrations and lists MCP tools, `ze/cmd/ze/help_ai.go:25-48,84-105`. MCP handler exposes JSON-RPC tools generated from command registry, `ze/internal/component/mcp/handler.go:1-46`. | not found in inspected sources | not found in inspected sources | Ze-only in inspected sources. | | Plugin SDK/API | Internal/external plugin SDK/API | Ze plugin registry defines `Registration` with CLI handler, engine handler, config roots, YANG, RPC handlers, dependencies, event/send types, config verifiers, metrics/event bus/server hooks, `ze/internal/component/plugin/registry/registry.go:1-120`. Plugin package documents Unix socket server, CLI/external tool communication, JSON encoder, and subprocess management, `ze/internal/component/plugin/types.go:1-11`. | not found as an external plugin SDK/API in inspected sources. VyOS has extension by adding XML definitions and conf/op scripts per README layout, `vyos-1x/README.md:15-27`. | freeRtr has Java classes for scripts, schedulers, aliases, processes, VDCs, etc., in global lists, `freeRtr/src/org/freertr/cfg/cfgAll.java:292-300,372-375`. External plugin SDK/API not found in inspected sources. | Ze has explicit plugin API. VyOS and freeRtr are extensible by source/config constructs, but no standalone external plugin SDK was confirmed. | | JSON output | JSON outputs | Ze command catalog supports `--json`, `ze/cmd/ze/help_command.go:31-54`; config completion, diff, dump, validation and the repair plan each register their answer and reach JSON through the pipe layer, `ze/internal/component/config/cli/config_data.go`; REST/MCP use JSON. | REST API parses JSON/form data into commands, `vyos-1x/src/services/api/rest/routers.py:98-225`; completion helpers call FRR JSON and `jq`, for example `show evpn es json`, `vyos-1x/src/completion/list_esi.sh:19-20`; `configsession.py` imports JSON and API routes are Pydantic/FastAPI models. | `userFormat` has a first-class `json` table-output mode: enum at `freeRtr/src/org/freertr/user/userFormat.java:39`, `str2tabmod` at `:100-101`, JSON serialization at `:874`, `:894`, CLI-wired via the `tabMod` pipe setting rendered by `userReader.formatAll`: `freeRtr/src/org/freertr/user/userReader.java:1000-1004`; `userReader` also supports `csv`/`html`/`xml` modes: `:172-182`. | All three have JSON management output; freeRtr exposes it as a `json` table mode. | | Command/op-mode organization | Command registration/organization | Ze command catalog collects YANG command tree, wire-to-path mappings, local command registry, read-only/daemon modes, backend metadata, `ze/cmd/ze/help_command.go:73-127`. | VyOS explicitly stores configuration XML under `interface-definitions/` and op-mode XML under `op-mode-definitions/`, generated via Makefile into templates and op cache, `vyos-1x/README.md:15-20`, `vyos-1x/Makefile:29-63`. | freeRtr organizes exec commands in `userExec`, config commands in `userConfig`, and many config object classes implement `getHelp` and `doCfgStr`, for example `cfgBndl` delegates help/config to bundle object, `freeRtr/src/org/freertr/cfg/cfgBndl.java:96-112`. | VyOS has the most explicit filesystem split for command definitions. Ze uses YANG/local registries. freeRtr uses Java help/method dispatch. | | Automation hooks | Scripts, schedulers, aliases, hooks | Ze archive command is daemon-driven, API has session hooks for commit validation and reload, `ze/cmd/ze/hub/api.go:41-68`. Plugin registry supports event bus and RPC handlers, `ze/internal/component/plugin/registry/registry.go:101-119`. | ConfigMgmt defines post-commit hook names `01vyos-commit-revision` and `02vyos-commit-archive`, `vyos-1x/python/vyos/config_mgmt.py:61-64`. Makefile generates activation-scripts JSON, `vyos-1x/Makefile:132-138`. | `cfgAll` has global lists for schedulers, scripts, chat scripts, and aliases, `freeRtr/src/org/freertr/cfg/cfgAll.java:292-300,327-330,372-375`; HTTP server can allow scripts and config/API commands, `freeRtr/src/org/freertr/serv/servHttp.java:436-445`. | All have automation hooks, with different models. | | Generated docs/references | Generated docs or command refs | Ze `help_ai.go` says AI reference is generated from code, plugin registry, YANG schemas, and RPC registrations, `ze/cmd/ze/help_ai.go:25-34,60-64`. `help_command.go` outputs JSON consumable by external tooling/wiki generators, `ze/cmd/ze/help_command.go:1-10`. | Makefile generates config reference cache and op cache JSON, `vyos-1x/Makefile:38-63`; README says schema checks happen at build time, `vyos-1x/README.md:41-43`. | `userNetconf.makeYang()` generates YANG from help/config inputs, `freeRtr/src/org/freertr/user/userNetconf.java:87-139`; `misc/netconf` contains text seed files for generated netconf definitions, `freeRtr/misc/netconf/global.txt:1-4`. | All generate some machine/reference artifacts, but Ze has source-level AI/command catalog generation. | ### Gaps and unclear items - **Ze NETCONF:** not found in inspected sources. No management NETCONF implementation is present under Ze `cmd`, `internal`, `docs`, or tests. - **Ze commit-confirm session mode:** file-mode `commit confirmed <N>` exists with auto-revert, `confirm`, and `confirm abort`, but `model_commands.go` rejects it in ZeFS session mode. - **VyOS gNMI:** not found in inspected sources. No gNMI management server exists under `src`, `python`, `interface-definitions`, `op-mode-definitions`, `schema`, or `README.md`. - **VyOS NETCONF:** not found as management API in inspected sources. The only `netconf` hits were netlink-event comments/classes in `vyos-network-event-logger`, not NETCONF RPC/server code. - **VyOS web UI/HTMX/MCP:** not found in inspected sources. The repo contains HTTP API/GraphQL services but no browser router UI or HTMX/MCP integration in inspected paths. - **freeRtr REST/OpenAPI/JSON management output:** freeRtr has HTTP API permissions (`servHttp.java:439-444`) and XML/NETCONF sessions but no typed REST/OpenAPI surface; JSON CLI output does exist as a `json` table mode (`userFormat.java:39`). - **freeRtr rollback:** `configure revert` restores running to startup (`userExec.java:3759-3772`) and a session-drop auto-revert exists (`userLine.java:445-483`), but there is no numbered-revision rollback like Ze/VyOS. - **freeRtr external plugin SDK:** scripts, schedulers, aliases, VDCs, processes, and HTTP script/API hooks exist, but a standalone external plugin SDK/API comparable to Ze was not found in inspected sources. - **freeRtr HTMX/MCP/gNMI:** not found in inspected sources. ## Evidence appendix: platform, packaging, and appliance ### Feature inventory | Area | Feature | Ze evidence | VyOS evidence | freeRtr evidence | Notes | | --- | --- | --- | --- | --- | --- | | Build system | Primary build tool | Go toolchain plus the compiled `./le` development command. Native actions register under `internal/le/`, product binaries build directly from `cmd/`, and each generated artifact has one Go owner. | Debian-oriented Makefile generates config/op XML templates and runs tests: XML source patterns and `BUILD_ARCH` in `Makefile:1-14`; `interface_definitions` pipeline in `Makefile:29-54`; `op_mode_definitions` in `Makefile:56-76`; `all` target in `Makefile:86-88`; `deb` target runs `dpkg-buildpackage -uc -us -tc -b` in `Makefile:129-130`. | No formal build system, shell scripts. `readme.md:95-99` says no build system and lists `d.sh`, `c.sh`, `r.sh`, `t.sh`; `src/c.sh:1-5` chains clean, Java compile, package, service/native scripts; `src/cj.sh:1-3` runs `javac @compiler.txt`. | Ze uses native Go commands. VyOS uses Make for Debian packaging and its generated CLI layer, while freeRtr is script-driven. | | Build system | Compile-time feature selection | Ze build tags differentiate distro and appliance builds. `internal/le/featuretags.DaemonTags` parses `feature-gates.txt`; `internal/le/gotoolchain` derives complete tag sets for native builds; `internal/test/runner.TestBuildTags` adds the functional-test personality. | not found in inspected sources for equivalent compile-time product variants. VyOS uses package/dependency selection and XML generation, not source-level build tags in inspected files. | not found in inspected sources for equivalent compile-time feature gates. | Ze is the only inspected tree with explicit compile-out tags in the main build. | | Build system | Generated registries or schema artifacts | Native generators own YANG glue, plugin imports, and derived feature tags: `internal/le/yangglue`, `internal/le/pluginimports`, and `internal/le/featuretags`. Their `check` and `write` actions share the same Go implementation. | VyOS transcludes XML and builds node templates and caches: `scripts/transclude-template` in `Makefile:18-25`; `build-command-templates` and `generate_cache.py` in `Makefile:34-40`; op-mode `build-command-op-templates` and `generate_op_cache.py` in `Makefile:58-64`. | not found in inspected sources. | Ze and VyOS both generate runtime command/schema artifacts, but with different stacks. | | Packaging | Native package output | not found in inspected sources for `.deb` or `.rpm` packaging. Ze local install copies binary to prefix instead, per `docs/guide/ze-install.md:3-28`. | Debian packaging present. `debian/control:1-36` declares source package and build dependencies; `debian/control:50-51` declares `Package: vyos-1x` and `Architecture: amd64 arm64`; `debian/vyos-1x.install:1-46` installs `/etc`, `/usr/bin`, `/usr/libexec/vyos`, `/usr/share`, etc.; `Makefile:129-130` has `deb`. | Full Debian package sources present: `misc/debian1/control:1-38` (Source `freerouter`, build-deps, two binary packages `freerouter`/`freerouter-native`), `misc/debian1/rules:1-14` (debhelper `dh ... --with=javahelper --with=systemd --with=sysuser`), plus `changelog`/`compat`/`*.install`/`*.postinst`/`*.service`/`*.sysuser`, mirrored in `misc/debian2/`. The in-tree shell build (`readme.md:54-60` output dirs `binDwn`/`binDsk`/`binImg`/`binOut`/`binTmp`) does not invoke them. | VyOS and freeRtr both carry complete Debian packaging; freeRtr's is Debian-maintainer packaging not wired into its own shell build. | | Packaging | Jar or archive packaging | not applicable, not found in inspected sources. | not applicable, not found in inspected sources. | `src/cp.sh:1-12` stages `META-INF/MANIFEST.MF`, normalizes timestamps with `touch -d "2010-01-01 00:00:00"`, and creates `rtr.jar` with `zip`. `userUpgrade.java:253-285` verifies the main archive and extra files. | freeRtr's Java distribution artifact is first class in inspected source. | | Image generation | Appliance disk image | `internal/appliance/cmd_build.go` and `cmd_assemble.go` build the gokrazy image, format `/perm`, and inject `database.zefs`; `gokrazy/` contains the instance configuration, builddir, module cache, and kernel files. | ISO image generation itself is delegated to `vyos-build`: `README.md:8-10` says use vyos-build to build an image, and CI calls `./build-vyos-image --architecture amd64 ... generic` in `.github/workflows/package-smoketest.yml:167-180`. | Demo VM image creator exists: `readme.md:89-90` says `misc/image` is a demo VM ISO creator and `misc/img2ova` is OVA creator. `misc/image/image.beg:1-40` selects Debian packages and excludes large subsystems; `misc/image/image.end:1-54` assembles kernel/initrd/rootfs artifacts. | Ze has source-local image generation. VyOS image generation is in a sibling repo, with this root contributing packages and tests. freeRtr has demo image recipes. | | Image generation | Installer ISO | `internal/appliance/cmd_iso.go` assembles the installer ISO from the kernel, initrd, appliance image, and boot assets; `docs/guide/ze-install.md:98-141` describes the bare-metal flow and disk image output. | This root exposes install/add image commands, not ISO builder internals. CI builds a custom ISO through `vyos-build` in `.github/workflows/package-smoketest.yml:167-180`. | `misc/image/ci.sh:1-3` runs `java -jar rtr.jar test image ... image.dsk` and `image.gns`; image finalization in `misc/image/image.end:1-54`. | Ze ISO builder is in-tree. VyOS ISO builder is external. freeRtr image recipes use router test image commands. | | Image generation | Installer initrd and kernel | `docs/guide/ze-install.md:122-142` documents installer kernel profiles. `internal/appliance/cmd_kernel.go` drives the native Docker or QEMU kernel builder, while `cmd_initrd.go` cross-builds `ze-installer` for linux/amd64 or linux/arm64 with the `ze_installer` tag. | Installer uses the live ISO squashfs and boot files: `src/op_mode/image_installer.py:143-145` defines `/boot/` and live `filesystem.squashfs`; install flow copies boot files and squashfs in `1029-1033`. | `misc/image/image.end:1-54` moves boot kernel to `%img%.krn`, creates cpio initrd, compresses with zstd, and wraps with `wraplinux`; `misc/image/image.beg:57-66` selects `linux-image-%kern%`, busybox, udev, kmod and libs. | Ze's installer is a Go initrd path. VyOS uses live ISO rootfs installation. freeRtr demo image creates custom initrd artifacts. | | Installer | Local install to host OS | `docs/guide/ze-install.md:3-28` says `ze install local` copies the running binary to `<prefix>/bin/ze`, creates config dir if needed, and supports `/usr/local`, `/usr`, `/opt/ze`. | `op-mode-definitions/system-image.xml.in:108-118` provides `install image` for installing VyOS to hard drive, backed by `image_installer.py --action install`. | `misc/service/c.sh:1-120` installs into a target directory, writes `/etc/init.d/rtr`, writes `/lib/systemd/system/rtr.service`, copies `rtr.jar`, `rtr.ver`, native binaries and `.so` files. | Ze local install is daemon-oriented. VyOS installer is appliance OS install. freeRtr service installer modifies the host. | | Installer | Disk partitioning and filesystem install | Ze docs describe the installer writing a disk image with a QEMU test verifying boot/SSH, `docs/guide/ze-install.md:98-220`. | `image_installer.py:232-250` creates partitions after disk cleanup; single-disk flow creates EFI and ext4 filesystems in `413-418`; install target mounting and config/squashfs copy in `999-1037`; GRUB install in `1054-1078`. | freeRtr partitions and formats a disk during image build: `misc/image/image.dsk:27-32` (`qemu-img create`, `sfdisk < native.sfdsk`, `mkfs.ext3`, extlinux via `guestfish`) with the partition table in `misc/image/native.sfdsk`; `misc/image/burn2usb.sh` even formats a physical device. What is absent is an interactive guided installer. `misc/service/c.sh:1-116` is a host service installer. | VyOS has the most detailed interactive disk installer in inspected source; Ze's flow is established by its docs and native appliance/QEMU actions. | | Installer | RAID install | not found in inspected sources. | RAID prompts and creation in `image_installer.py`: messages `96-100`, `check_raid_install` `422-490`, `raid_create`, `update_initramfs`, ext4 on RAID, and GRUB modules/install `1042-1074`. | not found in inspected sources. | VyOS only. | | Boot/update | Multi-image boot management | not found in inspected Ze sources for boot menu multi-image management. Ze has an update backend with a rollback method, but no boot-menu multi-image management. | `op-mode-definitions/system-image.xml.in:1-220` defines `add system image`, `set system image default-boot`, `delete system image`, `rename system image`, and `show system image`; `image_installer.py` sets GRUB default in `1054-1059`. | not found in inspected sources for boot menu multi-image management. | VyOS has first-class installed image management. | | Boot/update | Upgrade download, signature or hash verification | Ze self-update downloads (500 MB cap) and verifies a SHA256 hash: `internal/component/config/system/selfupdate.go` (`crypto/sha256`, `downloadSHA256`, download/verify/stage/restart) with `selfupdate_validate.go`; `backend.go:15-74` defines `ze-self-update` and `gokrazy-ab` backends with `Check`, `Download`, `Apply`, `Rollback`, `History`; command catalog `docs/architecture/api/commands.md:185-189` lists show/update firmware operations. | `image_installer.py:704-760` fetches local or remote ISO, supports `latest`, downloads minisign signature, calls `validate_signature` when available, and warns/prompts if absent; compatibility checks in `image_installer.py:886-929`. | `userUpgrade.java:253-285` verifies release info, archive, and file hashes; `userUpgrade.java:705-727` downloads to temp, checks hash before and after rename; signature verification in `userUpgrade.java:1010-1028`. | All three verify downloads: Ze by SHA256 hash, VyOS by minisign signature, freeRtr by release/file hash. | | Boot/update | Rollback or auto-revert | `UpdateBackend` includes `Rollback() (FirmwareResult, error)` in `internal/component/config/system/backend.go:62-74`; `NewBackend` switches to gokrazy A/B for gokrazy at `backend.go:96-107`. | VyOS auto-reverts to the previously-booted image on a failed upgrade boot: `src/init/vyos-router:641-664` reads `system option reboot-on-upgrade-failure`, and on failed config load re-sets the previous image as default boot (`image_manager.py --action set`) and reboots; the previous image is recorded by the installer at `image_installer.py:1253`, `:1278`. It can also keep older images and set default boot via `system-image.xml.in:84-101`. | `userUpgrade.java:340-359` implements `doRevert` by renaming `.bak` back and `doBackup`; auto-revert flow in `userUpgrade.java:362-437`; `userUpgrade.java:737-777` starts a reverter thread after boot and calls `doAutoRevert`; config knobs found in `cfgAll.java:1064-1080`, `userConfig.java:523-529`, `userConfig.java:3593-3617`. | freeRtr has the clearest explicit auto-revert in inspected code. Ze has a gokrazy A/B backend (`internal/component/config/system/backend_gokrazy.go`, `Check`/`Download`/`Apply`/`Restart`) alongside `ze-self-update`. | | Boot/update | Upgrade compatibility checks | Ze: not found in inspected sources beyond backend selection. | VyOS checks architecture and flavor: error constants in `image_installer.py:85-89`; current/new version read and compared in `image_installer.py:886-929`; current arch falls back to `dpkg --print-architecture` at line 894. | freeRtr release blob signature/hash verifies package contents, but CPU/platform compatibility checks were not found in inspected `userUpgrade.java`. | VyOS only for explicit arch/flavor checks. | | OS integration | systemd support | `docs/guide/ze-install.md:31-94` documents `ze install systemd` and the generated unit, including `ExecStart=<prefix>/bin/ze start`, restart policy, `ZE_CONFIG_DIR`, `XDG_RUNTIME_DIR`, capabilities, `RuntimeDirectory=ze`. Doctor checks validate systemd unit and skip gokrazy/container: in `internal/component/doctor/checks_platform.go:128-147`, `217-260`, `901-918`. | Debian dependencies include `systemd` in `debian/control:82-93`; `debian/vyos-1x.install:1-21` installs `etc/systemd`; many op-mode commands restart services with systemctl, e.g. `src/op_mode/restart.py:139-144`; container conf writes under `/run/systemd/system` at `src/conf_mode/container.py:58`. | Systemd units in `misc/debian1/freerouter.service:1-27` and `misc/debian2/freerouter-native@.service:1-29`; `misc/service/c.sh:61-95` writes `/lib/systemd/system/rtr.service`, reloads systemd, unmasks and enables `rtr`. | All three support systemd in some mode. Ze also explicitly supports non-systemd gokrazy. | | OS integration | Non-systemd appliance mode | The gokrazy appliance runtime is kernel, gokrazy init, and Ze, with no systemd, package manager, or general shell. `internal/appliance/cmd_build.go` and `cmd_assemble.go` build the image and populate `/perm`. | not found in inspected sources. VyOS uses Debian/systemd as a base in inspected package dependencies. | `misc/service/c.sh:21-59` also writes SysV init script `/etc/init.d/rtr`; however it still uses systemd later in `misc/service/c.sh:61-95`. Dedicated non-systemd appliance mode not found. | Ze is source-grounded as gokrazy appliance. freeRtr has SysV script compatibility, not a standalone no-systemd OS claim. | | OS integration | Runtime platform detection | `internal/component/host/inventory.go:320-391` defines `PlatformType` and `PlatformInfo` with gokrazy, systemd, container, plain-linux, darwin and capability fields; `ze-cli-show-cmd.yang:55-70` exposes `show system platform`. | UEFI detection helper in `python/vyos/utils/boot.py:37-39`; image installer reads serial console flavor and kernel cmdline in `150-158`, `622-660`; hardware/interface detection include `src/op_mode/interfaces.py:343-349` using `lspci`, `show_sensors.py:24-41`, `show_usb_serial.py:20-50`. | Platform descriptor files under `misc/image/platform.*`, e.g. `platform.mips32:1-8` defines arch, qemu, Debian arch, kernel, grub, boot, UEFI, image output. Runtime hardware detection script generated by `misc/service/c.sh:111-118` captures serial, interfaces and routes and runs `java -jar ... test hwdet`. | Ze has runtime platform taxonomy. VyOS has hardware/boot helpers. freeRtr has image platform descriptors and generated hwdet scripts. | | OS integration | Environment/runtime config | Ze's native toolchain puts Go and golangci-lint caches under the checkout (`internal/le/gotoolchain`). Runtime socket resolution is in `internal/component/config/environment.go`; appliance credential and passphrase environment keys are registered in `internal/appliance/cmd_init.go`. | VyOS uses environment variables for installer credentials passed to download scripts, `image_installer.py:692-703`; config defaults in `data/config.boot.default:43-52` include reboot-on-upgrade-failure and syslog defaults. | Docker entrypoint uses `ENV FREERTR_HOSTNAME` and `FREERTR_INTF_LIST` in `misc/docker/Dockerfile:32-35`; `misc/docker/scripts/freertr.sh:1-83` consumes those flags and starts Java. | All three have env/runtime config, but Ze's env registry is more typed. | | Kernel/sysctl | Kernel command line tuning | Ze runtime and installer kernel profiles are documented in `docs/guide/ze-install.md:122-142`; `internal/appliance/cmd_kernel.go` selects the target and profile, `internal/appliance/kernelbuilder` builds it, and `internal/le/qemu` owns runtime-kernel proofs. Specific sysctl defaults are not enumerated here. | VyOS maps config options to GRUB kernel cmdline: `image_installer.py` `532-576` include mitigations, power saving, panic, CPU isolate/nohz/rcu_nocbs/NMI watchdog; the runtime counterpart renders the same cmdline in `src/conf_mode/system_option.py:307-369`. | `misc/service/c.sh:10-15` writes sysctl `net.ipv6.conf.*.disable_ipv6=1` and `kernel.panic=10`, and edits `/etc/default/grub` to add `panic=10 nomodeset nofb` and lower timeout. | VyOS and freeRtr have direct kernel/sysctl tuning evidence. Ze has kernel build evidence and a runtime sysctl component (`internal/component/sysctl/`). | | Kernel/sysctl | Runtime sysctl management | Ze has a runtime sysctl component: `internal/component/sysctl/register.go` (registers the component with set/applied events and a CLI handler) and `internal/component/sysctl/backend_linux.go` (writes `/proc/sys`). | `src/conf_mode/system_sysctl.py:19-63` reads `system sysctl`, renders `/run/sysctl/99-vyos-sysctl.conf`, and applies `sysctl -f`; dependency graph has sysctl dependencies in `data/config-mode-dependencies/vyos-1x.json` `100-112`. | `misc/service/c.sh:10-12` writes sysctl files. No dynamic CLI sysctl manager found in inspected sources. | Ze and VyOS both manage runtime sysctls; freeRtr writes sysctl files at install time. | | Container support | Build or runtime container image | `docker/Dockerfile` builds the deployment image directly with Go, version/build-date arguments, and tags derived from `feature-gates.txt`. Host inventory exposes a `container` platform type. | VyOS package-smoketest runs inside privileged container with sysctl option, `.github/workflows/package-smoketest.yml:129-132`; runtime podman containers configured by `src/conf_mode/container.py:1-223`; package deps include many container-related tools elsewhere in `debian/control`, but podman dep line not in the read segment. | `misc/docker/Dockerfile:1-29` builds Debian-based freeRtr container, installs JRE, DPDK, OVS, downloads router artifacts, sets env and CMD; `misc/docker/scripts/freertr.sh:1-83` configures interfaces and starts router. | All three have container evidence. VyOS supports configured runtime containers; freeRtr and Ze have project container images/builds. | | Container support | Managed guest containers | not found in inspected Ze sources. | `src/conf_mode/container.py:58` uses `/run/systemd/system`; `container.py:52-90` discovers config and restart lists; `container.py:99-223` validates images, networks, CPU quota, devices, sysctls; warning about shared container storage across VyOS images in `container.py:131-140`. | not found in inspected sources. | VyOS only for router-managed containers. | | Provisioning | PXE or remote provisioning | `docs/guide/ze-install.md:144-193` documents remote install with DHCP/PXE, TFTP, the HTTP image server, iPXE chainloading, and `database.zefs`; `internal/plugins/provision/staging.go` owns the staged netboot artifacts. | not found in inspected vyos-1x sources. VyOS installer can fetch remote images by URL/VRF in `image_installer.py:692-760`, but no PXE provisioning server found. | not found in inspected sources. | Ze only in inspected sources. | | Provisioning | Init/bootstrap database or seed config | `internal/appliance/cmd_assemble.go` creates or reuses `database.zefs`, writes SSH/TLS credential keys, and appends environment listener overrides to the seed configuration. | VyOS install creates target config dir, copies config, configures auth and serial console, touches `.vyatta_config`, `1011-1022`; default boot config exists at `data/config.boot.default:43-52`. | `misc/docker/run/freertr-hw.txt` and `freertr-sw.txt`; Docker script starts with `run/<hostname>-hw.txt` and `run/<hostname>-sw.txt` in `misc/docker/scripts/freertr.sh:49-54`; service installer copies `default.cfg` to `rtr-sw.txt` and runs hwdet in `misc/service/c.sh:107-118`. | All three have bootstrap/config seed paths. | | Crash reporting | Crash reporting or crash inspection | Ze offline `show crashes` is described in `docs/architecture/api/commands.md:100-109`, the systemd unit sets `LimitCORE=infinity` in `docs/guide/ze-install.md:60-66`, and the implementation is in `internal/core/crashlog/` (`crashlog.go`, `persist.go`, `list.go`). | not found in inspected sources for a dedicated crash reporting feature. Tech-support report collection exists but not crash reporting. | not found in inspected sources for crash reporting. `userUpgradeRevert` catches and logs tracebacks in `userUpgrade.java:771-775`, but not crash reporting. | Ze has crash capture and inspection in `internal/core/crashlog/`. | | Testing harness | Unit and functional tests tied to build | Native Go actions cover unit, functional, fuzz, chaos, mutation reporting, release, and integration work under `internal/le/`; their command tables live in `testunit`, `functional`, `fuzz`, `testchaos`, `mutation`, `evidence`, and `integration`. | `Makefile:105-107` runs compileall and `nose2`; `debian/control:24-35` lists test dependencies; `smoketest/scripts/system/test_kernel_options.py`, `test_module_load.py`, and many `smoketest/scripts/cli/test_system*.py`. | `readme.md:89-112` lists `t.sh` selftest and examples `tw.sh`, `twd.sh`; `cfg/` contains self tests per `readme.md:36-40`; CodeQL workflow builds Java with `src/d.sh` and `src/cj.sh` per `.github/workflows/codeql.yml:59-66`. | All have tests, but Ze and VyOS have more formal build/test integration in inspected files. | | Testing harness | Image/QEMU tests | `internal/le/qemu` owns the host VM harness, full guest suite, installer HTTP/ISO/scenario/Ventoy proofs, and Linux network-namespace labs. `internal/appliance` owns image and kernel construction for amd64 and arm64. | CI builds ISO and passes to test jobs; `.github/workflows/package-smoketest.yml:167-193` builds and uploads ISO. README says runtime tests execute inside QEMU and smoketests are placed into `vyos-1x-smoketest`, `README.md:70-72`. | `misc/image/ci.sh:1-3` runs `rtr.jar test image` for dsk and gns image recipes; `readme.md:36-44` lists image/test output dirs; `platform.mips32:1-8` defines qemu target. | Ze has the richest QEMU matrix in inspected source. | | Cross-compilation and architecture | Target architectures | `internal/appliance/cmd_initrd.go` builds installers for linux/amd64 and linux/arm64; `cmd_kernel.go` accepts the same two kernel architectures; `internal/le/qemu` drives their virtual-machine proofs. | Debian package `Architecture: amd64 arm64` in `debian/control:38-43`; CI ISO build passes `--architecture amd64` in `.github/workflows/package-smoketest.yml:167-176`; installer compatibility compares current/new architecture in `image_installer.py:886-929`. | `misc/image/platform.mips32:1-8` shows mips32/mipsel descriptors; many `misc/image/platform.*` files; Docker pulls `rtr-$(uname -m).tgz` in `misc/docker/Dockerfile:15-18`; native dependencies are documented in `readme.md:28-35`. | Ze and VyOS explicitly support amd64/arm64 in inspected build/package files. freeRtr image descriptors show broader platform concept, with at least mips32 read. | | Distro integration | Debian base/package integration | not found in inspected sources for Debian packages, but Ze can install to standard Linux prefixes and systemd per `docs/guide/ze-install.md:3-94`. | Strong Debian integration: `debian/control:1-223` declares source/build/runtime deps; `Makefile:129-130` builds Debian package; install list in `debian/vyos-1x.install:1-21`; image installer uses `dpkg --print-architecture` fallback at `image_installer.py:892-895`. | Dockerfile and image recipes use Debian. `misc/docker/Dockerfile:1-7` starts `FROM debian` and installs Debian packages; `misc/image/image.beg:3-40` reads Debian catalogs and selects Debian packages; `readme.md:37` says up-to-date Debian sid and JDK are needed. | VyOS is the most Debian-integrated as a package. freeRtr uses Debian for images/containers. Ze is distro-neutral binary/systemd install. | | Appliance mode | Router appliance behavior | Ze's gokrazy appliance image is built by `internal/appliance/cmd_build.go` and `cmd_assemble.go`; the install guide describes TLS and SSH credentials plus seed configuration stored in the `/perm` ZeFS image, `docs/guide/ze-install.md:132-141`. | VyOS is a router OS image managed as live/installable images: `system-image.xml.in:1-220` and `image_installer.py:929-1080`; default config includes system option `reboot-on-upgrade-failure` in `data/config.boot.default:43-45`. | freeRtr service/image scripts aim at router appliance behavior: `misc/service/c.sh:10-95` disables network managers, sets default target, creates router service, masks services; `misc/image/image.end:37-54` creates initrd and default config. | All can be appliance-like, but Ze and VyOS have clearer end-to-end image/install flows in inspected source. | ### Gaps and unclear items | Item | Status | | --- | --- | | Ze self-update and gokrazy A/B implementation | Present. `selfupdate.go` (download + SHA256 verify), `backend_gokrazy.go` (A/B `Check`/`Download`/`Apply`/`Restart`), and `backend_ze_appliance.go`/`backend_ze_distro.go` in `internal/component/config/system/`. | | Ze sysctl implementation | Present. Runtime sysctl component at `internal/component/sysctl/` with `register.go`, `backend_linux.go`/`backend_darwin.go`, and `sysctl.go`. | | Ze crash reporting implementation | Present. Crash capture, persistence and listing in `internal/core/crashlog/` (`crashlog.go`, `persist.go`, `list.go`, `stderr.go`). | | VyOS image generation internals | Not in this root. CI and README point to `vyos-build`, and this root builds `vyos-1x` packages plus smoketest package. Image builder internals live in `vyos-build`, outside this root. | | VyOS upgrade rollback | Present. Automatic revert to the previous image on a failed upgrade boot at `src/init/vyos-router:641-664` (driven by `system option reboot-on-upgrade-failure`). | | VyOS dedicated crash reporting | Not found in inspected sources. | | freeRtr package build metadata | Full Debian package sources present at `misc/debian1/{control,rules,changelog,*.install}` and `misc/debian2/`, though not wired into the in-tree shell build. | | freeRtr installer partitioning | Image-build disk partitioning + filesystem + bootloader exist (`misc/image/image.dsk:27-32`, `native.sfdsk`, `burn2usb.sh`); no interactive target-disk installer. | | freeRtr systemd versus init | Both SysV init and systemd setup are in `misc/service/c.sh`, plus Debian systemd units. No dedicated no-systemd appliance OS was found. | | freeRtr crash reporting | Not found in inspected sources. | | freeRtr runtime container orchestration | Docker image exists, but router-managed guest/container support was not found. | ## Evidence appendix: observability, diagnostics, and testing ### Feature inventory | Area | Feature | Ze evidence | VyOS evidence | freeRtr evidence | Notes | | --- | --- | --- | --- | --- | --- | | Metrics | Native metrics registry and Prometheus endpoint | Prometheus metrics exposed by `telemetry { prometheus { ... } }`, default `127.0.0.1:9273`, path `/metrics`, BGP refreshed every 10s, docs `docs/guide/monitoring.md:117-163`; HTTP exporter handles configured path via `registry.Handler()` and health endpoint via `health.DefaultRegistry.Handler()`, `internal/component/telemetry/exporter/server.go:87-106`. | Prometheus integration is via external exporters: `node_exporter`, `frr_exporter`, `blackbox_exporter`, built from config paths and rendered as systemd services, `src/conf_mode/service_monitoring_prometheus.py:47-82`, `:119-151`, `:153-199`. | Built-in Prometheus server default port 9001, configured metric sensors and `all-metrics`, `src/org/freertr/serv/servPrometheus.java:35-71`, `:98-127`; global Prometheus daemon list in `cfgAll`, `src/org/freertr/cfg/cfgAll.java:647-650`. | Ze and freeRtr have in-process exporters. VyOS delegates to common exporter daemons. | | Metrics | OS metrics | Netdata-compatible OS collector metrics, 138 metrics, `/proc` and `/sys` sources, `docs/guide/monitoring.md:183-220`; exporter config distinguishes Netdata-compatible OS metrics from Ze-native `ze_*`, `internal/component/telemetry/exporter/server.go:45-63`. | node_exporter configured by `service monitoring prometheus node-exporter`, service render/restart in `src/conf_mode/service_monitoring_prometheus.py:53-61`, `:119-139`, `:189-195`. | Sensor framework can execute commands and define metric columns, local memory/file collection, `src/org/freertr/cfg/cfgSensor.java:155-180`, `:216-263`; shipped `misc/sensor/` and `misc/prometheus/` directories. | Ze docs explicitly call out Netdata compatibility. freeRtr is highly generic sensor-driven. | | Telemetry | Streaming telemetry | not found in inspected sources for a full external streaming telemetry protocol beyond Prometheus and event streams. | Telegraf config exports to InfluxDB, Prometheus client, Azure Data Explorer, Loki, Splunk and renders telegraf plus rsyslog integration, `src/conf_mode/service_monitoring_telegraf.py:70-120`, `:122-183`, `:184-223`. | Streaming telemetry destinations export sensors to target/port with intervals, delays, randomization, time ranges and proxy, `src/org/freertr/cfg/cfgTlmtry.java:60-108`, `:120-207`; client sends sensor reports and warns on empty reports, `src/org/freertr/clnt/clntTelemetry.java:155-163`. | VyOS and freeRtr have stronger source evidence for push telemetry than Ze in inspected source. | | Dashboards | Live dashboard or dashboard config | Live BGP peer dashboard via `ze cli -c "monitor bgp"`, auto-refreshing peer table, `docs/guide/monitoring.md:5-14`; web workbench dashboard tests and pages found under `internal/component/web/workbench_dashboard_test.go`, `page_logs.go`. | not found in inspected sources for a native dashboard framework. | Configurable dashboard object with command and sensor cells, `src/org/freertr/cfg/cfgDshbrd.java:18-26`, `:81-95`, `:283-331`; readme lists `misc/trackmap` as web based monitoring, `readme.md:75-86`. | Ze has an operator CLI dashboard. freeRtr has configurable dashboards. | | Health checks | HTTP health endpoint | Prometheus HTTP service registers `/health` unless metrics path is `/health`, `internal/component/telemetry/exporter/server.go:98-106`; docs state Basic Auth protects metrics and health endpoints, `docs/guide/monitoring.md:164-181`. | Health checks are feature-specific: container health command/interval/timeout/retry, `src/conf_mode/container.py:482-498`; VRRP health check validation for script/ping, `src/conf_mode/high-availability.py:177-197`; WAN load-balancing interface health tests, `src/conf_mode/load-balancing_wan.py:67-82`. | not found in inspected sources as a generic HTTP `/health` endpoint. Watchdog show commands exist, `src/org/freertr/user/userShow.java:659-682`. | Ze has a generic service health endpoint. VyOS has config health checks. freeRtr has watchdog/process diagnostics. | | Doctor and diagnostics | Doctor framework | Registry supports phases `pre-config`, `missing-config`, `post-config`, platform filtering, dependencies, codes, validation and duplicate checking, `internal/core/diagnostic/doctor_registry.go:19-57`, `:59-87`, `:101-160`; appliance doctor checks registered for kernel/initrd/grub/xorriso/e2fsprogs, `internal/appliance/doctor_checks.go:15-66`. | No generic doctor framework found in inspected sources. Operational tech-support collection exists, see support bundle row. | No generic doctor framework found in inspected sources. Check definitions are mentioned in readme, `misc/consistency` and `misc/check`, `readme.md:83-87`, but no doctor-like runtime framework inspected. | Ze is strongest for first-class doctor checks. | | Diagnostics | Symptom-based operational diagnostics | Production diagnostics guide maps symptoms to built-in commands: TCP check, traceroute, kernel log, CPU profile, heap profile, FDs, goroutines, DNS, ping, netlink, capture, `docs/guide/production-diagnostics.md:1-24`; detailed BGP, CPU, memory, FD and goroutine workflows, `:26-136`; platform limitations for `/proc` commands, `:238-243`. | Tech-support report gathers version, uptime, load, CPU, process stats, storage, devices, memory, config, routes, protocols, neighbor tables, nftables, connections and logs, `src/op_mode/tech_support.py:293-401`; report runner executes command sections and writes stdout or files, `src/op_mode/show_techsupport_report.py:75-121`, `:142-205`. | `show platform` prints uptime, pid, config files, class, CPU and memory, `src/org/freertr/cfg/cfgInit.java:415-430`; `show process cpu`, `show watchdog gc/thread/iface/memory`, `show logging process`, and `show sensor` handlers appear in `src/org/freertr/user/userShow.java:536-543`, `:659-682`, `:691-717`, `:936`. | Ze diagnostics are command-focused and appliance-aware. VyOS tech support is broad collection. freeRtr has extensive show diagnostics. | | Logs | Structured logging | Uses `log/slog`, per-subsystem env levels, backends stderr/stdout/syslog/kmsg, destination and relay settings, `internal/core/slogutil/slogutil.go:1-24`, env registrations `:37-46`; subsystem descriptions include BGP, plugin, chaos, web, test, `:56-109`. | Logging wrapper supports syslog, stderr and rotating files, `python/vyos/logger.py:1-24`, `:37-98`; operational log command uses journalctl with boot, count, unit, since, UTC, reverse, raw JSON filters, `src/op_mode/log.py:22-49`, `:52-66`. | Logger supports levels, file, terminal, syslog, IRC, buffer, position format, stack trace dumps, `src/org/freertr/util/logger.java:26-110`, `:112-159`, and default logging config includes buffered, monitor, format, milliseconds, `src/org/freertr/cfg/cfgAll.java:1490-1495`. | All three have mature logging, but with different models. | | Debug commands | Runtime debug switches | Debug command test files found for Ze under `test/ui/debug-help.ci`, `debug-invalid-subsystem.ci`, `debug-enable-show.ci`; debug plugin source `internal/plugins/debug/debug.go`. Production diagnostics includes raw BGP capture commands, `docs/guide/production-diagnostics.md:42-69`. | Debug flags enabled by environment or files under `/tmp` and `/config`, registered flags `developer`, `log`, `ifconfig`, `command`, `python/vyos/debug.py:21-49`, `:52-84`, `:101-135`; smoketest shim has `/tmp/vyos.smoketest.debug` and prints set/del/commit/op outputs, `smoketest/scripts/cli/base_vyostest_shim.py:43-50`, `:70-111`. | Debugger has hundreds of static flags by subsystem, including user commands and protocol/service traffic, `src/org/freertr/util/debugger.java:12-80`, `:121-180`; Prometheus traffic debug logs rx/tx, `src/org/freertr/serv/servPrometheus.java:165-166`, `:215-216`. | freeRtr exposes the broadest explicit debug flag surface in inspected code. | | Crash and core support | Panic/core/crash capture | Crashlog captures stderr including Go panics, writes syslog and crash file, `internal/core/crashlog/crashlog.go:1-17`, `:38-59`; reports include time, version, build, commit, Go version, OS/arch, PID, goroutine count, uptime, command, stack trace and recent log, `:102-199`. | Tech-support archive includes `/var/core` as `core-dump`, and `/var/log`, `/run`, config, etc., `src/op_mode/generate_tech-support_archive.py:83-111`; debug developer flag says unhandled exceptions can drop into PDB, `python/vyos/debug.py:52-66`. | Uncaught JVM exception handler is installed, `src/org/freertr/cfg/cfgInit.java:604-608`; logger can dump stack traces, `src/org/freertr/util/logger.java:112-159`. No support bundle style core archive found. | Ze has first-class crash report files. VyOS archives core dumps. freeRtr catches/logs JVM exceptions. | | MRT/BMP/log export | MRT analysis and BMP export | `ze-analyze export bmp` sends MRT BGP4MP records as BMP v3 route monitoring to a collector, `internal/analyze/export_bmp.go:16-39`, parses target and peer filters `:42-76`, reads MRT and writes BMP frames `:90-116`; `ze-analyze convert pcap/json`, filter, inject, dump found in `internal/analyze/convert.go:17-30`, `:49-68`, `:81-109`. | BMP config validation in BGP checks that `bgpd` runs with `bmp` module and target address exists, `src/conf_mode/protocols_bgp.py:217-228`; NetFlow, sFlow and VPP IPFIX are present, `src/conf_mode/system_flow-accounting.py:75-100`, `src/conf_mode/system_sflow.py:41-84`, `src/conf_mode/vpp_ipfix.py:32-41`, `:81-102`, `:171-174`. MRT tooling not found in inspected sources. | BMP-to-MRT service documented in class comment as RFC 7854 BMP to RFC 6396 MRT toolkit, `src/org/freertr/serv/servBmp2mrt.java:24-31`; supports file output/rotation, listeners for BMP/RIS/BGP, relays and rate controls, `:86-144`, `:166-223`; config tests `cfg/serv-bmp01.tst`, `cfg/serv-bmp02.tst`. | Ze has offline analysis and BMP export. freeRtr has an online BMP-to-MRT service. VyOS relies on FRR BMP plus flow exporters. | | Packet capture and traffic diagnostics | Packet capture | Built-in capture commands in diagnostics include raw BGP capture, interface packet capture in text or pcap format, `docs/guide/production-diagnostics.md:42-69`; MRT convert to pcap, `internal/analyze/convert.go:17-30`, `:49-68`. | Interface dump completion reads tcpdump interfaces via `tcpdump -D`, `src/completion/list_dumpable_interfaces.py:2-11`; bandwidth test uses `iperf`, `src/op_mode/execute_bandwidth_test.sh:16-32`. | Tester supports `capture` option and emits `packet capture <ifc> ... .pcap`, `src/org/freertr/user/userTester.java:906-914`, `:2422-2429`, and pcap test command, `:2480-2482`; readme lists `misc/captures` and `misc/sniffer`, `readme.md:67-70`. | All have capture evidence, with Ze most integrated into diagnostic CLI docs. | | Testing | Unit tests | Go unit tests run in five race-instrumented groups declared by `internal/le/testunit/groups.go` and dispatched by `internal/le/testunit/actions.go`; `docs/functional-tests.md:202-222` describes the groups. | Makefile `test` compiles all Python and runs `nose2`, `Makefile:104-107`; `src/tests/test_*.py` files. | Source readme states `cfg` contains self tests and `t.sh` selftest, `readme.md:50-60`, `:98-99`; Java `userTester` is process image tester, `src/org/freertr/user/userTester.java:18-30`. | Ze has the clearest source-grounded native unit action structure. | | Testing | Functional and smoke tests | `internal/le/functional/suites.go` is the native functional-suite catalog; `actions.go` derives one action per suite and the bare command runs every gating suite. The docs enumerate gating and non-gating suites in `docs/functional-tests.md`. | Runtime tests are QEMU-based smoketests in `smoketest`, README says QEMU CI and `vyos-1x-smoketest` package, `README.md:70-72`; smoketest shim snapshots/restores config and checks FRR mgmtd continuity, `smoketest/scripts/cli/base_vyostest_shim.py:31-41`, `:52-68`; workflow builds ISO and runs CLI smoketests, `.github/workflows/package-smoketest.yml:166-183`, `:192-218`. | `userTester` supports parallel workers, retries, result tracking, summaries, HTML/CSV/FTR, `src/org/freertr/user/userTester.java:96-103`, `:170-180`, `:1098-1146`, `:1167-1185`; readme lists `binTmp output of testing`, `readme.md:55-60`. | Ze and VyOS both have explicit release/smoke gates. freeRtr has a custom test harness. | | Testing | Topology and interop tests | Native Docker interop against FRR, BIRD, GoBGP, ExaBGP, and strongSwan is implemented by `internal/le/interoplab/bgp`, `internal/le/interoplab/ipsec`, and the action table in `internal/le/integration/gates.go`; 150+ scenarios cover BGP, IS-IS, and OSPF. | GitHub workflow builds a custom ISO and runs smoketests in containers with privileged options, `.github/workflows/package-smoketest.yml:150-183`, `:192-218`; VPP smoketest and config-load jobs, `:225-252`, `:325-352`. Topology/interop beyond QEMU/VPP smoketest not found in inspected `vyos-1x` source. | Readme says `img` VM images are used for interop and dataplane testing, `readme.md:55-60`; tester can launch multiple routers with generated ports and captures, `src/org/freertr/user/userTester.java:36-48`, `:1437-1440`, `:2422-2429`; many `src/t*.sh` selftest/topology scripts and `cfg/*.tst`. | Ze has most explicit source docs for third-party interop. | | Performance | Benchmarks and performance tools | `ze-perf` implements sender/DUT/receiver benchmarking, repeats and warmups, outlier handling, JSON/HTML/Markdown reports, and regression checks (`internal/perf/cli`, `internal/perf/report`). `cmd/ze-perf-run` and `internal/test/perfrunner` own the multi-DUT Docker run. | Bandwidth test wraps `iperf`, choosing IPv6 option based on address/FQDN, `src/op_mode/execute_bandwidth_test.sh:16-32`; no benchmark harness found in inspected sources. | Native dataplane benchmark script runs `p4bench.bin` for IPv4, IPv6, VLAN, PPPoE, MPLS, `misc/native/p4emu_bench.sh:1-13`; readme lists `misc/tests` as volumetric generators and native dataplanes, `readme.md:64-85`. | Ze has most complete benchmark and regression tooling. | | CI helpers | Verify, status, release evidence | `./le verify current mode full` writes per-stage logs, a failure index, and a status fingerprint (`internal/le/verifyengine`). `./le evidence release-candidate` runs the same gate over a clean checkout in Docker (`internal/le/evidence`). | Make `all` includes pylint, tests, j2lint, XML definitions, OCaml, `Makefile:86-87`; GitHub workflow builds ISO, uploads artifact, runs smoketests and comments, `.github/workflows/package-smoketest.yml:150-183`, `:192-223`, `:225-255`. | Shell scripts `c.sh`, `r.sh`, `t.sh` for compile/run/selftest, `readme.md:95-99`; tester writes summaries and result files, `userTester.java:1138-1185`. | Ze and VyOS have more explicit CI helper evidence in inspected source. | | Lint and static gates | Lint gates | `.golangci.yml` enables diagnostic, style, and performance checks and forbids the legacy log package. `internal/le/verifylint` runs `./le verify lint run` across the shipped feature matrix with native CPU and memory limits; `.woodpecker/verify.yml` installs golangci-lint and agent-browser. | Makefile `pylint` and `j2lint`, `Makefile:115-127`; GitHub reusable darker/ruff lint workflow, `.github/workflows/darker-ruff-lint.yml:1-13`. | freeRtr runs CodeQL static-analysis code-scanning on push/PR and weekly cron for C/Java/Python: `.github/workflows/codeql.yml:20-45`, `:69-72`. No style linter was found; the readme notes shell-script builds only, `readme.md:95-99`. | VyOS and Ze have visible lint/static gates. | | Mutation testing | Mutation | gomu runs as a native Go command for incremental, package, JSON, and HTML mutation reports; `.gomuignore` excludes build-tagged and platform paths. `internal/le/mutation` combines JSON reports and records package history. | not found in inspected sources. | not found in inspected sources. | Ze only. | | Support bundles | Support bundle or archive | No support bundle found in inspected runtime/diagnostic sources. Appliance export archives are encrypted appliance config archives, not a diagnostic support bundle, `internal/appliance/cmd_export.go:27-28`, `:107-131`. | `generate tech-support archive` saves tech-support reports, archives `/etc`, `/home`, `/var/log`, `/root`, `/tmp`, `/var/core`, config and `/run`, syncs/flushes journal, rotates old tmp archives, `src/op_mode/generate_tech-support_archive.py:36-46`, `:48-111`, `:201-223`; can upload to `ftp://` or `scp://`, `:208-211`, `:244-251`. | not found in inspected sources. | VyOS is strongest for support bundles. | ### Gaps and unclear items - Ze support bundle: not found in inspected sources. There are crash reports and appliance export/import archives, but no source-grounded support-bundle collector analogous to VyOS tech-support archive. - Ze push telemetry beyond Prometheus/event streams: not found in inspected sources. The `ze_telemetry` build tag and Prometheus exporter are clear, but a streaming telemetry destination protocol was not found. - VyOS generic doctor framework: not found in inspected sources. It has tech-support reports, debug flags, op-mode commands, config validation, and service health checks, but no registry-style doctor checks found. - VyOS benchmark framework: not found in inspected sources beyond `iperf` operational bandwidth test and VPP/smoketest jobs. - VyOS MRT tooling: not found in inspected sources. BMP validation exists for FRR and flow exporters exist, but no MRT parser/export/converter was found in `vyos-1x` inspected paths. - freeRtr support bundle: not found in inspected sources. Logging, show diagnostics, sensors, and tester artifacts exist, but no support archive collector was found. - freeRtr lint and mutation gates: CodeQL static analysis runs in CI (`.github/workflows/codeql.yml:20-45`); no style linter or mutation-testing gate was found. - freeRtr generic HTTP health endpoint: not found in inspected sources. Process/watchdog diagnostics and show commands are present. - freeRtr test inventory size: 27 `src/t*.sh` selftest/topology scripts and 4426 `cfg/*.tst` config tests, plus generated `rtr*.csv/html/ftr` files. - VyOS topology/interop scope: QEMU ISO smoketests and VPP jobs are visible in `vyos-1x`, but broader topology/interop harnesses may live in `vyos-build`, not in this source root. ## Evidence appendix: architecture, extensibility, and implementation model ### Feature inventory | Area | Feature | Ze evidence | VyOS evidence | freeRtr evidence | Notes | | --- | --- | --- | --- | --- | --- | | Implementation language and runtime | Primary implementation language/runtime | Go module declares `module github.com/ze-software/ze` and `go 1.26` in `go.mod:1-3`. | Python is first-class: `debian/control:11` requires `python3 (>= 3.10)`, and conf mode scripts are Python, for example `src/conf_mode/protocols_bgp.py:1` shebang. OCaml helper package exists in `src/ocaml/vyos-1x.opam:1-18`, and C/lib build dependencies are listed in `debian/control:7-15`. | Java entry point: `src/org/freertr/router.java:1-19`, plus source package tree under `src/org/freertr`. Build scripts call Java/JAR paths, for example `misc/docker/scripts/freertr.sh:51` runs `java -jar ... rtr.jar`. | [analysis] Ze is a compiled Go daemon with internal Go packages and external Go SDK. VyOS is a Debian/Linux integration layer built around Python scripts, XML schemas, C/OCaml helpers, and system packages. freeRtr is an in-process Java router with optional native-image packaging. | | Dependency model | Language and system dependencies | `go.mod:5-28` lists direct Go dependencies including Bubble Tea, goyang, nftables, DHCP, DNS, Prometheus, netlink, govpp, WireGuard control, gRPC/protobuf. | `debian/control:5-38` lists build deps, including gcc, libzmq3-dev, Python libs, protobuf compiler, lxml, xmltodict. Runtime depends on system packages and Python libraries in `debian/control:45-87`. | No non-JDK, non-`org.freertr` package imports exist under `src/org/freertr`; only `java.*`/`javax.*` JDK imports appear. Build scan found `javac`/`java -jar` scripts, not Maven/Gradle manifests, for example `misc/applet/c.sh:3` and `misc/docker/scripts/freertr.sh:51`. | [analysis] Ze has explicit module-managed dependencies. VyOS depends heavily on distribution packages and external daemons/libraries. freeRtr appears self-contained in inspected Java sources, with fewer package-manager dependency boundaries. | | Modularity | Top-level modularity shape | Components and plugins are split under `internal/component/` and `internal/plugins/`, discovered by registries and generated imports. Generated import root states it imports plugins and schema packages to trigger `init()` registration in `internal/component/plugin/all/all.go:1-8`. | Each feature is typically an XML definition plus a conf mode script. `interface-definitions/protocols_bgp.xml.in:3` binds `protocols bgp` to `${vyos_conf_scripts_dir}/protocols_bgp.py`, with priority at `:6`. | Static global registries in `cfgAll`: routers at `src/org/freertr/cfg/cfgAll.java:229-230`, interfaces and policy objects around `:224-361`, service daemon lists around `:379-500`. | [analysis] Ze modularity is registry and generated composition-root based. VyOS modularity is command-owner/script based. freeRtr modularity is class/package based but centrally anchored in global registries. | | Plugin/component model | Internal plugins and external plugins | `PluginConfig` includes `Run`, `Encoder`, `Respawn`, `WorkDir`, `ReceiveUpdate`, `StageTimeout`, and `Internal` fields in `internal/component/plugin/types.go:187-203`. Plugin YANG has `plugin internal` and `plugin external` lists in `internal/component/plugin/yang/ze-plugin-conf.yang:62-115`. | No general plugin framework found in inspected sources. Extensibility is via XML definitions with owner scripts, generated command templates, and Python module loading. Evidence: XML `owner` in `protocols_bgp.xml.in:3`; configdep loads target scripts dynamically in `python/vyos/configdep.py:98-116`. | No out-of-process plugin model found in inspected sources. Extensibility is adding Java classes and wiring into static registries. Evidence: `servGenList.srvFind` dispatches via `servGenEntry.getDaemon(typ)` in `serv/servGenList.java:171-186`, and `cfgRtr` has one field per protocol handler in `cfg/cfgRtr.java:78-166`. | [analysis] Ze has the richest plugin boundary, including external processes. VyOS supports feature extension at package/script/schema level, but not a generic third-party plugin API in inspected sources. freeRtr extension likely requires source changes and recompilation. | | Registration patterns | Startup and command registration | Ze plugin command registry validates names, blocks built-in shadowing, and records owning process in `internal/component/plugin/server/command_registry.go:147-208`. Static plugin registers by `init()` with `registry.Registration` in `internal/plugins/static/register.go:37-68`. | XML owner and priority drive CLI generation, as shown by `interface-definitions/protocols_bgp.xml.in:3-6`. Runtime dependencies call script lifecycle functions dynamically in `python/vyos/configdep.py:98-116`. | Service registry uses `find(create)` to add entries and call `srvInitialize()` in `serv/servGenList.java:31-57`, and `del()` calls `srvDeinit()` in `:59-72`. Global service lists are hard-coded fields in `cfgAll.java:379-500`. | [analysis] Ze and freeRtr both use explicit registration, but Ze has declarative metadata and process ownership while freeRtr uses generic typed lists plus central switch/entry maps. VyOS registration is mostly data-driven by XML and script filenames. | | Generated composition roots | Generated imports or command lists | `internal/component/plugin/all/all.go:1-8` is generated by `internal/le/plugin/imports/pluginimports.go` and imports all internal plugins/schema packages. `./le plugin imports write` regenerates the composition root, and `./le plugin imports check` rejects drift. | `Makefile:29-40` generates XML templates and reftree cache. VyOS's `generate-configd-include-json.py` (lines 1-31) generates `data/configd-include.json` from `src/conf_mode`. VyOS's `generate-activation-scripts-json.py` (lines 17-35) generates activation lists. | No generated Java composition root found in inspected sources. Build scripts are shell-based. `rel-jar.sh:1-6` calls `rel-zip.sh`, `src/c.sh`, `src/cb.sh`, and native helper builds. | [analysis] Ze and VyOS rely on generated composition/index artifacts to keep large feature sets wired. freeRtr appears to rely more on manually maintained Java registries. | | Schema generation and validation | Schema source and validation model | YANG validator uses `github.com/openconfig/goyang/pkg/yang` in `internal/component/config/yang/validator.go:7-13`, maps config prefixes to modules in `:153-180`, and validates YANG types in `:205-223`. | XML definitions are transcluded and converted to command templates by `Makefile:24-40`, then caches are generated by `python/vyos/xml_ref/generate_cache.py` in `Makefile:40`. The README confirms node.def files are generated from unified XML and schemas at `README.md:35-44`. | freeRtr generates YANG from its embedded CLI/help tree: `userHelp.getYang` (`user/userHelp.java:1249`), `userNetconf.makeYang` (`user/userNetconf.java:104`), exposed as CLI `yangconfig`/`yangsensor` (`user/userExec.java:3072-3075`); the help and defaults themselves are Java-embedded, for example `cfgRtr.defaultF` at `cfg/cfgRtr.java:211-228` and `servGenList.srvHelp` at `serv/servGenList.java:188-282`. | [analysis] Ze and VyOS have machine-checkable schema layers. freeRtr encodes schema/help in Java code, which keeps code and config close and generates external YANG on demand from that embedded schema. | | Code generation | protobuf and generated code | Ze has API protobuf schema in `api/proto/ze.proto:1-40` defining `ZeService` and `ZeConfigService`; generated plugin aggregator in `internal/component/plugin/all/all.go:1-8`. `go.mod:26-28` includes grpc/protobuf. | VyOS uses XML to templates/cache generation and configd/activation JSON generation: `Makefile:24-40`, `Makefile:56-63`, VyOS's `generate-configd-include-json.py` (lines 1-31), VyOS's `generate-activation-scripts-json.py` (lines 17-35). `debian/control:30-31` lists the protobuf compiler for serialization functions; VyOS ships a protobuf-over-Unix-socket IPC client (`python/vyos/proto/vyconf_client.py:16-86`, generated `python/vyos/proto/vyconf_pb2.py`), not gRPC. | Native/JAR release scripts found, but no code generation framework found in inspected Java sources. `rel-jar.sh:1-6`; `src/cb.sh:1-5` uses `native-image`. | [analysis] Generated artifacts are central to Ze and VyOS maintainability. freeRtr maintainability leans on uniform Java coding conventions and central registries instead of generation, based on inspected files. | | External SDKs and API boundaries | SDK/API offered to extensions | Ze SDK package documents plugin communication over YANG RPC, net.Pipe for internal plugins, TLS for external, MuxConn, and five-stage startup in `pkg/plugin/sdk/sdk.go:1-30`. Callback registry is in `sdk.go:60-97`. | `python/vyos/config.py:17-20` states Config is used internally by all config scripts and its API should be stable and safe for user scripts, but also notes it will not work outside VyOS at `:21`. | No external SDK found in inspected sources. Java APIs are internal classes. `router.java:15-18` only delegates to `cfgInit.doMain(args)`. | [analysis] Ze explicitly exposes a plugin SDK boundary. VyOS exposes Python scripting APIs within a VyOS runtime. freeRtr exposes CLI/config and source-level Java classes, not a separate SDK in inspected sources. | | API boundaries | CLI, RPC, web, config | Ze hub startup resolves web, looking glass, MCP, config file, env, and CLI precedence in `cmd/ze/hub/main.go:227-314`; plugin server is created and registered as dispatcher/event bus in `:422-454`. Protobuf API includes Execute, Stream, ListCommands, DescribeCommand, Complete in `api/proto/ze.proto:7-20`. | VyOS API boundary is command scripts and process execution. Conf mode script `protocols_bgp.py` uses `get_config`, `verify`, `generate`, `apply`, then main calls all four in `src/conf_mode/protocols_bgp.py:596-611`. Process boundary uses `subprocess.Popen` wrapper in `python/vyos/utils/process.py:35-104`. | freeRtr API boundary is mostly in-process CLI/config classes and services. Main entry `router.java:15-18` calls `cfgInit.doMain(args)`. `servGeneric` implements common service config and protocol flags in `serv/servGeneric.java:44-154`. | [analysis] Ze centralizes API fan-in through hub/plugin server. VyOS distributes API behavior across per-feature scripts and system commands. freeRtr keeps APIs in a monolithic JVM object model. | | Process model | Daemon and child process model | Ze `cmdStart` starts daemon or managed client, then calls `hub.Run` or `hub.RunWithManagedClient` in `cmd/ze/ze_core_start.go:176-221` and `:248-291`. ProcessManager starts plugin processes with context and stores them in a map in `manager.go:125-173`. | VyOS conf scripts are executable Python scripts with lifecycle functions and `if __name__ == '__main__'` path in `protocols_bgp.py:604-611`. System services are managed via systemd commands, for example `container.py` systemctl calls in `src/conf_mode/container.py:647-684`, HA reload at `high-availability.py:213-234`. | freeRtr runs in a JVM with an infinite management loop. `cfgInit` implements `Runnable` at `cfg/cfgInit.java:61`, starts a thread via `logger.startThread(this)` in `:1688-1696`, and `mainLoop()` runs forever in `:1698-1724`. | [analysis] Ze owns its Go process and plugin subprocess lifecycle. VyOS delegates most long-running services to systemd or external daemons. freeRtr is a single long-lived in-process router with Java threads. | | Daemon supervision | Respawn and supervision | Ze plugin manager enforces per-window respawn limit 5/60s and cumulative max 20 in `manager.go` (`RespawnLimit`, `RespawnWindow`, `MaxTotalRespawns`); `Respawn` disables processes and reports warnings past those bounds. | VyOS uses systemd for supervised services. Examples: container units rendered under `/run/systemd/system` and `systemctl daemon-reload/restart` in `container.py:633-684`; HA keepalived systemd override and reload/restart in `high-availability.py:40-234`. | freeRtr has its own periodic checks and exception logging in `cfgInit.mainLoop()` at `cfgInit.java:1698-1724`, but no systemd-like respawn mechanism found in inspected Java sources. Some config objects have start/stop methods, for example `cfgXconn.start2run()` at `cfgXconn.java:187-206`. | [analysis] Ze and freeRtr supervise from inside the application for their internal components. VyOS relies on the OS service manager for many dataplane/control daemons. | | Protocol implementation model | Own implementations vs external reuse | Ze implements protocol engines/plugins in Go and also imports protocol-related libs for integration. Evidence: BGP plugin registration and SDK engine in `internal/component/bgp/plugins/redistribute_egress/register.go:23-47`; dependencies include `github.com/miekg/dns`, DHCP, netlink, govpp, WireGuard control in `go.mod:12-26`. | VyOS often renders external daemon configs and applies them. BGP conf mode uses `FRRender` in `protocols_bgp.py:26-27` and `generate/apply` call `FRRender().generate/apply()` at `:596-601`; system services are restarted via systemd as shown above. | freeRtr has many in-tree protocol classes by package names and fields, for example `cfgRtr` fields `rtrOspf4`, `rtrOspf6`, `rtrIsis`, `rtrBgp`, `rtrRpki` in `cfg/cfgRtr.java:108-166`; no external Java imports found by dependency scan. | [analysis] Ze is mixed: own control-plane code plus Go libraries for OS/protocol helpers. VyOS is primarily orchestration over Linux and daemons such as FRR, nft, strongSwan, VPP, etc. freeRtr is closest to an own-stack protocol implementation in inspected sources. | | Zero-copy and performance patterns | Avoiding allocations and copies | Ze has explicit zero-allocation text formatting: `textbuf.Buffer` with inline `[128]byte`, `sync.Pool`, unsafe noescape, zero-copy `Slice`, and stack-residence comments in `internal/core/textbuf/textbuf.go:54-93` and pool in `:108-125`. Command registry freezes immutable snapshots for lock-free lookup in `command_registry.go:19-26` and `:131-145`. | Performance patterns found are mostly system-level and command execution, not zero-copy. `popen` supports buffered or live line output in `python/vyos/utils/process.py:35-104`. The only zero-copy reference in VyOS Python is a VPP memif mode toggle (`python/vyos/vpp/control_vpp.py:358-380`), not application-level buffer reuse. | `packHolder` keeps packet header and payload byte arrays, offset/size fields, and a `clear()` method that resets variables except buffers in `pack/packHolder.java:18-44` and `:365-430`. | [analysis] Ze and freeRtr show source-level performance-oriented buffer reuse. VyOS performance is mostly delegated to external daemons/kernel and Python orchestration, based on inspected sources. | | Testability seams | Unit and integration seams | Ze has explicit seams: `ProcessManager.AddProcess` is documented as used by tests to inject mock processes in `manager.go:257-263`; SDK callbacks are maps, not switches, in `sdk.go:60-97`; generated build-tag tests are present in `cmd/ze/hub/build_tag_protocols_absent_test.go:7-12`. | VyOS has unit tests under `src/tests` and smoketest CLI harness. Unit tests use `unittest` in `src/tests/test_config_tree.py:17-48`; smoketest uses `ConfigSession` in `smoketest/bin/vyos-configtest:21-52`; `test_utils.py` patches process calls in `src/tests/test_utils.py:31-38`. | freeRtr has a large process image tester harness: `userTester` fields for temp path, config path, parallel workers, persistent process, captures, runner in `user/userTester.java:35-153`. No JUnit was found in inspected sources. | [analysis] Ze favors in-process unit seams and build-tag checks. VyOS favors Python unit tests plus full CLI/system smoketests. freeRtr appears to favor scenario/process image testing over Java unit frameworks. | | Configuration transaction model | Verify/generate/apply or verify/apply/rollback | Ze plugin SDK supports config verify/configure/apply/rollback callbacks in static plugin registration: `OnConfigVerify`, `OnConfigure`, `OnConfigApply`, and `OnConfigRollback` appear in `internal/plugins/static/register.go:104-204`; rollback journal uses `sdk.NewJournal()` in `:148-177`. | VyOS conf scripts implement `get_config`, `verify`, `generate`, `apply`, then execute in main, `protocols_bgp.py:596-611`. Dependencies can run target scripts with verify/generate/apply via `configdep.py:98-116`. | freeRtr config model is immediate object mutation with start/stop hooks in inspected snippets, for example `servGenList.find(create)` calls `srvInitialize()` in `servGenList.java:31-57`, delete calls `srvDeinit()` in `:59-72`; xconnect stop/start pattern in `cfgXconn.java:115-206`. | [analysis] Ze and VyOS have explicit staged config lifecycles. Ze has rollback callbacks in plugin SDK. VyOS scripts raise ConfigError before generate/apply but rollback behavior is outside inspected snippets. freeRtr changes appear object-oriented and live. | | Configuration storage and config tree | Storage abstractions and tree representations | Ze `cmdStart` resolves blob or filesystem storage in `ze_core_start.go:29-40` and requires blob storage unless auto-init or fallback applies in `:150-174`; hub reads active/candidate configs and writes active versions in `hub/main.go:126-154` and `:209-226`. | VyOS Config API describes running/session config semantics in `python/vyos/config.py:44-57`; `Config.__init__` chooses `ConfigSourceVyconfSession` or `ConfigSourceSession` and stores running/session trees at `:137-154`. | freeRtr stores global state in static Java registries, such as `cfgAll.routers`, `cfgAll.ifaces`, daemon lists at `cfgAll.java:229-500`; config file handling exists in `cfgInit` fields `cfgFileHw`/`cfgFileSw` at `cfgInit.java:100-109`. | [analysis] Ze has explicit storage backends and versioned active/candidate behavior. VyOS has running/session tree sources. freeRtr represents runtime config as live Java objects backed by config files. | | External process integration | Calling out to OS or subprocesses | Ze external plugins use process manager, context cancellation, TLS acceptor, and net.Pipe/TLS SDK, `manager.go:125-173`, `pkg/plugin/sdk/sdk.go:1-30`. Ze also imports netlink/nftables/govpp/wgctrl in `go.mod:15-26`. | VyOS process boundary is central: `popen()` wraps subprocess, shell detection, VRF/netns exec, environment, timeout, stdout/stderr in `python/vyos/utils/process.py:35-104`. Conf mode scripts call systemctl, nft, ip, podman, etc. | freeRtr has Java process execution and shell helpers in imports, for example `cfgInit.java:28-41` imports pipeShell, pipeTcp, pipeConsole, userExec, etc., but no generic external SDK found. | [analysis] VyOS has the most external process integration by design. Ze uses external process boundaries primarily for plugins and system integration libraries. freeRtr mostly keeps network behavior in-process. | | Feature gating and compile-out | Optional feature inclusion | Ze build-tag tests prove protocols compile out, for example `cmd/ze/hub/build_tag_protocols_absent_test.go:1-12`; dispatch comments mention gated CLI imports in `cmd/ze/ze_core_dispatch.go:43-48`. | No compile-time feature gate model found in inspected VyOS sources. Features are included by package files, XML definitions, Debian dependencies, and generated configd include list. | No compile-time feature gate model found in inspected freeRtr sources beyond build scripts/native image. | [analysis] Ze can reduce binary surface by build tags. VyOS and freeRtr feature surface appears package/source included rather than mechanically gated in inspected sources. | ### Concrete implications 1. [analysis] Adding a new feature in Ze usually means adding a component/plugin package, YANG, register.go metadata, tests, and regenerating composition roots. This has high mechanical overhead but strong discoverability, schema validation, command ownership, and optional compile-out. Evidence: generated `all.go:1-8`, static plugin registration `internal/plugins/static/register.go:37-68`, YANG validator `validator.go:153-223`. 2. [analysis] Adding a new feature in VyOS usually means adding or modifying XML command definitions, a Python conf mode script with get_config/verify/generate/apply, Jinja templates or process calls, dependencies, and tests. This enables broad feature parity with Linux packages and daemons quickly, but behavior is distributed across XML, Python, templates, generated node.def/cache files, and systemd. Evidence: `Makefile:24-40`, `protocols_bgp.xml.in:3-6`, `protocols_bgp.py:596-611`, process helper `process.py:35-104`. 3. [analysis] Adding a new feature in freeRtr likely means adding Java protocol/service/config classes and wiring them into central registries or command handlers. This keeps runtime behavior in one process and one language, but source changes can require touching central objects such as `cfgAll`, `cfgRtr`, or service registry maps. Evidence: `cfgAll.java:229-500`, `cfgRtr.java:78-166`, `servGenList.java:133-220`. 4. [analysis] Feature parity between Ze and VyOS may be fastest when Ze can reuse kernel or Go library interfaces, but parity with VyOS features backed by mature external daemons may require either writing Ze-native protocol/service implementations or adding plugin/subprocess integration. Evidence: Ze deps include netlink/govpp/wgctrl in `go.mod:15-26`; VyOS BGP renders to FRR via `FRRender` in `protocols_bgp.py:26-27` and `:596-601`. 5. [analysis] Feature parity between Ze and freeRtr is architecturally different: freeRtr has many own in-process protocol implementations, while Ze uses component/plugin boundaries and SDKs. A Ze feature port from freeRtr may need decomposition into YANG, plugin registration, engine subsystem, and API callbacks rather than a single in-process class. Evidence: freeRtr `cfgRtr` one field per protocol handler in `cfgRtr.java:78-166`; Ze plugin config, SDK, and process manager in `types.go:187-203`, `sdk.go:1-30`, `manager.go:125-173`. 6. [analysis] Ze and freeRtr are better positioned for source-level performance tuning than VyOS scripts, because Ze has explicit allocation-aware helpers and freeRtr has reusable packet buffers. VyOS performance-sensitive forwarding/control behavior is more likely in external daemons/kernel, outside this repo. Evidence: Ze `textbuf.go:54-93`; freeRtr `packHolder.java:18-44` and `:365-430`; the only VyOS zero-copy reference is a VPP memif toggle, not application buffer reuse. 7. [analysis] Ze has clearer external plugin isolation and restart policies than freeRtr in inspected sources, while VyOS gets service restart/isolation from systemd. Evidence: Ze respawn limits and disable path in `manager.go` (`RespawnLimit`, `MaxTotalRespawns`, `Respawn`); VyOS systemctl examples from `container.py:647-684` and `high-availability.py:213-234`; freeRtr no systemd-like respawn found in inspected Java source. 8. [analysis] Test maintainability differs strongly: Ze offers focused unit seams around registries/processes and compile-time gate tests, VyOS offers Python unit tests plus system-level CLI smoketests, freeRtr offers a process image tester. Evidence: Ze `manager.go:257-263` and build-tag tests; VyOS `src/tests/test_utils.py:31-38` and `smoketest/bin/vyos-configtest:21-52`; freeRtr `userTester.java:35-153`. ### Gaps and unclear items - VyOS generic plugin model: not found in inspected sources. I found XML owner/script extensibility and dynamic Python module loading, not a general third-party plugin process SDK. - freeRtr external SDK or plugin model: not found in inspected sources. I found in-process Java class registries and service/router lists. - freeRtr external dependency manifest: not found in inspected sources. No non-JDK, non-`org.freertr` package imports exist (only `java.*`/`javax.*` JDK imports). Shell build scripts use `javac`, `java -jar`, `native-image`, and native helper scripts. - VyOS zero-copy: no application-level zero-copy buffer management in VyOS Python; the only reference is a VPP memif mode toggle (`python/vyos/vpp/control_vpp.py:358-380`). C helpers and external daemons are out of scope here. - freeRtr schema generation: freeRtr generates YANG from its embedded help/CLI schema (`userHelp.getYang` `user/userHelp.java:1249`, `userNetconf.makeYang` `user/userNetconf.java:104`); the help/defaults themselves are Java-embedded. - Ze full generated-protobuf outputs: proto schema is at `api/proto/ze.proto:1-40` (defines `ZeService` and `ZeConfigService`); generated `.pb.go` outputs are out of scope here. - Runtime behavior of VyOS configd itself is only indirectly evidenced here through generated include JSON and guards such as `is_systemd_service_running('vyos-configd.service')` in `protocols_bgp.py:596-601`; the configd daemon implementation itself is out of scope here. --- ### Page: Performance. https://ze-software.net/performance/ Benchmarks # Performance. Measured, not claimed. Benchmark numbers need context: these numbers measure one scenario, on one machine, under artificial conditions. They show relative differences between implementations, not absolute performance. If it matters to you, run `ze-perf` on your own hardware with your own workload. `BGP benchmark` `ze-perf` establishes two BGP sessions with a device under test: a sender injects 100,000 routes, a receiver times their arrival. The same harness runs unmodified against BIRD, FRR, GoBGP, RustyBGP, freeRtr, rustbgpd, and OpenBGPd, all in Docker on the same host. Go carries an estimated 10-15% CPU overhead against C/Rust implementations. That number comes from code-path analysis, not a measured benchmark -- Ze has not been benchmarked at DFZ scale (1M+ prefixes). - **Convergence:** 62ms to propagate 100,000 routes (2026-06-05 run) - **Throughput:** 1,612,903 routes/sec sustained during propagation - **Withdrawal:** 596ms from withdrawal sent to receiver idle - **Full results:** [All DUTs, all runs, full methodology](https://ze-software.net/performance/bgp/) ``` # build ze-perf and all DUT images, then run $ go build -o bin/ze-perf ./cmd/ze-perf && go run ./cmd/ze-perf-run --build --test # test specific DUTs only $ go run ./cmd/ze-perf-run --build --test ze bird # regenerate docs/performance.md from results $ bin/ze-perf report --doc test/perf/results/*.json ``` `Prerequisites` Docker (Colima on macOS). `ze-perf` works against any BGP implementation, not just Ze -- point it at your own DUT. - [Benchmarking guide architecture, flags, JSON output](https://ze-software.net/guides/benchmarking/) - [BGP performance tests with Ze sender, receiver, DUT, JSON](https://ze-software.net/use-cases/bgp-performance/) ## Performance evidence in the labs. Where else the numbers show up. ### [BGP Protocol Interop](https://ze-software.net/labs/bgp-interop/) `Daemon` `Docker` - Same DUTs as the benchmark: **FRR, BIRD, GoBGP** - Correctness first; the perf numbers above measure the same sessions ### [VPP Dataplane Evidence](https://ze-software.net/labs/vpp-dataplane/) `Daemon` `Docker` - Ze programs **FIB, traffic, and firewall** into a real VPP daemon - Dataplane throughput backs the numbers in the VPP guide ### [VLAN QoS Wire-Level Proof](https://ze-software.net/labs/vlan-qos/) `Daemon` `AF_PACKET` - 802.1p **PCP tagging** verified on the wire - Not just kernel-state acceptance -- captured packets --- ### Page: Performance Comparison https://ze-software.net/performance/bgp/ # Performance Comparison > **Benchmark numbers need context.** > > These numbers measure one specific scenario (route propagation latency through > a single DUT with two peers) on one specific machine under artificial conditions. > They do not predict real-world performance. Different hardware, different route > counts, different address families, different policies, different network > conditions will all produce different results. > > Use these numbers to understand *relative* differences between implementations, > not as absolute performance claims. If performance matters to you, run ze-perf > on your own hardware with your own workload. ## Methodology Ze-perf establishes two BGP sessions with a device under test (DUT): a sender and a receiver. The sender injects routes and records when each was sent. The receiver parses incoming UPDATEs and records when each prefix arrived. Propagation latency = time received minus time sent, matched by prefix. Each benchmark runs multiple iterations. Results show the **median** across iterations with standard deviation. Outlier iterations (beyond 2 stddev from median convergence time) are automatically discarded. ## Environment | Field | Value | |-------|-------| | Platform | darwin/arm64 | | Virtualization | Docker (Colima VM) | | Date | 2026-06-05 | | Routes | 100,000 | | Seed | 42 | | Iterations | 3 measured, 1 warmup | **These results were collected on a development laptop using Docker containers via Colima. A dedicated server with bare-metal networking would produce different (likely faster and more consistent) numbers.** ## DUT Setup All DUTs run in Docker containers on the same host. Each DUT is configured with two passive BGP peers (sender AS 65001, receiver AS 65002) and AS 65000 as the local AS. The benchmark tool (ze-perf) establishes both sessions, injects routes via the sender, and measures when they arrive at the receiver. - **Ze** -- Go BGP daemon, goroutine-based, kernel TCP stack. Config: passive peers, route-reflector plugin (bgp-rs), 1M prefix limit per family. Transport: kernel TCP (standard Docker networking). - **BIRD** -- C BGP daemon, kernel TCP stack. Config: passive peers, import/export all (no filtering). Transport: kernel TCP (standard Docker networking). - **FRR** (Free Range Routing) -- C BGP daemon, kernel TCP stack. Config: passive peers, PERMIT route-maps in/out (no filtering). Transport: kernel TCP (standard Docker networking). - **GoBGP** -- Go BGP daemon, kernel TCP stack. Config: passive peers, default accept policy. Transport: kernel TCP (standard Docker networking). - **RustyBGP** -- Rust BGP daemon, kernel TCP stack. Config: passive peers, default policy. Transport: kernel TCP (standard Docker networking). - **freeRtr** -- Java BGP daemon with its own TCP/IP stack. Config: passive peers, 256KB buffer-size, extended-update enabled, advertisement-interval-tx 0, incremental bestpath (1M limit), no safe-ebgp. JVM: 2GB heap with ZGC (low-pause garbage collector). Transport: rawInt bridge (UDP encapsulation between Docker eth0 and freeRtr's virtual interface layer) -- adds latency vs kernel TCP used by other DUTs. Config files: `test/perf/configs/` ## Results ### ipv4/unicast (2026-06-05, 4 GB VM) Fixed RPKI validation gate (was adding 30s pending delay without cache servers) and throughput stddev (now derived from convergence via error propagation). | DUT | Convergence | +/- | Throughput (r/s) | +/- | p99 | +/- | Withdrawal | +/- | |-----|-------------|-----|------------------|-----|-----|-----|------------|-----| | ze | 62ms | 10ms | 1,612,903 | 260,145 | 43ms | 13ms | 596ms | 15ms | | bird | 65ms | 0ms | 1,538,461 | 0 | 28ms | 3ms | 518ms | 0ms | | rustybgp | 327ms | 17ms | 305,810 | 15,898 | 266ms | 2ms | 683ms | 18ms | | frr | 595ms | 11ms | 168,067 | 3,107 | 568ms | 11ms | 637ms | 12ms | | gobgp | 1,198ms | 20ms | 83,472 | 1,393 | 1,145ms | 22ms | 1,319ms | 7ms | | freertr | 2,218ms | 56ms | 45,085 | 1,138 | 2,209ms | 57ms | 1,145ms | 4,609ms | ### ipv4/unicast (2026-06-05, 4 GB VM, pre-fixes) Throughput stddev inflated by reciprocal transform (raw per-iteration stddev). Ze penalized by RPKI validation gate enabling without cache servers (30s fail-open). | DUT | Convergence | +/- | Throughput (r/s) | +/- | p99 | +/- | Withdrawal | +/- | |-----|-------------|-----|------------------|-----|-----|-----|------------|-----| | bird | 29ms | 0ms | 3,448,275 | 97,221 | 35ms | 0ms | 513ms | 0ms | | ze | 53ms | 18ms | 1,886,792 | 939,982 | 57ms | 16ms | 571ms | 22ms | | frr | 548ms | 7ms | 182,481 | 2,414 | 549ms | 6ms | 633ms | 5ms | ### ipv4/unicast (2026-05-24, post-optimization) After pool dedup, buffer-first encoding, and forwarding fast-path work. | DUT | Convergence | +/- | Throughput (r/s) | +/- | p99 | +/- | |-----|-------------|-----|------------------|-----|-----|-----| | bird | 44ms | 1ms | 2,272,727 | 62,858 | 28ms | 5ms | | ze | 71ms | 2ms | 1,408,450 | 44,964 | 54ms | 4ms | | rustbgpd | 179ms | 5ms | 558,659 | 15,247 | 151ms | 12ms | | rustybgp | 252ms | 14ms | 396,825 | 20,283 | 233ms | 13ms | | openbgpd | 472ms | 0ms | 211,864 | 0 | 461ms | 0ms | | frr | 537ms | 10ms | 186,219 | 3,764 | 532ms | 10ms | | gobgp | 1,147ms | 13ms | 87,183 | 1,031 | 1,118ms | 14ms | | freertr | 2,294ms | 146ms | 43,591 | 7,872 | 1,992ms | 619ms | ### ipv4/unicast (2026-04-22, initial) First benchmark run, before any optimization work. | DUT | Convergence | +/- | Throughput (r/s) | +/- | p99 | +/- | |-----|-------------|-----|------------------|-----|-----|-----| | bird | 50ms | 0ms | 2,000,000 | 32,675 | 26ms | 0ms | | ze | 91ms | 27ms | 1,098,901 | 461,693 | 81ms | 27ms | | rustbgpd | 179ms | 5ms | 558,659 | 15,247 | 151ms | 12ms | | rustybgp | 252ms | 14ms | 396,825 | 20,283 | 233ms | 13ms | | frr | 537ms | 10ms | 186,219 | 3,764 | 532ms | 10ms | | gobgp | 1,147ms | 13ms | 87,183 | 1,031 | 1,118ms | 14ms | | freertr | 2,294ms | 146ms | 43,591 | 7,872 | 1,992ms | 619ms | ## Reading the Results **Convergence** is the time from the first UPDATE sent to the last UPDATE received. Lower is better. This is the primary metric: the time until all routes are propagated. **Throughput** is routes received per second, averaged over the convergence window. Higher is better. Zero means all routes arrived in a single burst (sub-second convergence with coalesced TCP delivery). **p50/p99** are per-route latency percentiles. p50 is the median route's latency; p99 is the slowest 1%. The gap between p50 and p99 shows how consistent the DUT's forwarding is. **+/-** columns show standard deviation across iterations. Small stddev means consistent performance; large stddev means the measurement is noisy. **Withdrawal** is the time from sending route withdrawals to the receiver going idle. Lower is better. Measures how fast the DUT propagates route removals. **Lost** should always be zero. Any lost routes indicate the DUT failed to forward some prefixes. ## Reproducing This document is generated by `ze-perf report --doc`. To regenerate it with fresh results: ```bash # Build the Go benchmark binary and all DUT images, then run every benchmark. go build -tags ze_perf -o bin/ze-perf ./cmd/ze go run ./cmd/ze-perf-run --build --test # Run selected DUTs. go run ./cmd/ze-perf-run --build --test ze bird # Render the document from the saved results. bin/ze-perf report --doc test/perf/results/*.json > docs/performance.md ``` Requires Docker (Colima on macOS). See [Benchmarking Guide](../../guides/benchmarking/index.md) for details on environment variables (`DUT_ROUTES`, `DUT_REPEAT`, `PPROF`, etc.). --- ### Page: Code Quality https://ze-software.net/quality/ # Code Quality Ze's quality work has one rule: when something fails, the output should show what broke and what to run next. This page follows that path from a small Go test to the full release evidence. **Quality model** Ze uses several test layers because bugs show up in different places. Local Go tests check package behavior. Fuzz tests are Go tests that try many generated inputs. gomu changes the code and runs the same tests, which shows whether the assertions are strong. Functional transcripts check what an operator, peer, browser, or editor can see. QEMU and interop check the cases that need Linux or real peer daemons. The layers can overlap, but each one has a job. A parser bug should leave behind a Go test or fuzz corpus entry. A survived gomu mutation means either the changed code was equivalent or the test did not check the behavior tightly enough. A CLI or daemon bug should become a functional transcript. A kernel bug should run in QEMU. - **Local:** Go tests, race runs, coverage, fuzz targets, and gomu run before the expensive gates. - **Process:** `.ci`, `.wb`, and `.et` files run Ze the way an operator, peer, browser, or editor would use it. - **Linux:** QEMU runs the same tests where netlink, nftables, eBPF, PPP, and namespaces exist. - **Release:** Interop, deployment, performance, chaos, and release evidence compose the slow proof. ## The flow **Step 1** ### Prove package behavior Start with a small Go test that names the case and expected result. Use fuzzing when one or two examples cannot cover the input space. Use gomu when the code is covered but the assertion may still be too loose. **Step 2** ### Prove visible behavior When the behavior crosses a process boundary, use a functional transcript. `.ci` drives commands, peers, files, HTTP, and daemons. `.wb` drives the browser. `.et` drives the editor. **Step 3** ### Prove Linux behavior Linux behavior is tested on Linux, not guessed from macOS. A functional file can mark itself `option=needs-linux` and then run inside the QEMU Alpine image. **Step 4** ### Prove release behavior Before broad evidence is claimed, Ze runs real peer daemons, deployment checks, performance gates, chaos scenarios, and release reports instead of trusting local fixtures. ## Local tests are one layer ### Example tests A normal Go test gives the case a name and states the exact result. It is the first proof for a parser rule, encoder rule, state transition, validation path, or error shape. ### Fuzz targets A fuzz target is a Go test with generated inputs. It starts from useful seed cases, tries more shapes, and keeps any crash or semantic failure as a corpus entry. ### gomu mutation checks gomu changes production code in small ways and runs the same tests. If the tests still pass, the changed code was equivalent or the assertions did not check that behavior tightly enough. ## Which guide to open | What you are testing | Use this | Guide | | --- | --- | --- | | Package behavior, parser rules, encoders, races, fuzzable inputs, or weak assertions | `go test`, race, fuzz, coverage, gomu mutation checks | [Local Go tests, fuzzing, and gomu](https://ze-software.net/quality/unit-fuzz-mutation/) | | Daemon startup, CLI output, BGP wire output, HTTP, syslog, files, or process exits | `.ci` under `test/<suite>/` | [Functional `.ci` tests](https://ze-software.net/quality/functional-ci/) | | Rendered web UI behavior or interactive editor behavior | `.wb` under `test/web/`, `.et` under `test/editor/` | [Browser and editor tests](https://ze-software.net/quality/browser-editor/) | | Linux kernel behavior, real peer compatibility, deployment, or release evidence | QEMU, Docker interop, deployment scripts, perf gates | [QEMU, interop, and release evidence](https://ze-software.net/quality/qemu-interop-release/) | | A failing verify run that needs a clear rerun command | Verify stages, failure groups, trace output, debug logs | [Verify and debugging workflow](https://ze-software.net/quality/verify-debugging/) | | Whether the suite would actually catch a regression, not how large it is | Proof density, tests that cannot fail, tests nothing runs, ratchets, KPI history | [Testing health](https://ze-software.net/quality/health/) | | RFC requirement coverage, public gap disclosure, or AI agent test-change guards | `./le rfc check`, RFC test tags, status-ledger agreement, audit freshness | [RFC compliance gate](https://ze-software.net/quality/rfc-compliance/) | ## Commands that matter ### Edit loop Use one focused command while changing code. ``` go test -race -run TestName ./internal/component/config/... FUZZ=FuzzParseNLRI PKG=./internal/component/bgp/wire/ TIME=30s ./le fuzz run go run github.com/sivchari/gomu/cmd/gomu run --incremental --base-branch=main --fail-on-gate=false bin/ze-test bgp plugin 42 -v ``` ### Handoff gate Use the shared gate before handing over normal work. ``` ./le verify current mode full ./le verify current mode changed ./le repository check ``` ### Linux and release Use the wider gates only when the behavior needs Linux, real peers, or release evidence. ``` ./le qemu netns-test ./le qemu run command '...' keep-alive ./le integration interop ./le evidence release-candidate ``` ## How a failure becomes useful `./le verify current mode full` is more than a command wrapper. It takes a lock so two heavy runs do not corrupt each other, writes per-stage logs under `tmp/`, groups related failures, and prints the rerun commands. The functional runner adds per-step traces for `.ci`, `.wb`, and `.et` files. BGP expectations decode wire messages before showing differences, so a failed UPDATE is reported as protocol structure instead of a long hex string. ### The rule Do not hide a failure with a skip or a loose assertion. Move the proof to the layer that can see the real behavior, rerun the narrow command, then rerun the gate that should have caught the regression. ## File formats Ze currently has three functional formats. Use `.ci` for process, protocol, command, HTTP, syslog, file, and daemon behavior. Use `.wb` for rendered browser behavior. Use `.et` for the headless interactive editor. There is no `.wt` parser in the current tree; notes using that extension should be read as web tests only if a new parser is later added. --- ### Page: Browser `.wb` and Editor `.et` Tests https://ze-software.net/quality/browser-editor/ # Browser `.wb` and Editor `.et` Tests Ze has two UI functional formats. `.wb` drives the web interface through a real browser session. `.et` drives the interactive configuration editor through the CLI testing harness. Use them when the contract is what a person sees or types, not a helper function hidden under that interface. There is no `.wt` parser in the current tree. Web tests use `.wb`; editor tests use `.et`. ## Browser tests A `.wb` file is a short browser script. The runner starts Ze, opens an isolated `agent-browser` session, runs actions, waits for browser activity to settle, and evaluates expectations against the rendered page. The point is to test the behavior after templates, HTMX, handlers, and client-visible state have all interacted. Use `.wb` when navigation, form behavior, HTMX replacement, visible copy, page title, URL, or accessibility-visible elements are the contract. Use a Go unit test for pure handler logic and use `.ci` for simple HTTP status checks. ``` bin/ze-test web --list bin/ze-test web -p config -v ./le functional web-test ``` ### Browser syntax | Line | Meaning | Example | | --- | --- | --- | | `action=open` | Navigate to a path | `action=open:path=/config` | | `action=click` | Click by visible text or id | `action=click:text=Save` | | `action=fill` | Fill an input selected by label, text, or id | `action=fill:text=Hostname:value=router1` | | `action=press` | Press a key after optional focus | `action=press:text=Search:key=Enter` | | `action=wait` | Wait for a small explicit condition when auto-waiting is not enough | `action=wait:ms=100` | | `expect=element` | Assert visible text in the accessibility snapshot | `expect=element:text=Routes` | | `expect=url` | Assert current browser URL | `expect=url:contains=/config` | | `expect=html` | Assert markup when markup itself is the contract | `expect=html:contains=hx-get` | Prefer `id=` for stable controls and `text=` when the visible label is the contract. A `.wb` test should assert after state-changing actions so the failure points at the first broken transition. Avoid fixed sleeps except for behavior that genuinely has no observable readiness signal. ## Editor tests An `.et` file is a replay script for the interactive configuration editor. The runner creates a temporary config root, starts the editor model, sends key and text input, and checks the prompt, context, completions, dirty state, validation messages, and persisted files. It is headless, which makes it fast, but it still exercises the editor input model rather than a single parser function. ``` bin/ze-test editor --list bin/ze-test editor -p completion -v ./le functional editor-test ``` ### Editor syntax | Line | Meaning | Example | | --- | --- | --- | | `session=` | Create or reuse a named editor session | `session=main` | | `input=type` | Type text into the editor | `input=type:text=set interfaces` | | `input=key` | Send a named key | `input=key:name=tab` | | `input=enter` | Submit the current line | `input=enter` | | `expect=prompt` | Assert the prompt or context | `expect=prompt:contains=interfaces` | | `expect=completion` | Assert completion candidates | `expect=completion:contains=neighbor` | | `expect=dirty` | Assert whether pending changes exist | `expect=dirty:value=true` | | `expect=file` | Assert saved config content | `expect=file:path=config.conf contains=router bgp` | | `restart=` | Restart the editor and reopen persisted state | `restart=session=main` | Use editor tests for completion, validation, path context, commit and discard behavior, session persistence, and lifecycle bugs. Use `.ci` when the behavior is already visible through a non-interactive command. ## Failure reading Both runners emit per-step trace records. The human output shows action and expectation lines with source locations. The machine output emits `VERIFY STEP` JSON so `./le verify current mode full` can group failures without scraping prose. ``` bin/ze-test web config-menu -v bin/ze-test editor completion-basic -v ZE_TEST_KEEP_TMP=1 bin/ze-test editor completion-basic -v ``` For web failures, read the snapshot before changing selectors. For editor failures, read the prompt, context, and buffered text before changing parser code. Most flaky UI tests come from checking too early; prefer an observable wait such as URL, element text, validation state, or command completion. --- ### Page: Functional `.ci` Tests https://ze-software.net/quality/functional-ci/ # Functional `.ci` Tests `.ci` is Ze's process and protocol test format. Use it when correctness is visible through a real command, daemon, socket, HTTP request, syslog line, file artifact, process exit, or BGP wire message. A `.ci` file is an executable transcript. The runner reads key-value directives, creates a temporary workspace, writes embedded files, allocates ports, starts foreground and background commands, waits for readiness, and checks the observable result. That makes the test close to the debugging session a developer would run by hand, but repeatable enough for the verify gate. ## Where it belongs | Behavior | Files | Narrow run | Native suite | | --- | --- | --- | --- | | BGP encode and route output | `test/encode/*.ci` | `bin/ze-test bgp encode NAME -v` | `./le functional encode-test` | | BGP plugin behavior | `test/plugin/*.ci` | `bin/ze-test bgp plugin NAME -v` | `./le functional plugin-test` | | Config parsing | `test/parse/*.ci` | `bin/ze-test bgp parse NAME -v` | `./le functional parse-test` | | Decode command output | `test/decode/*.ci` | `bin/ze-test bgp decode NAME -v` | `./le functional decode-test` | | Reload behavior | `test/reload/*.ci` | `bin/ze-test bgp reload NAME -v` | `./le functional reload-test` | | CLI output | `test/ui/*.ci` | `bin/ze-test ui NAME -v` | `./le functional ui-test` | | L2TP, firewall, policy, LDP, RSVP-TE, IS-IS, OSPF, OSPFv3, static, traffic, VPP, and install flows | `test/<suite>/*.ci` | `bin/ze-test <suite> NAME -v` | `./le functional <suite>-test` | Run `bin/ze-test bgp plugin --list` or the equivalent suite command before picking an id. The list output gives the exact test name, id, status, and rerun shape. ## Execution model The runner has five stages. It parses the file into records, materializes embedded files in a temporary directory, starts background processes, waits for readiness, and then executes foreground commands and expectations in order. Each test owns its temporary directory and ports, so parallel runs do not share mutable state. Background commands are for daemons and peers. Foreground commands are for assertions that should complete, such as `ze cli`, `ze config`, `curl`, helper scripts, or packet checks. A foreground process can assert stdout, stderr, exit code, files, JSON, HTTP, and BGP messages without adding a second test harness. ## Minimal shape ``` file=config.conf<<EOF router bgp 65000 neighbor 127.0.0.1 remote-as 65001 EOF cmd=background:ze:bin/ze config.conf cmd=background:peer:bin/ze-peer --as 65001 --listen 127.0.0.1:$PORT1 cmd=foreground:show:bin/ze cli -c "show bgp peer list" expect=stdout:show:contains=Established ``` Use embedded files for configs, fixtures, plugin payloads, and helper scripts. Use variables such as `$TMP`, `$PORT1`, and `$ZE` rather than hard-coded paths and ports. That keeps the test parallel-safe and portable. ## Expectations | Expectation | Use it for | Example | | --- | --- | --- | | `expect=stdout` and `reject=stdout` | CLI-visible text | `contains=Established` | | `expect=stderr` | Warnings and validation errors | `contains=invalid prefix` | | `expect=exit` | Process status | `code=1` | | `expect=file` | Generated files and state dumps | `path=$TMP/out.json contains=neighbor` | | `expect=json` | Decoded JSON with volatile fields normalized | `json={...}` | | `expect=http` | HTTP readiness and body checks | `url=http://127.0.0.1:$PORT1/health code=200` | | `expect=bgp` | Exact BGP messages | `seq=1 hex=...` | Prefer the narrowest expectation that expresses the contract. For BGP, assert the decoded protocol behavior or the exact wire message. For JSON, remove known volatile fields instead of asserting a loose substring. For CLI output, assert the user-visible line that would catch a regression. ## Linux behavior A `.ci` file that needs netlink, nftables, eBPF, PPP, L2TP, namespaces, kernel routing, or Linux-only sockets should say so directly: ``` option=needs-linux ``` On macOS that test reports SKIP. Inside QEMU the option is inert and the same file runs for real. Use `./le qemu netns-test` for the curated Linux kernel suites. For one command or an interactive investigation, use `./le qemu run command '...' keep-alive`. ## Failure reading A good `.ci` failure tells you the file, step, line, assertion, process output, temporary directory, and rerun command. With `-v`, the runner keeps enough detail to see command stdout and stderr. BGP mismatches are decoded before display, which turns a wire failure into protocol fields. ``` bin/ze-test bgp plugin 42 -v ZE_TEST_KEEP_TMP=1 bin/ze-test bgp plugin 42 -v ze.log.bgp.reactor.peer=debug bin/ze-test bgp plugin 42 -v ``` If the failure only reproduces under Linux, rerun it through `./le qemu run` rather than adding sleeps or Darwin-only skips. --- ### Page: QEMU, Interop, and Release Evidence https://ze-software.net/quality/qemu-interop-release/ # QEMU, Interop, and Release Evidence Use this page when the proof needs a Linux kernel, a real peer daemon, Docker, QEMU, internet data, performance evidence, or the full release matrix. These checks are slower because they leave the local unit-test world and exercise the environment Ze is meant to run in. `./le verify current mode full` stays local on purpose. It should be fast enough to run often and safe enough for a normal developer machine. QEMU and interop jobs are the next layer when a local run would skip the real contract. ## When QEMU is required QEMU is required for code that depends on Linux behavior rather than Go behavior. That includes netlink, nftables, network namespaces, veth pairs, PPP, L2TP, eBPF, kernel route state, raw sockets, Linux-only build tags, and tests marked `option=needs-linux`. | Need | Command | | --- | --- | | Run curated Linux-only functional files | `./le qemu netns-test` | | Run the full suite inside a prepared guest | `./le qemu run command '<guest-le> le qemu all-tests'` | | Rerun one failing command inside the VM | `./le qemu run command '...'` | | Keep a VM alive for manual inspection | `./le qemu run command '...' keep-alive` | The runner boots Alpine from an ISO, mounts the repository over 9p, installs the needed packages, and runs the requested command inside the VM. The VM has Linux capabilities that Docker Desktop on macOS cannot provide reliably. A debug run is the right place to inspect `ip`, `nft`, `dmesg`, temporary files, process state, and generated logs. ## Linux-only `.ci` files A functional file that needs Linux says so explicitly: ``` option=needs-linux ``` On Darwin, the functional runner reports that test as skipped. In QEMU, the same file runs. This keeps the normal verify gate honest while still making the Linux proof available on the same source file. ## Integration Go tests Go tests that require Linux are built with `integration` and `linux` tags. They run in QEMU through the integration targets, not through the normal unit suite. ``` //go:build integration && linux ``` Use this shape when the test is naturally a Go test, such as a kernel API wrapper or package-level integration. Use `.ci` when the proof is a daemon, command, config, or observable operator flow. ## Docker interop Docker interop proves protocol behavior against real implementations. Ze runs BGP scenarios against FRR, BIRD, GoBGP, OpenBGPD, RustyBGP, freeRouter, and related peers. Other labs cover IPsec, L2TP, PPPoE, deployment, and live checks where the target behavior depends on an external daemon or service. | Evidence | Command | What it proves | | --- | --- | --- | | BGP interop | `./le integration interop` | Ze exchanges real protocol messages with third-party BGP daemons. | | IPsec interop | `./le integration interop-ipsec` | strongSwan and Ze agree on the deployed behavior. | | L2TP and PPPoE | `./le deployment docker-l2tp-ppp-test`, `./le deployment docker-pppoe-accel-test` | Access protocol behavior works against real peers. | | Deployment evidence | `./le deployment l2tp-test`, `./le deployment vpp-test` | Deployment paths are not just unit-tested scripts. | Interop tests are not a replacement for functional transcripts. A `.ci` test explains a Ze behavior precisely and cheaply. Interop proves that the behavior still works when another implementation interprets the protocol. ## Performance and live evidence Performance gates are used when a change can regress throughput, convergence, or data-plane behavior. Live evidence is used when the contract includes external data, such as RPKI cache behavior. These checks are not default verify steps because they depend on time, host capacity, Docker, root privileges, or the internet. ``` ./le perf-bench record ./le integration live-rpki ``` ## Release evidence Release evidence runs verification over a clean checkout in a container. The native action checks its prerequisites before it starts the matrix. ``` ./le evidence release-candidate ``` Use release evidence when claiming broad coverage, not when debugging a single change. For a single failure, start from the narrow target that reproduces it and move outward only when the contract requires a wider environment. --- ### Page: Local Go Tests, Fuzzing, and gomu https://ze-software.net/quality/unit-fuzz-mutation/ # Local Go Tests, Fuzzing, and gomu Use this page for the local Go test layer: named `_test.go` cases, race runs, coverage, fuzz targets, and gomu mutation testing. These checks are one system. They all ask whether package-level behavior is proved well enough before the slower gates run. ## The local test loop Start with a normal Go test. It names the behavior and fixes the expected result. If the input space is too large for a few named cases, add a fuzz target for the same behavior. If the code is covered but the assertion may still be weak, run gomu and see whether a deliberate code change survives. | Mode | Question | Command | | --- | --- | --- | | Example test | Does this named input produce the exact expected behavior? | `go test -race -run TestName ./path/...` | | Fuzz target | Does the same rule hold for generated inputs and saved corpus entries? | `FUZZ=FuzzParseNLRI PKG=./internal/component/bgp/wire/ TIME=30s ./le fuzz run` | | gomu mutation run | Would the tests fail if the implementation made a small wrong decision? | `go run github.com/sivchari/gomu/cmd/gomu run --incremental --base-branch=main --fail-on-gate=false` | | Race run | Does the behavior still hold when goroutines are scheduled differently? | `./le test-unit` | | Coverage report | Which branches ran without a strong assertion attached? | `go test -coverprofile coverage.out ./path/...` | Fuzzing without a clear rule is just random input. gomu without real assertions only proves that code was executed. The normal example test gives both tools something precise to extend. ## Example tests A normal unit test is the right tool when the behavior sits inside one package or a small group of production types. Good targets are wire encoders, parsers, state machines, validation helpers, route selection, command formatting, and error paths. If the behavior only exists after a daemon starts, a browser renders, or the Linux kernel answers, use `.ci`, `.wb`, `.et`, or QEMU instead. | Scope | Command | When to use it | | --- | --- | --- | | One test | `go test -race -run TestName ./path/...` | Fast edit loop for one named behavior. | | BGP group | `./le test-unit bgp` | Wire, FSM, peer, and BGP component changes. | | Core group | `./le test-unit core` | Core libraries and shared infrastructure. | | Plugin group | `./le test-unit plugins` | Runtime plugin logic and plugin boundaries. | | Config group | `./le test-unit config` | YANG, config parsing, validation, and rendering. | | CLI group | `./le test-unit cli` | Command parsing and user-visible formatting. | | All unit groups | `./le test-unit` | Local unit gate. | ## Fuzz targets are still tests A Go fuzz target is a test function with generated inputs. It should start from useful seed cases, call the same parser or decoder a normal unit test would call, and assert a stable rule. For Ze, good fuzz targets are BGP attributes, communities, capabilities, AS paths, L2TP control packets, TACACS packets, and other parsers that must survive malformed external input. ``` ./le fuzz run FUZZ=FuzzParseNLRI PKG=./internal/component/bgp/wire/ TIME=30s ./le fuzz run ``` Keep the target deterministic and small. Be strict about accepted errors and round trips. When fuzzing finds a crash or semantic bug, keep the corpus entry. That saved input becomes the named regression case that explains the failure. ## gomu checks assertion strength gomu is the mutation-testing tool Ze uses to test the tests. It changes Go code in small ways and reruns the same test suite. If the tests fail, the mutation is killed. If the tests still pass, the mutation survived. A survived mutation usually means the changed code was equivalent or the test did not check the decision tightly enough. ``` go run github.com/sivchari/gomu/cmd/gomu run --incremental --base-branch=main --fail-on-gate=false go run github.com/sivchari/gomu/cmd/gomu run --incremental=false --fail-on-gate=false ./internal/core/textbuf/ go run github.com/sivchari/gomu/cmd/gomu run --incremental=false --fail-on-gate=false ./le mutation record-history report mutation-report.json ``` This complements fuzzing. Fuzzing changes the inputs and keeps the implementation fixed. gomu changes the implementation and keeps the tests fixed. Together they show whether a test is broad enough and sharp enough. | gomu result | Meaning | Response | | --- | --- | --- | | Killed | The test suite noticed the changed behavior. | No action needed. | | Survived | The tests still passed after a code mutation. | Add a stronger assertion, add a functional test, or classify the mutation as equivalent. | | Timed out | The package or test is too slow for the current mutation settings. | Narrow the package or skip mutation where it does not add signal. | gomu runs through `go run`, so no separate install is needed. `.gomuignore` excludes paths where mutation testing is noisy or not useful. Full mutation runs are slower than unit tests and advisory in release evidence, but a survived mutation in changed code deserves a real decision. ## Choosing the proof | Change | First proof | Strengthen it with | | --- | --- | --- | | Pure parser or encoder | Normal test with exact input and output. | Fuzz target for malformed inputs and gomu for assertion strength. | | State machine or route decision | Normal test for transition or selected result. | Race run if goroutines are involved. | | Error handling | Normal test that asserts the error shape. | gomu if changing a condition could silently pass. | | Malformed external input | Fuzz target seeded with known examples. | Corpus regression when a failure is found. | | Process or UI behavior | Functional transcript. | Normal tests only for helper logic behind the surface. | --- ### Page: Verify and Debugging Workflow https://ze-software.net/quality/verify-debugging/ # Verify and Debugging Workflow Use this page when `./le verify current mode full` fails, when a test needs to be rerun narrowly, or when debug logging should be enabled without turning the whole run into noise. `./le verify current mode full` is the normal pre-handoff gate. It is staged, locked, and designed to tell a developer where to rerun. The changed-only command is useful during development, but a finished change should pass the shared gate that would catch cross-package and functional regressions. ``` ./le verify current mode changed ./le verify current mode full ``` ## What verify does | Stage | Purpose | Typical rerun | | --- | --- | --- | | Lint and architecture checks | Formatting, static analysis, generated docs, wiring, and project rules. | `./le verify lint run` or the printed validation target. | | Unit and race checks | Package contracts, changed groups, and race-sensitive paths. | `go test -race -run TestName ./path/...` | | Functional suites | `.ci`, `.wb`, and `.et` behavior that an operator or browser can observe. | `bin/ze-test <suite> NAME -v` | | Compatibility checks | ExaBGP and related protocol compatibility gates that belong in the local pass. | The command printed by the failure group. | The verify runner writes logs under `tmp/`, keeps a compact failure index, and prints grouped failures. The lock in `internal/le/verify/lock/register.go` prevents two verify-class runs from corrupting shared temp state or making failures unreadable. ## Reading the failure Start with the first failing group. It tells you the stage, summary, related files, and rerun command. If the failure came from a functional transcript, rerun exactly that test with `-v`. If it came from a Go package, rerun one test or one package before rerunning a group target. ``` bin/ze-test bgp plugin 42 -v go test -race -run TestName ./internal/component/bgp/... ``` If the rerun prints a temporary directory, keep it only when you need the artifacts. If the test is Linux-only, go straight to QEMU rather than trying to make Darwin behave like Linux. ``` ZE_TEST_KEEP_TMP=1 bin/ze-test bgp plugin 42 -v ./le qemu run command 'bin/ze-test-linux-arm64 bgp plugin 79 -v' keep-alive ``` ## Trace output Functional runners emit per-step trace records. Human output shows the line and status. Machine output uses `VERIFY STEP` JSON so failure grouping can stay stable even when prose changes. ``` 12 ✓ action open 13 ✗ expect element -> expected element with text "Routes" not found VERIFY STEP: {"file":"test/web/foo.wb","step":13,"kind":"expect","status":"fail"} ``` Trace output is available for `.ci`, `.wb`, and `.et`. Use it to locate the first broken transition, not the last assertion that happened to fail. ## Debug logging Ze logging is controlled per subsystem through environment variables. Enable the narrow log that matches the failing surface. | Surface | Example | | --- | --- | | BGP peer behavior | `ze.log.bgp.reactor.peer=debug bin/ze-test bgp plugin NAME -v` | | Plugin server behavior | `ze.log.plugin.server=debug bin/ze-test bgp plugin NAME -v` | | Config parsing | `ze.log.config=debug bin/ze-test bgp parse NAME -v` | | Linux diagnosis | `./le qemu run command '...' keep-alive`, then inspect `ip`, `nft`, `dmesg`, and temp files. | Do not turn on every log by default. Broad logs can hide the one line that matters and can change timing in concurrent tests. ## Skips and flakes A skip is not proof. `option=needs-linux` is the correct marker for functional behavior that requires Linux. `.wb` has an explicit skip option for external browser fixtures. Go tests should skip only when an optional capability is genuinely absent and another gate covers the required behavior. Flakes should be fixed at the source. Replace timing guesses with readiness checks, process waits, observable browser state, or protocol synchronization. If the test only passes after a sleep grows, the runner is missing a signal. ## Debugging order Use the failure index first, rerun the narrow command second, enable one debug log third, and escalate to QEMU or interop only when the contract needs that environment. After the fix, rerun the narrow command and then the gate that would have caught the regression. --- ### Page: Testing Health https://ze-software.net/quality/health/ # Testing Health GENERATED by `./le test-health update` -- do not edit. Source: `internal/le/testhealth.Answer`. **How current is this?** The structural facts -- which test files nothing runs, which RFCs have no test pair, and every metric's status -- are checked by `./le verify current mode full` and cannot lag the tree. The volume counters are as of the last `./le test-health update` and may lag by a few tests; they deliberately do not fail the check, because a check that fired on most commits that add a test would be routed around rather than read. Ratchets are enforced from the tree itself, not from this page. This page answers **is our testing correct**, not *is our testing large*. Those are different questions. A suite can grow forever while the share of behaviour it would actually catch a regression in falls, and no count of tests can show that. Every metric below belongs to one of three questions; anything belonging to none is volume and is deliberately absent. ## Needs attention | Metric | Question | Value | What to do | |---|---|---|---| | Enrolled RFCs with zero test-proven requirements | Q2 | **35 / 181** (attention) | Pick the largest and complete a pair, or accept it is a single-polarity claim. | | Logged known-failing tests | Q3 | **3** (attention) | Fix or delete the oldest entry; a permanently logged failure is a deleted test with extra steps. | 7 further metric(s) are within threshold and are listed in full below. ## Sensitivity *If the code were wrong, would something go red?* ### Tests with no reachable failure call **132 / 29796 (floor 132)** (ok) These execute code and pass unconditionally. Breaking the code under test would not turn them red. *Action if this degrades:* Add a real assertion, or annotate with `// test-asserts-nothing: <why>` when the oracle is genuinely implicit (a must-not-panic smoke test). | file | test | |---|---| | internal/chaos/chaos_test.go | TestChaosConcurrency | | internal/chaos/watchdog/watchdog_test.go | TestWatchdogRouteRegression | | internal/component/bfd/api/registry_test.go | TestSetGetService_ConcurrentNoRace | | internal/component/bfd/metrics_test.go | TestMetricsHookStateChangeCounters | | internal/component/bfd/metrics_test.go | TestRefreshSessionsGauge | | internal/component/bgp/plugins/bmp/event_test.go | TestBMPPeerUpSkippedOnCacheMiss | | internal/component/bgp/plugins/bmp/event_test.go | TestHandleSenderNoSenders | | internal/component/bgp/plugins/bmp/route_action_test.go | TestProcessRouteMonitoring_MonitorMode_StoresInBMPRIB | | internal/component/bgp/plugins/bmp/route_action_test.go | TestProcessRouteMonitoring_ShortUpdate_Skipped | | internal/component/bgp/plugins/filter_irr/filter_irr_test.go | TestRefreshSwapsAtomically | ### time.sleep() calls in .ci tests **0 (floor 52)** (ok) A sleep is a guess about timing that hides the race it was added to mask. The ratchet allows the count to fall, never rise. *Action if this degrades:* Replace a sleep with a payload-predicate wait (wait_until, dispatch_until), then lower the floor in the same change. ## Intent coverage *Are the things that matter checked, or only the happy path?* ### Enrolled RFCs with zero test-proven requirements **35 / 181** (attention) Enrolled and gate-green, but no requirement is proven by BOTH polarities. Some of these do carry positive-only tests; none carries a pair. *Action if this degrades:* Pick the largest and complete a pair, or accept it is a single-polarity claim. ### RFC MUST requirements proven by test, over the RFCs ze implements **1834 / 3047** (ok) 60.2% of the 3047 gated MUSTs the 146 RFCs ze implements carry are proven by a tagged test: both polarities, or one polarity whose annotation records that no input drives the other side. The gate holds a wider set -- 3309 gated MUSTs across 181 enrolled RFCs -- and of the 1795 of those not proven in both polarities: 827 not-applicable (recorded as not binding ze; the owner ruling of 2026-08-31 presumes most of these need re-homing, so they stay inside the denominator above rather than being subtracted from it), 498 known gap (unimplemented, genuinely untested), 373 single-polarity -- those DO have a passing tagged test, just one side of the pair, and the RFC gate fails if that test is missing -- 26 met by a layer under ze on state ze installs, which the annotation names with the producer that installs it: those are MET and are not proven by ze, so they count in the denominator above and never in the share, 2 conditional on an optional feature ze does not offer, each quoting the RFC sentence that makes it optional: the condition is false, so nothing is owed, and 62 with no test and no annotation at all, which is what `./le rfc check` is red about. Only the gap column and that last one are untested work. *Action if this degrades:* Write a test for a {gap} requirement, or for one carrying no test and no annotation. A single-polarity requirement is already counted as proven, and not-applicable needs no test. | rfc | gated | |---|---| | rfc7871 | 38 | | rfc2132 | 34 | | rfc4213 | 23 | | rfc4761 | 18 | | rfc3032 | 17 | | rfc7166 | 17 | | rfc4862 | 16 | | draft-ietf-idr-linklocal-capability | 13 | | rfc2003 | 13 | | rfc9514 | 13 | ### In-repo test inventory **29830 test functions** (ok) 4065 Go test files, 83 fuzz targets, 133 benchmarks, 2029 .ci scenarios, 170 .et editor tests. Counts cover internal, cmd, pkg, test only: vendor/ and gokrazy/modcache/ are third-party module trees and are excluded. *Action if this degrades:* This is volume, not health. It is here to state the counting boundary, because a count that silently includes vendored tests inflates by ~6x. ### Test files that expect a specific error **1467 / 4065** (ok) Counts files using an error-expectation token (wantErr, ErrorIs, assert.Error, ...), with comments stripped. Setup guards of the form `if err != nil { t.Fatal(err) }` are deliberately NOT counted: those assert the happy path. Blind spot: expecting *an* error is weaker than pinning the right one. *Action if this degrades:* Take the lowest-ranked subsystem and add malformed-input or fault-injection cases. | area | negative | files | percent | |---|---|---|---| | internal/chaos/report | 0 | 6 | 0.0 | | internal/chaos/web | 0 | 10 | 0.0 | | internal/core/rib | 0 | 13 | 0.0 | | internal/core/stats | 0 | 5 | 0.0 | | internal/le/hookruntime | 0 | 6 | 0.0 | | internal/le/leroot | 0 | 5 | 0.0 | | internal/plugins/completion | 0 | 6 | 0.0 | | internal/chaos/peer | 1 | 11 | 9.1 | | internal/component/lg | 2 | 21 | 9.5 | | internal/component/doctor | 2 | 20 | 10.0 | ### Technique adoption by package age **2 age buckets** (ok) A technique adopted only forward from its introduction shows here as a step: recent buckets carry it, older ones never do. *Action if this degrades:* Back-fill the oldest bucket, or record the uncovered remainder as tracked backlog (ai/rules/testing.md, Back-Fill New Test Types). | package first commit | packages with tests | with a fuzz target | with an RFC-tagged test | with a .ci scenario | |---|---|---|---|---| | 2025 | 1 | 0 | 0 | 0 | | 2026 | 629 | 32 | 103 | 35 | ## Integrity *When something goes red, does it stop the line?* ### Logged known-failing tests **3** (attention) Reds logged rather than fixed, one shard file per live failure (42 entries archived in plan/known-failures/RESOLVED.md are not counted). Structural gates may never be logged here, but a live entry is not necessarily flaky: some are deterministic product bugs awaiting a fix. *Action if this degrades:* Fix or delete the oldest entry; a permanently logged failure is a deleted test with extra steps. ### Test files no native test action can build **0 (floor 0)** (ok) No registered Go test action supplies these build tags, so the tests exist but never run. *Action if this degrades:* Add the tag to a native Go test action, or delete the file. Either way the false inventory shrinks. ## Trends Insufficient data: 2 recorded sample(s), 4 needed before a trend is drawn. A line through three points is noise with a direction. Append a sample with `./le test-health record`. ## How to read this - Every ratio shows its numerator and denominator. A percentage alone hides the case where a score improves because the denominator shrank. - `unknown` is not `ok`. A metric whose input is missing sorts above every other row, because a number nobody is computing is worse than a bad number. - Counts marked with a floor are ratchets: they may fall, never rise. `./le verify current mode full` enforces them. - Volume figures cover in-repo trees only. `vendor/` and `gokrazy/modcache/` are third-party module trees; including them inflates the test count roughly sixfold. --- ### Page: RFC Compliance Gate Report https://ze-software.net/quality/rfc-compliance/ # RFC Compliance Gate Report Source: `internal/le/rfc`, `rfc/short/*.md`, and `rfc/audit/*.json`. ## Gate verdict RED. 105 open gate issues. Check results below names them, up to the 25 this page inlines. Reproduce it with `./le rfc check`. The gate's own line reads `rfc-requirements: 105 violation(s)`. ### Overall the populations every share below is taken over | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 3,047 | 5,450 extracted from 200 summaries | MUST-level requirements the gate HOLDS, across the 146 RFCs Ze implements, out of 3,309 across the 181 RFCs inspected. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 637 | of 3,047 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Proven by test | 60.2% | 1,834 of 3,047 gated MUSTs across the 146 RFCs Ze implements | the share this site publishes everywhere: Tested both ways and One polarity plus reason added together, two of the five shares that partition the same denominator. That denominator keeps the {not-applicable} obligations, so annotating a requirement away cannot raise it | | Tested both ways | 48.5% | 1,478 of 3,047 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 11.7% | 356 of 3,047 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | Proven by a recorded break | 9.9% | 433 of 4,368 tagged units in enrolled RFCs, 2 escaped and 28 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Not applicable | 20.8% | 635 of 3,047 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.9% | 26 of 3,047 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.1% | 2 of 3,047 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | | Semantic verdicts | 54 | 0 shifted, 2 stale, 3,253 missing | requirements a reader has judged and whose judgement is still current. A missing verdict is not claimed, and the shifted and stale ones are named on their own RFC's page | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | One polarity, unexcused | 0.2% | 7 of 3,047 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 17.8% | 543 of 3,047 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Gate verdict | RED | 105 open gate issues | whether ./le rfc check passes over this tree | The 7 shares marked as a part above are the whole of the 3,047 gated MUSTs: they add to 100%. Proven by test is the first two of them added together, so it is not a part of its own. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Proven by test | ok | green at every value: a proven obligation is the outcome this gate exists to produce, and the number under the label is what says how far Ze has got | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | bad | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Gate verdict | bad | the verdict IS the value: green when the gate passes, red when it does not | | Semantic verdicts | neutral | no color: a count of judgements recorded is a scale rather than an outcome, and the shifted and stale counts beside it are the states that need reading | | Metric | Value | |---|---:| | Gate issues | 105 | | Gated MUST-level requirements | 3,309 | | Enrolled RFCs | 183 | | Resolved test tags | 4,560 | | Declared gaps | 481 | | RFCs with declared gaps | 78 | | Fresh semantic audit verdicts | 54 | | Shifted semantic audit verdicts | 0 | | Stale semantic audit verdicts | 2 | ## Requirement buckets The bar is every one of the 3,047 gated MUST-level requirements. 637 of them are {not-applicable} or {feature-declined}: they do not bind Ze, and they are a named segment of the bar rather than an omission from it. | Bucket | Count | Share of gated | Source condition | |---|---:|---:|---| | Positive and negative tests | 1,478 | 48.5% | `positive tag + negative tag` | | One polarity plus reason | 356 | 11.7% | `{single-polarity} annotation + required tag` | | Declared gap | 481 | 15.8% | `{gap} annotation + public ledger disclosure` | | One polarity, unexcused | 7 | 0.2% | `tag without annotation` | | Missing, unexcused | 62 | 2.0% | `no tag, no annotation` | | Not applicable | 635 | 20.8% | `{not-applicable} annotation`: the obligation does not bind Ze, so it is scope rather than coverage | | Met below Ze | 26 | 0.9% | `{lower-layer} annotation + named producer` | | Optional feature declined | 2 | 0.1% | `{feature-declined} annotation + quoted RFC sentence`: the obligation does not bind Ze, so it is scope rather than coverage | | **Gated MUST-level requirements** | **3,047** | 100.0% | every gated MUST falls in exactly one bucket above, the 637 that do not bind Ze included. This total is the denominator of every share above it | ## Gap disclosure | Public status for RFCs with gaps | RFCs | |---|---:| | Partial | 62 | | Experimental | 12 | | Supported | 3 | | Not supported | 1 | ### Supported rows that still disclose a gap - **RFC 1350:** 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 6396:** 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 7313:** Four MUST-level receive-side gaps annotated in `rfc/short/rfc7313.md`: 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. ## Exclusion disclosure A reviewer walks an RFC's own text sentence by sentence and decides which sentences become requirements. One that does not is EXCLUDED, with a kind and a reason, and it never reaches the gated ledger at all. That is a different mechanism from the Out of scope card above, which counts requirements that exist and carry a {not-applicable} annotation. Across the sign-offs done so far, 941 sentences were mapped to a requirement and 536 were declined. 13 of those declines are not scope at all: they are obligations Ze OWES, relocated to a named spec, and they are stated apart below. 61 of 200 summaries carry an extraction sign-off, 58 of them among the 183 enrolled. The other 139 have no exclusion ledger at all, so what follows counts the walks that HAVE been done and is not the whole picture. | Excluded kind | Means | Sites | Summaries | What it means | |---|---|---|---|---| | `binds-another-role` | never bound Ze | 239 | 22 | the obligation is addressed to a role Ze never acts as | | `duplicate-of` | never bound Ze | 116 | 24 | the same obligation is already captured under another requirement id | | `not-a-requirement` | never bound Ze | 100 | 37 | the sentence states a fact or describes another document, and directs no implementation | | `feature-out-of-scope` | never bound Ze | 29 | 5 | the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | | `cross-document` | never bound Ze | 26 | 17 | the obligation belongs to another document that this one only cites | | `advisory-in-context` | never bound Ze | 13 | 7 | the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | | `relocated-to-spec` | Ze owes it | 13 | 2 | the obligation is real and unbuilt, and a named spec owes it | | **Sentences declined** | - | **536** | - | 523 say the obligation never bound Ze and 13 say Ze owes it, of 1,477 normative sentences the walks found | **binds-another-role (239):** [`RFC 1035`](rfc1035/index.md), [`RFC 1350`](rfc1350/index.md), [`RFC 1997`](rfc1997/index.md), [`RFC 2545`](rfc2545/index.md), [`RFC 2759`](rfc2759/index.md), [`RFC 2865`](rfc2865/index.md), [`RFC 2866`](rfc2866/index.md), [`RFC 2869`](rfc2869/index.md), [`RFC 3032`](rfc3032/index.md), [`RFC 3579`](rfc3579/index.md), [`RFC 3748`](rfc3748/index.md), [`RFC 4302`](rfc4302/index.md), [`RFC 4303`](rfc4303/index.md), [`RFC 4360`](rfc4360/index.md), [`RFC 4364`](rfc4364/index.md), [`RFC 4456`](rfc4456/index.md), [`RFC 4761`](rfc4761/index.md), [`RFC 5176`](rfc5176/index.md), [`RFC 6396`](rfc6396/index.md), [`RFC 7296`](rfc7296/index.md), [`RFC 7535`](rfc7535/index.md), [`RFC 8654`](rfc8654/index.md) ai/rules/rfc-compliance.md treats binds-another-role as PRESUMED WRONG until it is justified: Ze rarely implements one side of a protocol, so an obligation addressed to "the sender" or "the receiver" almost always binds it, and the label reads as "not our problem" where the truth is usually "our problem, unbuilt". Each one is justified on its own RFC's page, under Extraction sign-off, and the justification MUST name the role, show Ze never acts as it, and cite the producer that would act as it if Ze did. **duplicate-of (116):** [`RFC 1035`](rfc1035/index.md), [`RFC 2759`](rfc2759/index.md), [`RFC 2865`](rfc2865/index.md), [`RFC 2866`](rfc2866/index.md), [`RFC 2869`](rfc2869/index.md), [`RFC 3032`](rfc3032/index.md), [`RFC 3579`](rfc3579/index.md), [`RFC 3748`](rfc3748/index.md), [`RFC 3948`](rfc3948/index.md), [`RFC 4302`](rfc4302/index.md), [`RFC 4303`](rfc4303/index.md), [`RFC 4364`](rfc4364/index.md), [`RFC 4456`](rfc4456/index.md), [`RFC 4760`](rfc4760/index.md), [`RFC 5549`](rfc5549/index.md), [`RFC 5798`](rfc5798/index.md), [`RFC 5880`](rfc5880/index.md), [`RFC 7296`](rfc7296/index.md), [`RFC 8671`](rfc8671/index.md), [`RFC 8950`](rfc8950/index.md), [`RFC 9003`](rfc9003/index.md), [`RFC 9069`](rfc9069/index.md), [`RFC 9190`](rfc9190/index.md), [`RFC 9234`](rfc9234/index.md) **not-a-requirement (100):** [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY`](draft-abraitis-bgp-version-capability/index.md), [`DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY`](draft-walton-bgp-hostname-capability/index.md), [`RFC 1035`](rfc1035/index.md), [`RFC 1350`](rfc1350/index.md), [`RFC 2347`](rfc2347/index.md), [`RFC 2385`](rfc2385/index.md), [`RFC 2545`](rfc2545/index.md), [`RFC 2759`](rfc2759/index.md), [`RFC 2865`](rfc2865/index.md), [`RFC 2869`](rfc2869/index.md), [`RFC 2918`](rfc2918/index.md), [`RFC 3032`](rfc3032/index.md), [`RFC 3579`](rfc3579/index.md), [`RFC 3748`](rfc3748/index.md), [`RFC 3765`](rfc3765/index.md), [`RFC 3948`](rfc3948/index.md), [`RFC 4303`](rfc4303/index.md), [`RFC 4360`](rfc4360/index.md), [`RFC 4364`](rfc4364/index.md), [`RFC 4456`](rfc4456/index.md), [`RFC 4761`](rfc4761/index.md), [`RFC 5176`](rfc5176/index.md), [`RFC 5282`](rfc5282/index.md), [`RFC 5301`](rfc5301/index.md), [`RFC 5492`](rfc5492/index.md), [`RFC 5798`](rfc5798/index.md), [`RFC 6286`](rfc6286/index.md), [`RFC 6396`](rfc6396/index.md), [`RFC 7296`](rfc7296/index.md), [`RFC 7534`](rfc7534/index.md), [`RFC 7535`](rfc7535/index.md), [`RFC 7911`](rfc7911/index.md), [`RFC 7999`](rfc7999/index.md), [`RFC 8654`](rfc8654/index.md), [`RFC 9190`](rfc9190/index.md), [`RFC 9384`](rfc9384/index.md), [`RFC 9687`](rfc9687/index.md) **feature-out-of-scope (29):** [`RFC 3748`](rfc3748/index.md), [`RFC 5082`](rfc5082/index.md), [`RFC 5176`](rfc5176/index.md), [`RFC 8671`](rfc8671/index.md), [`RFC 9069`](rfc9069/index.md) **cross-document (26):** [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY`](draft-abraitis-bgp-version-capability/index.md), [`RFC 2545`](rfc2545/index.md), [`RFC 2869`](rfc2869/index.md), [`RFC 3032`](rfc3032/index.md), [`RFC 3579`](rfc3579/index.md), [`RFC 3748`](rfc3748/index.md), [`RFC 3948`](rfc3948/index.md), [`RFC 4303`](rfc4303/index.md), [`RFC 4364`](rfc4364/index.md), [`RFC 4761`](rfc4761/index.md), [`RFC 5082`](rfc5082/index.md), [`RFC 5176`](rfc5176/index.md), [`RFC 5282`](rfc5282/index.md), [`RFC 5492`](rfc5492/index.md), [`RFC 7535`](rfc7535/index.md), [`RFC 8654`](rfc8654/index.md), [`RFC 9069`](rfc9069/index.md) **advisory-in-context (13):** [`RFC 1035`](rfc1035/index.md), [`RFC 2866`](rfc2866/index.md), [`RFC 3032`](rfc3032/index.md), [`RFC 3748`](rfc3748/index.md), [`RFC 3948`](rfc3948/index.md), [`RFC 4761`](rfc4761/index.md), [`RFC 5176`](rfc5176/index.md) ### Obligations relocated to a spec These 13 sentences are NOT scope. Each is an obligation Ze owes and has not built, moved to a named spec that reserves a requirement id for it. ./le rfc check refuses the sign-off unless that spec exists and still reserves the id, so each row is tracked work rather than an obligation that went away. | RFC | Reserved id | The obligation | The spec that owes it | |---|---|---|---| | [`RFC 7296`](rfc7296/index.md) | `RFC7296-1.7-3` | Implementations that conform to this document MUST ignore proposals that have configuration attribute type 5, the old value for INTERNAL_ADDRESS_EXPIRY. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-2.19-2` | Initiator Responder ------------------------------------------------------------------- HDR, SK {IDi, [CERT,] [CERTREQ,] [IDr,] AUTH, CP(CFG_REQUEST), SAi2, TSi, TSr} --> <-- HDR, SK {IDr, [CERT,] AUTH, CP(CFG_REPLY), SAr2, TSi, TSr} In all cases, the CP payload MUST be inserted before the SA payload. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-2.19-3` | In variations of the protocol where there are multiple IKE_AUTH exchanges, the CP payloads MUST be inserted in the messages containing the SA payloads. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-2.19-5` | The responder MUST NOT send a CFG_REPLY without having first received a CP(CFG_REQUEST) from the initiator, because we do not want the IRAS to perform an unnecessary configuration lookup if the IRAC cannot process the REPLY. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-2.19-6` | In the case where the IRAS's configuration requires that CP be used for a given identity IDi, but IRAC has failed to send a CP(CFG_REQUEST), IRAS MUST fail the request, and terminate the Child SA creation with a FAILED_CP_REQUIRED error. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-2.22-1` | These payloads MUST NOT occur in messages that do not contain SA payloads. | `plan/spec-ipsec-ipcomp.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-2.22-2` | Although there has been discussion of allowing multiple compression algorithms to be accepted and to have different compression algorithms available for the two directions of a Child SA, implementations of this specification MUST NOT accept an IPComp algorithm that was not proposed, MUST NOT accept more than one, and MUST NOT compress using an algorithm other than one proposed and accepted in the setup of the Child SA. | `plan/spec-ipsec-ipcomp.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-3.15.1-1` | Only one netmask is allowed in the request and response messages (e.g., 255.255.255.0), and it MUST be used only with an INTERNAL_IP4_ADDRESS attribute. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-3.15.1-3` | o SUPPORTED_ATTRIBUTES - When used within a Request, this attribute MUST be zero-length and specifies a query to the responder to reply back with all of the attributes that it supports. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-3.15.1-4` | Unrecognized or unsupported attributes MUST be ignored in both requests and responses. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-4-2` | If an implementation supports responding to such requests, it MUST parse the CP payload of type CFG_REQUEST in the first message in the IKE_AUTH exchange and recognize a field of type INTERNAL_IP4_ADDRESS or INTERNAL_IP6_ADDRESS. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7296`](rfc7296/index.md) | `RFC7296-4-3` | If it supports leasing an address of the appropriate type, it MUST return a CP payload of type CFG_REPLY containing an address of the requested type. | `plan/immediate/spec-ipsec-remote-access.md` | | [`RFC 7947`](rfc7947/index.md) | `RFC7947-2.1-1` | A route server MUST accept all UPDATE messages received from each of its clients for inclusion in its Adj-RIB-In. | `plan/immediate/spec-rfc7947-adj-rib-in-accepts-filtered-updates.md` | ## Top gap clusters | RFC | Declared gaps | Public status | |---|---:|---| | `RFC 9012` | 51 | Partial | | `DRAFT-IETF-BESS-MUP-SAFI` | 37 | Partial | | `RFC 1661` | 24 | Partial | | `RFC 9830` | 20 | Partial | | `RFC 2131` | 18 | Partial | | `RFC 4577` | 16 | Not supported | | `RFC 4271` | 15 | Partial | | `RFC 7432` | 15 | Partial | | `RFC 3579` | 14 | Partial | | `RFC 5880` | 14 | Partial | | `RFC 8665` | 14 | Partial | | `RFC 9514` | 13 | Partial | ## How this is checked This gate runs before a commit is verified: ./le rfc check is 1 stage of the 49 that ./le verify current mode full runs. | Input | Producer | What it answered here | |---|---|---| | Reproduce it | `./le rfc check` | 1 of 49 full-mode verify stages run it | | Requirement source | `rfc/short/*.md` | 3,309 gated MUST-level requirements | | Enrolment | `rfc/short/*.md`, the `\| Enrolment \|` Meta row | 183 enrolled RFCs | | Test tags | `internal/`, `pkg/`, `test/` | 4,560 resolved tags | | Public ledger | `rfc/short/*.md`, the `\| Support \|` Meta row | 78 RFCs with gaps, 3 Supported with Remaining | | Semantic audits | `rfc/audit/*.json` | 54 fresh, 0 shifted, 2 stale, 3,253 missing | | Pre-commit verification | `internal/le/verify/engine/stages.go` | ./le rfc check, 1 of 49 full-mode stages | | Published artifacts | `data/rfc-compliance.json`, `data/rfc-requirements.json` | the same answers this page renders, machine-readable | ## Check results | RFC | Requirement | Level | What is wrong | The requirement | |---|---|---|---|---| | - | `-` | - | internal/component/bgp/reactor/session_as_migration_test.go:79: unknown RFC requirement: RFC7705-4.2-2 | - | | - | `-` | - | internal/component/bgp/reactor/session_as_migration_test.go:82: unknown RFC requirement: RFC7705-4.2-2 | - | | - | `-` | - | internal/component/bgp/reactor/session_as_migration_test.go:139: unknown RFC requirement: RFC7705-4.2-3 | - | | - | `-` | - | internal/component/bgp/reactor/session_as_migration_test.go:143: unknown RFC requirement: RFC7705-4.2-3 | - | | - | `-` | - | internal/component/bgp/reactor/session_as_migration_test.go:219: unknown RFC requirement: RFC7705-4.2-4 | - | | - | `-` | - | internal/component/bgp/reactor/session_as_migration_test.go:222: unknown RFC requirement: RFC7705-4.2-4 | - | | - | `-` | - | internal/component/tacacs/packet_test.go:161: unknown RFC requirement: RFC8907-10-2 | - | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-1`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3-1) | MUST | has no test and no annotation | "If an implementation supports the inclusion of the capability, the implementation MUST include a configuration option to enable or disable its use, and MUST default to disabled" -- the configuration option half (§3) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-2`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3-2) | MUST | has no test and no annotation | "If an implementation supports the inclusion of the capability, the implementation MUST include a configuration option to enable or disable its use, and MUST default to disabled" -- the default-to-disabled half (§3) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-3`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3-3) | MUST | has no test and no annotation | "The Capability Length for the Software Version Capability MUST be greater than zero" (§3) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-4`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3-4) | SHALL | has no test and no annotation | "A value of zero SHALL be treated as an encoding error and the Capability MUST be ignored" -- the encoding-error half (§3) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-5`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3-5) | MUST | has no test and no annotation | "A value of zero SHALL be treated as an encoding error and the Capability MUST be ignored" -- the ignore half (§3) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-6`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3-6) | MUST | has no test and no annotation | "The Version field MUST be encoded using UTF-8" (§3) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-7`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3-7) | MUST NOT | has no test and no annotation | "A receiving BGP speaker MUST NOT interpret invalid UTF-8 sequences" (§3) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-8`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3-8) | MUST NOT | has no test and no annotation | "A sender SHOULD limit generated product identifiers to what is necessary to identify the product; a sender MUST NOT generate advertising or other nonessential information within the product identifier" -- the MUST NOT half (§3) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3.1-1`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-3.1-1) | REQUIRED | has no test and no annotation | "Implementations of this specification are REQUIRED Extended Optional Parameters Length for BGP OPEN Message support as defined in [RFC9072]" (§3.1) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-1`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-4-1) | MUST | has no test and no annotation | "The Software Version Capability MUST only be used for displaying the version of a BGP speaker's router daemon to make troubleshooting easier" (§4) | | DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-2`](draft-abraitis-bgp-version-capability/index.md#draft-abraitis-bgp-version-capability-4-2) | MUST | has no test and no annotation | "Enabling (i.e., turning on) this capability requires bouncing all existing BGP sessions and the feature MUST be explicitly configured before an implementation advertizes the Software Version Capability" (§4) | | DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-1`](draft-ietf-idr-linklocal-capability/index.md#draft-ietf-idr-linklocal-capability-3-1) | MUST | has no test and no annotation | "If an implementation intends to send a single IPv6 Link-Local forwarding address in the Next Hop field of the MP_REACH_NLRI, it MUST set the length of the Next Hop field to 16 and include only the IPv6 Link-Local address in the Next Hop field" (§3) | | DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-2`](draft-ietf-idr-linklocal-capability/index.md#draft-ietf-idr-linklocal-capability-3-2) | MUST | has no test and no annotation | "If an implementation intends to send both a IPv6 Global and Link-Local forwarding address in the Next Hop field of the MP_REACH_NLRI, it MUST set the length of the Next Hop field to 32 and include both the IPv6 Global and Link-Local addresses in the Next Hop field" (§3) | | DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-1`](draft-ietf-idr-linklocal-capability/index.md#draft-ietf-idr-linklocal-capability-4-1) | MUST | has no test and no annotation | "If, after completing these procedures, there are no IPv6 next hop addresses included in the next hop, the BGP route MUST not be advertised to its peer" (§4) | | DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-2`](draft-ietf-idr-linklocal-capability/index.md#draft-ietf-idr-linklocal-capability-4-2) | MUST NOT | has no test and no annotation | "If the internal peer is more than one IP hop away, the BGP speaker MUST NOT include a Link-Local IPv6 next hop" (§4) | | DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-3`](draft-ietf-idr-linklocal-capability/index.md#draft-ietf-idr-linklocal-capability-4-3) | MUST | has no test and no annotation | "If the route is directly connected to the speaker, or if the interface address of the router through which the announced network is reachable for the speaker is the internal peer's address, the next hop MUST include its own Link-Local IPv6 address" (§4) | | DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-4`](draft-ietf-idr-linklocal-capability/index.md#draft-ietf-idr-linklocal-capability-4-4) | MUST NOT | has no test and no annotation | "If, after evaluating the above procedures, there are no IPv6 next hops included with the route, the route MUST NOT be announced to the remote BGP speaker" (§4) | | DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-5`](draft-ietf-idr-linklocal-capability/index.md#draft-ietf-idr-linklocal-capability-4-5) | MUST NOT | has no test and no annotation | "A Route Reflector (RR) reflecting a route with a link-local-only next hop MUST NOT advertise that route to a client unless the client shares the same link-layer segment as the original advertiser" (§4) | 80 further findings not shown here. The whole list is in data/rfc-compliance.json, and each one is on its own RFC's page under the requirement it names. ## Enrolled RFCs | RFC | Public status | Gated MUSTs | Declared gaps | Gated with no test | |---|---|---:|---:|---:| | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY`](draft-abraitis-bgp-version-capability/index.md) Software Version Capability for BGP | Partial | 11 | 0 | 11 | | [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT`](draft-abraitis-idr-addpath-paths-limit/index.md) Scalability Considerations for ADD-PATH with PATHS-LIMIT | Supported | 4 | 0 | 0 | | [`DRAFT-IETF-BESS-MUP-SAFI`](draft-ietf-bess-mup-safi/index.md) BGP Extensions for the Mobile User Plane (MUP) SAFI | Partial | 40 | 37 | 0 | | [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE`](draft-ietf-idr-bgp-bfd-strict-mode/index.md) BGP BFD Strict-Mode | Supported | 4 | 0 | 0 | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY`](draft-ietf-idr-linklocal-capability/index.md) Link-Local Next Hop Capability for BGP | Partial | 13 | 0 | 13 | | [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION`](draft-ietf-sidrops-aspa-verification/index.md) Verification of AS_PATH Using the Resource Certificate PKI and Autonomous System Provider Authorization | Partial | 8 | 2 | 0 | | [`DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY`](draft-walton-bgp-hostname-capability/index.md) Hostname Capability for BGP | Supported | 0 | 0 | 0 | | [`RFC 1071`](rfc1071/index.md) Computing the Internet Checksum | No public row declared | 8 | 0 | 0 | | [`RFC 1195`](rfc1195/index.md) Use of OSI IS-IS for Routing in TCP/IP and Dual Environments | Experimental | 10 | 0 | 0 | | [`RFC 1332`](rfc1332/index.md) The PPP Internet Protocol Control Protocol (IPCP) | Partial | 6 | 1 | 0 | | [`RFC 1334`](rfc1334/index.md) PPP Authentication Protocols | Partial | 7 | 0 | 0 | | [`RFC 1350`](rfc1350/index.md) The TFTP Protocol (Revision 2) | Supported | 11 | 1 | 0 | | [`RFC 1661`](rfc1661/index.md) The Point-to-Point Protocol (PPP) | Partial | 66 | 24 | 0 | | [`RFC 1877`](rfc1877/index.md) PPP Internet Protocol Control Protocol Extensions for Name Server Addresses | Partial | 4 | 1 | 0 | | [`RFC 1994`](rfc1994/index.md) PPP Challenge Handshake Authentication Protocol (CHAP) | Partial | 17 | 3 | 0 | | [`RFC 1997`](rfc1997/index.md) BGP Communities Attribute | Supported | 5 | 0 | 0 | | [`RFC 2003`](rfc2003/index.md) IP Encapsulation within IP | No public row declared | 13 | 0 | 0 | | [`RFC 2131`](rfc2131/index.md) Dynamic Host Configuration Protocol | Partial | 64 | 18 | 0 | | [`RFC 2132`](rfc2132/index.md) DHCP Options and BOOTP Vendor Extensions | Partial | 34 | 1 | 0 | | [`RFC 2181`](rfc2181/index.md) Clarifications to the DNS Specification | Partial | 23 | 1 | 0 | | [`RFC 2205`](rfc2205/index.md) Resource ReSerVation Protocol (RSVP) -- Version 1 Functional Specification | Experimental | 6 | 2 | 0 | | [`RFC 2328`](rfc2328/index.md) OSPF Version 2 | Partial | 25 | 1 | 0 | | [`RFC 2347`](rfc2347/index.md) TFTP Option Extension | Supported | 4 | 0 | 0 | | [`RFC 2348`](rfc2348/index.md) TFTP Blocksize Option | No public row declared | 5 | 0 | 0 | | [`RFC 2349`](rfc2349/index.md) TFTP Timeout Interval and Transfer Size Options | No public row declared | 4 | 0 | 0 | | [`RFC 2385`](rfc2385/index.md) Protection of BGP Sessions via the TCP MD5 Signature Option | Supported on Linux; FreeBSD needs a `setkey(8)` SAD entry | 9 | 0 | 0 | | [`RFC 2473`](rfc2473/index.md) Generic Packet Tunneling in IPv6 Specification | No public row declared | 11 | 0 | 0 | | [`RFC 2516`](rfc2516/index.md) A Method for Transmitting PPP Over Ethernet (PPPoE) | Partial | 22 | 0 | 0 | | [`RFC 2545`](rfc2545/index.md) Use of BGP-4 Multiprotocol Extensions for IPv6 Inter-Domain Routing | Supported | 4 | 0 | 0 | | [`RFC 2661`](rfc2661/index.md) Layer Two Tunneling Protocol "L2TP" | Partial | 20 | 1 | 0 | | [`RFC 2759`](rfc2759/index.md) Microsoft PPP CHAP Extensions, Version 2 | Supported | 12 | 0 | 0 | | [`RFC 2782`](rfc2782/index.md) A DNS RR for specifying the location of services (DNS SRV) | No public row declared | 7 | 0 | 0 | | [`RFC 2784`](rfc2784/index.md) Generic Routing Encapsulation (GRE) | No public row declared | 11 | 0 | 0 | | [`RFC 2865`](rfc2865/index.md) Remote Authentication Dial In User Service (RADIUS) | Supported for subscriber access | 30 | 0 | 0 | | [`RFC 2866`](rfc2866/index.md) RADIUS Accounting | Supported for subscriber access | 16 | 0 | 0 | | [`RFC 2869`](rfc2869/index.md) RADIUS Extensions | Supported for subscriber access | 11 | 0 | 0 | | [`RFC 2890`](rfc2890/index.md) Key and Sequence Number Extensions to GRE | No public row declared | 7 | 0 | 0 | | [`RFC 2918`](rfc2918/index.md) Route Refresh Capability for BGP-4 | Supported | 6 | 0 | 0 | | [`RFC 2966`](rfc2966/index.md) Domain-wide Prefix Distribution with Two-Level IS-IS | Experimental | 4 | 0 | 0 | | [`RFC 3031`](rfc3031/index.md) Multiprotocol Label Switching Architecture | No public row declared | 7 | 0 | 0 | | [`RFC 3032`](rfc3032/index.md) MPLS Label Stack Encoding | Partial | 17 | 0 | 0 | | [`RFC 3101`](rfc3101/index.md) The OSPF Not-So-Stubby Area (NSSA) Option | Experimental | 17 | 0 | 0 | | [`RFC 3209`](rfc3209/index.md) RSVP-TE: Extensions to RSVP for LSP Tunnels | Experimental | 13 | 2 | 0 | | [`RFC 3579`](rfc3579/index.md) RADIUS (Remote Authentication Dial In User Service) Support For Extensible Authentication Protocol (EAP) | Partial | 31 | 14 | 0 | | [`RFC 3623`](rfc3623/index.md) Graceful OSPF Restart | Experimental | 13 | 2 | 0 | | [`RFC 3630`](rfc3630/index.md) Traffic Engineering (TE) Extensions to OSPF Version 2 | Experimental | 5 | 0 | 0 | | [`RFC 3748`](rfc3748/index.md) Extensible Authentication Protocol (EAP) | Supported in IPsec | 61 | 0 | 15 | | [`RFC 3765`](rfc3765/index.md) NOPEER Community for Border Gateway Protocol (BGP) Route Scope Control | Supported | 0 | 0 | 0 | | [`RFC 3768`](rfc3768/index.md) Virtual Router Redundancy Protocol (VRRP) | Experimental | 39 | 0 | 0 | | [`RFC 3786`](rfc3786/index.md) Extending the Number of Intermediate System to Intermediate System (IS-IS) Link State PDU (LSP) Fragments Beyond the 256 Limit | No public row declared | 4 | 0 | 0 | | [`RFC 3787`](rfc3787/index.md) Recommendations for Interoperable IP Networks using Intermediate System to Intermediate System (IS-IS) | Partial | 3 | 1 | 0 | | [`RFC 3948`](rfc3948/index.md) UDP Encapsulation of IPsec ESP Packets | Partial | 14 | 1 | 0 | | [`RFC 3954`](rfc3954/index.md) Cisco Systems NetFlow Services Export Version 9 | Experimental | 9 | 1 | 0 | | [`RFC 4035`](rfc4035/index.md) Protocol Modifications for the DNS Security Extensions | Partial | 108 | 3 | 0 | | [`RFC 4090`](rfc4090/index.md) Fast Reroute Extensions to RSVP-TE for LSP Tunnels | Experimental | 12 | 0 | 0 | | [`RFC 4213`](rfc4213/index.md) Basic Transition Mechanisms for IPv6 Hosts and Routers | No public row declared | 23 | 0 | 0 | | [`RFC 4271`](rfc4271/index.md) A Border Gateway Protocol 4 (BGP-4) | Partial | 101 | 15 | 0 | | [`RFC 4301`](rfc4301/index.md) Security Architecture for the Internet Protocol | Partial | 20 | 1 | 0 | | [`RFC 4302`](rfc4302/index.md) IP Authentication Header | Partial | 34 | 2 | 3 | | [`RFC 4303`](rfc4303/index.md) IP Encapsulating Security Payload (ESP) | Supported | 22 | 0 | 0 | | [`RFC 4360`](rfc4360/index.md) BGP Extended Communities Attribute | Supported | 6 | 0 | 0 | | [`RFC 4364`](rfc4364/index.md) BGP/MPLS IP Virtual Private Networks (VPNs) | Partial | 8 | 0 | 0 | | [`RFC 4456`](rfc4456/index.md) BGP Route Reflection: An Alternative to Full Mesh Internal BGP (IBGP) | Supported | 6 | 0 | 0 | | [`RFC 4486`](rfc4486/index.md) Subcodes for BGP Cease NOTIFICATION Message | Supported | 1 | 0 | 0 | | [`RFC 4552`](rfc4552/index.md) Authentication/Confidentiality for OSPFv3 | Partial | 27 | 5 | 0 | | [`RFC 4555`](rfc4555/index.md) IKEv2 Mobility and Multihoming Protocol (MOBIKE) | Unsupported | 6 | 0 | 0 | | [`RFC 4576`](rfc4576/index.md) Using a Link State Advertisement (LSA) Options Bit to Prevent Looping in BGP/MPLS IP Virtual Private Networks (VPNs) | No public row declared | 4 | 0 | 0 | | [`RFC 4577`](rfc4577/index.md) OSPF as the Provider/Customer Edge Protocol for BGP/MPLS IP Virtual Private Networks (VPNs) | Not supported | 36 | 20 | 0 | | [`RFC 4578`](rfc4578/index.md) Dynamic Host Configuration Protocol (DHCP) Options for the Intel Preboot eXecution Environment (PXE) | Supported | 5 | 0 | 0 | | [`RFC 4659`](rfc4659/index.md) BGP-MPLS IP Virtual Private Network (VPN) Extension for IPv6 VPN | Partial | 16 | 3 | 0 | | [`RFC 4684`](rfc4684/index.md) Constrained Route Distribution for Border Gateway Protocol/MultiProtocol Label Switching (BGP/MPLS) Internet Protocol (IP) Virtual Private Networks (VPNs) | Partial | 4 | 4 | 0 | | [`RFC 4724`](rfc4724/index.md) Graceful Restart Mechanism for BGP | Partial | 26 | 8 | 0 | | [`RFC 4760`](rfc4760/index.md) Multiprotocol Extensions for BGP-4 | Supported | 6 | 0 | 0 | | [`RFC 4761`](rfc4761/index.md) Virtual Private LAN Service (VPLS) Using BGP for Auto-Discovery and Signaling | Partial | 18 | 0 | 0 | | [`RFC 4862`](rfc4862/index.md) IPv6 Stateless Address Autoconfiguration | No public row declared | 16 | 0 | 0 | | [`RFC 5036`](rfc5036/index.md) LDP Specification | Experimental | 14 | 7 | 0 | | [`RFC 5072`](rfc5072/index.md) IP Version 6 over PPP | Partial | 16 | 3 | 0 | | [`RFC 5082`](rfc5082/index.md) The Generalized TTL Security Mechanism (GTSM) | Supported on Linux | 4 | 0 | 1 | | [`RFC 5176`](rfc5176/index.md) Dynamic Authorization Extensions to Remote Authentication Dial In User Service (RADIUS) | Supported for subscriber access | 22 | 0 | 0 | | [`RFC 5187`](rfc5187/index.md) OSPFv3 Graceful Restart | Experimental | 4 | 0 | 0 | | [`RFC 5216`](rfc5216/index.md) The EAP-TLS Authentication Protocol | Partial | 21 | 1 | 0 | | [`RFC 5250`](rfc5250/index.md) The OSPF Opaque LSA Option | Experimental | 9 | 0 | 0 | | [`RFC 5282`](rfc5282/index.md) Using Authenticated Encryption Algorithms with the Encrypted Payload of the Internet Key Exchange version 2 (IKEv2) Protocol | Supported | 19 | 0 | 4 | | [`RFC 5286`](rfc5286/index.md) Basic Specification for IP Fast Reroute: Loop-Free Alternates | Experimental | 6 | 2 | 0 | | [`RFC 5301`](rfc5301/index.md) Dynamic Hostname Exchange Mechanism for IS-IS | Supported | 7 | 0 | 0 | | [`RFC 5303`](rfc5303/index.md) Three-Way Handshake for IS-IS Point-to-Point Adjacencies | Experimental | 18 | 7 | 0 | | [`RFC 5304`](rfc5304/index.md) IS-IS Cryptographic Authentication | Experimental | 9 | 0 | 0 | | [`RFC 5305`](rfc5305/index.md) IS-IS Extensions for Traffic Engineering | Experimental | 8 | 0 | 0 | | [`RFC 5308`](rfc5308/index.md) Routing IPv6 with IS-IS | Experimental | 7 | 0 | 0 | | [`RFC 5310`](rfc5310/index.md) IS-IS Generic Cryptographic Authentication | Experimental | 9 | 0 | 0 | | [`RFC 5340`](rfc5340/index.md) OSPF for IPv6 | Partial | 23 | 5 | 0 | | [`RFC 5392`](rfc5392/index.md) OSPF Extensions in Support of Inter-Autonomous System (AS) MPLS and GMPLS Traffic Engineering | Experimental | 12 | 4 | 0 | | [`RFC 5443`](rfc5443/index.md) LDP IGP Synchronization | Experimental | 8 | 1 | 0 | | [`RFC 5492`](rfc5492/index.md) Capabilities Advertisement with BGP-4 | Supported | 9 | 0 | 0 | | [`RFC 5549`](rfc5549/index.md) Advertising IPv4 Network Layer Reachability Information with an IPv6 Next Hop | Supported | 6 | 0 | 0 | | [`RFC 5561`](rfc5561/index.md) LDP Capabilities | No public row declared | 5 | 0 | 0 | | [`RFC 5575`](rfc5575/index.md) Dissemination of Flow Specification Rules | Partial | 12 | 4 | 0 | | [`RFC 5701`](rfc5701/index.md) IPv6 Address Specific BGP Extended Community Attribute | Partial | 4 | 1 | 0 | | [`RFC 5709`](rfc5709/index.md) OSPFv2 HMAC-SHA Cryptographic Authentication | Experimental | 15 | 0 | 0 | | [`RFC 5798`](rfc5798/index.md) Virtual Router Redundancy Protocol (VRRP) Version 3 for IPv4 and IPv6 | Partial | 55 | 0 | 15 | | [`RFC 5838`](rfc5838/index.md) Support of Address Families in OSPFv3 | Experimental | 16 | 8 | 0 | | [`RFC 5880`](rfc5880/index.md) Bidirectional Forwarding Detection (BFD) | Partial | 96 | 14 | 0 | | [`RFC 5881`](rfc5881/index.md) Bidirectional Forwarding Detection (BFD) for IPv4 and IPv6 (Single Hop) | Partial | 23 | 6 | 0 | | [`RFC 5882`](rfc5882/index.md) Generic Application of Bidirectional Forwarding Detection (BFD) | Partial | 3 | 0 | 0 | | [`RFC 5883`](rfc5883/index.md) Bidirectional Forwarding Detection (BFD) for Multihop Paths | Partial | 8 | 2 | 0 | | [`RFC 6071`](rfc6071/index.md) IP Security (IPsec) and Internet Key Exchange (IKE) Document Roadmap | No public row declared | 8 | 0 | 0 | | [`RFC 6138`](rfc6138/index.md) LDP IGP Synchronization for Broadcast Networks | No public row declared | 2 | 0 | 0 | | [`RFC 6286`](rfc6286/index.md) Autonomous-System-Wide Unique BGP Identifier for BGP-4 | Supported | 4 | 0 | 0 | | [`RFC 6396`](rfc6396/index.md) Multi-Threaded Routing Toolkit (MRT) Routing Information Export Format | Supported | 13 | 1 | 0 | | [`RFC 6397`](rfc6397/index.md) Multi-Threaded Routing Toolkit (MRT) Border Gateway Protocol (BGP) Routing Information Export Format with Geo-Location Extensions | No public row declared | 4 | 0 | 0 | | [`RFC 6482`](rfc6482/index.md) A Profile for Route Origin Authorizations (ROAs) | No public row declared | 9 | 0 | 0 | | [`RFC 6549`](rfc6549/index.md) OSPFv2 Multi-Instance Extensions | No public row declared | 1 | 0 | 0 | | [`RFC 6608`](rfc6608/index.md) Subcodes for BGP Finite State Machine Error | Partial | 3 | 3 | 0 | | [`RFC 6793`](rfc6793/index.md) BGP Support for Four-Octet Autonomous System (AS) Number Space | Partial | 30 | 1 | 0 | | [`RFC 6810`](rfc6810/index.md) The Resource Public Key Infrastructure (RPKI) to Router Protocol | Partial | 39 | 4 | 0 | | [`RFC 6811`](rfc6811/index.md) BGP Prefix Origin Validation | Supported | 5 | 0 | 0 | | [`RFC 6996`](rfc6996/index.md) Autonomous System (AS) Reservation for Private Use | No public row declared | 1 | 0 | 0 | | [`RFC 7011`](rfc7011/index.md) Specification of the IP Flow Information Export (IPFIX) Protocol for the Exchange of Flow Information | Experimental | 19 | 1 | 0 | | [`RFC 7012`](rfc7012/index.md) Information Model for IP Flow Information Export (IPFIX) | No public row declared | 11 | 0 | 0 | | [`RFC 7166`](rfc7166/index.md) Supporting Authentication Trailer for OSPFv3 | Unsupported | 17 | 17 | 0 | | [`RFC 7296`](rfc7296/index.md) Internet Key Exchange Protocol Version 2 (IKEv2) | Partial | 222 | 0 | 0 | | [`RFC 7311`](rfc7311/index.md) The Accumulated IGP Metric Attribute for BGP | Partial | 5 | 1 | 0 | | [`RFC 7313`](rfc7313/index.md) Enhanced Route Refresh Capability for BGP-4 | Supported | 10 | 4 | 0 | | [`RFC 7427`](rfc7427/index.md) Signature Authentication in the Internet Key Exchange Version 2 (IKEv2) | No public row declared | 3 | 0 | 0 | | [`RFC 7432`](rfc7432/index.md) BGP MPLS-Based Ethernet VPN | Partial | 82 | 15 | 0 | | [`RFC 7440`](rfc7440/index.md) TFTP Windowsize Option | No public row declared | 9 | 0 | 0 | | [`RFC 7474`](rfc7474/index.md) Security Extensions for OSPFv2 when Using Manual Key Management | Experimental | 10 | 0 | 0 | | [`RFC 7534`](rfc7534/index.md) AS112 Nameserver Operations | Supported | 3 | 0 | 0 | | [`RFC 7535`](rfc7535/index.md) AS112 Redirection Using DNAME | Partial | 1 | 0 | 0 | | [`RFC 7606`](rfc7606/index.md) Revised Error Handling for BGP UPDATE Messages | Partial | 52 | 1 | 0 | | [`RFC 7607`](rfc7607/index.md) Codification of AS 0 Processing | Supported | 5 | 0 | 0 | | [`RFC 7611`](rfc7611/index.md) BGP ACCEPT_OWN Community Attribute | No public row declared | 5 | 0 | 0 | | [`RFC 7684`](rfc7684/index.md) OSPFv2 Prefix/Link Attribute Advertisement | Experimental | 8 | 0 | 0 | | [`RFC 7752`](rfc7752/index.md) North-Bound Distribution of Link-State and Traffic Engineering (TE) Information Using BGP | Partial | 26 | 4 | 0 | | [`RFC 7770`](rfc7770/index.md) Extensions to OSPF for Advertising Optional Router Capabilities | Experimental | 11 | 0 | 0 | | [`RFC 7854`](rfc7854/index.md) BGP Monitoring Protocol (BMP) | Partial | 13 | 0 | 0 | | [`RFC 7858`](rfc7858/index.md) Specification for DNS over Transport Layer Security (TLS) | Partial | 19 | 1 | 0 | | [`RFC 7871`](rfc7871/index.md) Client Subnet in DNS Queries | Partial | 38 | 6 | 0 | | [`RFC 7911`](rfc7911/index.md) Advertisement of Multiple Paths in BGP | Supported | 9 | 0 | 0 | | [`RFC 792`](rfc792/index.md) Internet Control Message Protocol | No public row declared | 6 | 0 | 0 | | [`RFC 7947`](rfc7947/index.md) Internet Exchange BGP Route Server | Supported | 3 | 0 | 0 | | [`RFC 7950`](rfc7950/index.md) The YANG 1.1 Data Modeling Language | No public row declared | 9 | 0 | 0 | | [`RFC 7999`](rfc7999/index.md) BLACKHOLE Community | Partial | 4 | 0 | 0 | | [`RFC 8050`](rfc8050/index.md) Multi-Threaded Routing Toolkit (MRT) Routing Information Export Format with BGP Additional Path Extensions | Partial | 6 | 1 | 0 | | [`RFC 8092`](rfc8092/index.md) BGP Large Communities Attribute | Supported | 7 | 0 | 0 | | [`RFC 8097`](rfc8097/index.md) BGP Prefix Origin Validation State Extended Community | No public row declared | 5 | 0 | 0 | | [`RFC 8203`](rfc8203/index.md) BGP Administrative Shutdown Communication | Supported | 5 | 0 | 0 | | [`RFC 8210`](rfc8210/index.md) The Resource Public Key Infrastructure (RPKI) to Router Protocol, Version 1 | Partial | 56 | 12 | 0 | | [`RFC 8277`](rfc8277/index.md) Using BGP to Bind MPLS Labels to Address Prefixes | Partial | 34 | 10 | 0 | | [`RFC 8414`](rfc8414/index.md) OAuth 2.0 Authorization Server Metadata | Partial | 7 | 1 | 0 | | [`RFC 8484`](rfc8484/index.md) DNS Queries over HTTPS (DoH) | Partial | 16 | 1 | 0 | | [`RFC 8571`](rfc8571/index.md) BGP - Link State (BGP-LS) Advertisement of IGP Traffic Engineering Performance Metric Extensions | No public row declared | 4 | 0 | 0 | | [`RFC 8654`](rfc8654/index.md) Extended Message Support for BGP | Supported | 12 | 0 | 0 | | [`RFC 8665`](rfc8665/index.md) OSPF Extensions for Segment Routing | Partial | 47 | 14 | 0 | | [`RFC 8666`](rfc8666/index.md) OSPFv3 Extensions for Segment Routing | Partial | 31 | 3 | 0 | | [`RFC 8669`](rfc8669/index.md) Segment Routing Prefix Segment Identifier Extensions for BGP | Partial | 25 | 10 | 0 | | [`RFC 8671`](rfc8671/index.md) Support for Adj-RIB-Out in the BGP Monitoring Protocol (BMP) | Supported within BMP sender scope | 10 | 0 | 0 | | [`RFC 8707`](rfc8707/index.md) Resource Indicators for OAuth 2.0 | No public row declared | 7 | 0 | 0 | | [`RFC 8907`](rfc8907/index.md) The Terminal Access Controller Access-Control System Plus (TACACS+) Protocol | Partial | 12 | 2 | 0 | | [`RFC 8950`](rfc8950/index.md) Advertising IPv4 Network Layer Reachability Information (NLRI) with an IPv6 Next Hop | Supported | 6 | 0 | 0 | | [`RFC 8955`](rfc8955/index.md) Dissemination of Flow Specification Rules | Partial | 22 | 5 | 0 | | [`RFC 8956`](rfc8956/index.md) Dissemination of Flow Specification Rules for IPv6 | Partial | 9 | 7 | 0 | | [`RFC 9003`](rfc9003/index.md) Extended BGP Administrative Shutdown Communication | Supported | 4 | 0 | 0 | | [`RFC 9012`](rfc9012/index.md) The BGP Tunnel Encapsulation Attribute | Partial | 75 | 51 | 0 | | [`RFC 905`](rfc905/index.md) ISO Transport Protocol Specification (ISO DP 8073) | No public row declared | 9 | 0 | 0 | | [`RFC 9069`](rfc9069/index.md) Support for Local RIB in the BGP Monitoring Protocol (BMP) | Supported | 15 | 0 | 0 | | [`RFC 9072`](rfc9072/index.md) Extended Optional Parameters Length for BGP OPEN Message | Partial | 9 | 4 | 0 | | [`RFC 9085`](rfc9085/index.md) Border Gateway Protocol - Link State (BGP-LS) Extensions for Segment Routing | Partial | 12 | 9 | 0 | | [`RFC 9086`](rfc9086/index.md) Border Gateway Protocol - Link State (BGP-LS) Extensions for Segment Routing BGP Egress Peer Engineering | Partial | 12 | 10 | 0 | | [`RFC 9136`](rfc9136/index.md) IP Prefix Advertisement in Ethernet VPN (EVPN) | Partial | 14 | 5 | 0 | | [`RFC 9234`](rfc9234/index.md) Route Leak Prevention and Detection Using Roles in UPDATE and OPEN Messages | Supported | 19 | 0 | 0 | | [`RFC 9252`](rfc9252/index.md) BGP Overlay Services Based on Segment Routing over IPv6 (SRv6) | Partial | 19 | 8 | 0 | | [`RFC 9256`](rfc9256/index.md) Segment Routing Policy Architecture | Partial | 22 | 6 | 0 | | [`RFC 9319`](rfc9319/index.md) The Use of maxLength in the Resource Public Key Infrastructure (RPKI) | No public row declared | 4 | 0 | 0 | | [`RFC 9494`](rfc9494/index.md) Long-Lived Graceful Restart for BGP | Partial | 25 | 5 | 0 | | [`RFC 9514`](rfc9514/index.md) Border Gateway Protocol - Link State (BGP-LS) Extensions for Segment Routing over IPv6 (SRv6) | Partial | 13 | 13 | 0 | | [`RFC 9552`](rfc9552/index.md) Distribution of Link-State and Traffic Engineering Information Using BGP | Partial | 48 | 1 | 0 | | [`RFC 9568`](rfc9568/index.md) Virtual Router Redundancy Protocol (VRRP) Version 3 for IPv4 and IPv6 | Partial | 59 | 2 | 0 | | [`RFC 9582`](rfc9582/index.md) The Resource Public Key Infrastructure (RPKI) to Router Protocol, Version 2 | Unsupported | 10 | 0 | 0 | | [`RFC 9687`](rfc9687/index.md) Border Gateway Protocol 4 (BGP-4) Send Hold Timer | Supported | 13 | 0 | 0 | | [`RFC 9728`](rfc9728/index.md) OAuth 2.0 Protected Resource Metadata | No public row declared | 7 | 0 | 0 | | [`RFC 9830`](rfc9830/index.md) BGP Extensions for the Advertisement of Segment Routing (SR) Policies | Partial | 96 | 20 | 0 | | [`SFLOW-V5`](sflow-v5/index.md) sFlow: A Method for Monitoring Traffic in Switched and Routed Networks | Experimental | 16 | 3 | 0 | ## Summaries that are not enrolled - `backlog`: the requirements have not been extracted from the document yet; this is work owed rather than a decision - `blocked`: something outside the summary stops the extraction, and it is named in the reason - `non-normative`: the document imposes no MUST-level obligation on an implementation, so there is nothing to gate - `out-of-scope`: the requirements ARE extracted and the owner decided not to offer the feature for now, so the absence is a scope decision rather than a conformance gap | RFC | Disposition | Reason | |---|---|---| | [`DRAFT-IETF-SIDROPS-8210BIS`](draft-ietf-sidrops-8210bis/index.md) The Resource Public Key Infrastructure (RPKI) to Router Protocol, Version 2 | backlog | The RPKI to Router Protocol, Version 2. Split out of rfc9582 on 2026-09-01 because the obligations below are stated by this draft and not by RFC 9582, which profiles the ROA certificate. It is not enrolled because its obligations are not yet proven and the draft is still in the RFC Editor queue, so a version bump can restate them. | | [`RFC 1035`](rfc1035/index.md) Domain Names - Implementation and Specification | backlog | Domain Names: Implementation and Specification. Re-authored 2026-07-30 and it now declares 27 MUST-level obligations read from the indicative prose of a 1987 document (0 capitalised keywords, 23 lowercase must), so this is no longer an empty checklist. It is not enrolled because the obligations are not all proven and the unproven ones need an owner ruling, not an implementer's annotation. The obligation with no code path in Ze 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. The 512-octet UDP bound and the TC bit ARE enforced -- send calls Msg.Truncate(udpReplyLimit(r)) for a datagram reply in internal/core/dnsserver/handler.go, and udpReplyLimit holds the Section 2.3.4 floor while letting an RFC 6891 Section 6.2.3 OPT record raise it. An unsupported inverse query DOES draw Not Implemented: Authoritative branches on the opcode before any zone lookup, in the same file. It was the one obligation the 73-section walk found OUTSIDE the summary's declared scope and added to it. The response TTL is deliberately not raised to the SOA MINIMUM -- RFC 2308 Section 4 withdrew that rule, hdr in internal/plugins/geodns/server.go applies no floor, and TestRFC2308_NoZoneWideTTLFloor holds the decision. About 6 requirements admit only a positive polarity because miekg/dns owns the wire codec and no Ze-side change can break them. Escalated for scoping per OR-1b. | | [`RFC 4762`](rfc4762/index.md) Virtual Private LAN Service (VPLS) Using Label Distribution Protocol (LDP) Signaling | blocked | VPLS using LDP signaling. Written 2026-09-01 so the public row this RFC has always carried is declared by a summary rather than authored on a page nobody could tie back to a document. It is not enrolled because there is no source text at rfc/full/rfc4762.txt: fetch https://www.rfc-editor.org/rfc/rfc4762.txt, then extract. Ze speaks the BGP-signalled VPLS of RFC 4761 and not this one, so the row claims Unsupported and no obligation here is gated. | | [`RFC 5065`](rfc5065/index.md) Autonomous System Confederations for BGP | blocked | BGP confederations. Written 2026-09-01 for the same reason as the other six rows that had no summary: the public claim now lives in the document it is about. It is not enrolled because there is no source text at rfc/full/rfc5065.txt: fetch https://www.rfc-editor.org/rfc/rfc5065.txt, then extract. The row claims Unsupported, so no obligation here is gated. | | [`RFC 5120`](rfc5120/index.md) M-ISIS: Multi Topology (MT) Routing in Intermediate System to Intermediate Systems (IS-ISs) | blocked | IS-IS multi-topology routing. Written 2026-09-01 so the public row is declared by a summary. It is not enrolled because there is no source text at rfc/full/rfc5120.txt: fetch https://www.rfc-editor.org/rfc/rfc5120.txt, then extract. Ze runs IS-IS single-topology dual-stack only and the row claims Unsupported, so no obligation here is gated. | | [`RFC 5925`](rfc5925/index.md) The TCP Authentication Option | blocked | The TCP Authentication Option. Written 2026-09-01 so the public row is declared by a summary. It is not enrolled because there is no source text at rfc/full/rfc5925.txt: fetch https://www.rfc-editor.org/rfc/rfc5925.txt, then extract. Ze implements the RFC 2385 TCP MD5 signature option and not TCP-AO, and the row claims Unsupported, so no obligation here is gated. | | [`RFC 6514`](rfc6514/index.md) BGP Encodings and Procedures for Multicast in MPLS/BGP IP VPNs | out-of-scope | BGP Encodings and Procedures for Multicast in MPLS/BGP IP VPNs. OUT OF SCOPE by owner decision, 2026-09-01, marked for future development. The extraction is COMPLETE: the source text is at rfc/full/rfc6514.txt and this summary declares all 201 requirements, 133 of them MUST-level, so a later decision to build MVPN starts from the obligations rather than from nothing. What Ze has today is NLRI plumbing and nothing the RFC is about: the Section 4 route-type split (splitMVPN, internal/core/bgp/nlri/nlrisplit/mvpn.go), an NLRI codec, a config route parser for three of the seven route types, and opaque Adj-RIB-In storage. Exactly one MUST-level requirement is met and it is met vacuously -- RFC6514-9.1.1-10 says the Leaf Information Required flag "MUST be set to zero and MUST be ignored on receipt", and Ze ignores it by never parsing a PMSI byte, because knownAttrParsers leaves attribute code 22 nil. Absent entirely: the PMSI Tunnel attribute, the PE Distinguisher Labels attribute (code 27 has no constant), the Source AS and VRF Route Import extended communities, auto-discovery, the C-multicast route exchange, S-PMSI routes, inter-AS and ASBR operation, upstream multicast hop selection (SAFI 129 is unregistered), and every protocol the document leans on -- PIM, mLDP, RSVP-TE P2MP and MSDP. No {gap} annotation is written for any of it: a gap is an ISSUE and this is a DECISION (ai/rules/rfc-compliance.md), and 132 gap rows would record a feature nobody chose to build as 132 conformance failures. | | [`RFC 6987`](rfc6987/index.md) OSPF Stub Router Advertisement | backlog | OSPF Stub Router Advertisement. Its five obligations sit behind bare [LSA]-style category tags, which parse_checklist_line reads as prose rather than as requirements, so the summary captures zero at any level while the source is normative. Declared backlog and not non-normative on purpose: calling it non-normative would launder five unparsed obligations into a decision, which is exactly the shape D5 exists to expose. | | [`RFC 7454`](rfc7454/index.md) BGP Operations and Security | non-normative | BGP Operations and Security, published as BCP 194 with IETF category Best Current Practice. A capitalised MUST / MUST NOT / SHALL / SHALL NOT scan over rfc/full/rfc7454.txt hits four keywords and all four sit inside the RFC 2119 key-words sentence of section 1.1, which tells a reader how to read the other sentences and states no obligation of its own. Outside that sentence the document uses no MUST-level keyword except one NOT REQUIRED in section 5.1, and that phrase negates a requirement instead of stating one. The summary written 2026-08-08 therefore captures 64 requirements and gates none of them: 42 SHOULD, 12 SHOULD NOT, 8 RECOMMENDED, 1 MAY, and the single NOT REQUIRED of section 5.1 recorded at the OPTIONAL level. The document also addresses network administrators rather than protocol implementers, and section 12 states that it "does not aim to describe existing BGP implementations". A zero-MUST BCP can reach the public ledger two ways, as a non-normative disposition or as a manual-walk extraction sign-off with a register-reason, and that choice is a ledger judgement for the owner. Thomas made it on 2026-08-12: non-normative, on the grounds that the scan recorded above finds no MUST-level keyword outside the key-words sentence, so backlog overstated a debt this text does not create. The choice is no longer open. | | [`RFC 7627`](rfc7627/index.md) Transport Layer Security (TLS) Session Hash and Extended Master Secret Extension | backlog | TLS Session Hash and Extended Master Secret Extension. Summary written 2026-08-24 against rfc/full/rfc7627.txt, which was fetched the same day. It declares 27 requirements: 17 MUST-level over 5 sections, 9 at SHOULD level (8 SHOULD / SHOULD NOT plus one NOT RECOMMENDED) and 1 MAY. It is NOT enrolled because not one of the 17 has a producer inside Ze, so neither route to enrolment is an implementer's to take: there is no Ze function a positive and a negative test could tag, and annotating the remainder is a conformance judgement ai/rules/rfc-compliance.md reserves to the owner. WHERE THE OBLIGATIONS ARE DISCHARGED. Go's crypto/tls owns the extension end to end. Its client sets extendedMasterSecret on every ClientHello it builds (makeClientHello, crypto/tls/handshake_client.go), clearing it only for an ECH inner hello; its server copies the client's bit into the ServerHello (serverHandshakeState.processClientHello, crypto/tls/handshake_server.go); the negotiated result lands on Conn.extMasterSecret; and the Section 5.3 abbreviated-handshake mismatch rules are enforced in clientHandshakeState.processServerHello and serverHandshakeState.checkForResumption. Ze holds no ClientHello or ServerHello encoder and no master-secret derivation, so RFC7627-5.1-1, 5.2-1, 5.2-2, 5.2-4, 5.2-6, 5.3-3, 5.3-4, 5.3-6, 5.3-8, 5.3-9, 5.3-10 and 6.4-2 have no Ze-side producer at all. The Section 5.4 downgrade containment (RFC7627-5.4-1 to 5.4-4 and 5.4-6) is enforced below Ze too: Conn.connectionStateLocked selects noEKMBecauseNoEMS on `c.vers != VersionTLS13 && !c.extMasterSecret` (Conn.connectionStateLocked, crypto/tls/conn.go), which is the MUST NOT of RFC7627-5.4-1 executed by the library rather than by its caller. Ze sets no tls.Config.Renegotiation and computes no tls-unique -- the sha256(TLSUnique) fallback was removed from tlsMethod.deriveMSK -- and the authenticator now issues a TLS 1.3 session ticket per RFC 9190 Section 2.1.2, whose resumption is a TLS 1.3 PSK exchange and not the RFC 7627 abbreviated handshake this document governs (newTLSMethod, internal/core/eap/eap_tls.go). WHAT ZE ACTUALLY PRODUCES is the refusal path, and it is two functions in internal/core/eap/eap_tls.go: exportEAPTLSMSK returns the crypto/tls error rather than a zero MSK, and eapTLS12ExportRefused names the peer, the negotiated version, RFC 7627 and the operator's three remedies (move the peer to TLS 1.3 per RFC 9190, add RFC 7627 to its TLS 1.2 stack, or configure another EAP method). RFC 5216 Section 2.3 defines the EAP-TLS MSK as that export, so a TLS 1.2 peer whose session carries no extended master secret cannot authenticate. strongSwan 5.9.14 reaches that state by DEFAULT rather than by limitation: charon ships version_max = 1.2 and negotiates no RFC 7627, while charon.tls.version_max = 1.3 on the same build reaches an established SA (test/interop-ipsec/scenarios/eap-tls13/strongswan.conf, and the failing counterpart is test/interop-ipsec/scenarios/eap-tls). TWO FACTS AN OWNER RULING HAS TO ABSORB. First, RFC 7627 IS OBSOLETE. RFC 9846 (The Transport Layer Security (TLS) Protocol Version 1.3, July 2026) obsoletes RFC 5077, 5246, 6961, 7627, 8422 and 8446; its Appendix D renames the extension to Extended Main Secret and the code point to extended_main_secret, and leaves the Section 4 PRF label unchanged for compatibility; its Appendix E states the only obligation it adds about the extension, a SHOULD that an implementation supporting both TLS 1.3 and earlier versions indicate the use of the Extended Main Secret extension in its APIs whenever TLS 1.3 is used. ai/rules/rfc-compliance.md says the lineage that matters runs FORWARD, so the document that states what Ze owes today is RFC 9846, and rfc/full/rfc9846.txt is not in this repository. Enrolling rfc7627 would gate a superseded text. Second, the tlsunsafeekm override is GONE from this checkout, as of the toolchain bump on 2026-08-25. go.mod pins `toolchain go1.27.0`, whose internal/godebugs/table.go carries `{Name: "tlsunsafeekm", Removed: 27, Old: one}`, so a process that sets it to its old value raises a fatal error before main() rather than getting the unsafe export. crypto/tls agrees at the other end: noEKMBecauseNoEMS (crypto/tls/prf.go) now returns the bare sentence with no override clause at all. So the claim cmd/ze/main.go and the RFC 5216 row of docs/features/rfc-status.md both make, that no override remains, is true of what this tree builds; it was false while the toolchain was 1.26. Escalated for a scoping ruling, per the route rfc1035 and rfc9190 took. | | [`RFC 8195`](rfc8195/index.md) Use of BGP Large Communities | non-normative | Use of BGP Large Communities, IETF category Informational. A capitalised MUST / MUST NOT / SHALL / SHALL NOT / REQUIRED scan over rfc/full/rfc8195.txt hits zero keywords, and the document invokes neither RFC 2119 nor RFC 8174 nor BCP 14 anywhere, so it declares no key-words machinery for a reader to read its prose by. Its own abstract calls it examples and inspiration for operator application of BGP Large Communities. The summary written against it captures 3 requirements and gates none: RFC8195-2-1, RFC8195-2.2-1 and RFC8195-4.3.3-1, all at SHOULD. Section 3.2 and Section 4.1.1 both say an AS could assign a function number, which states a convention rather than an obligation. Thomas ruled on 2026-08-12 that this is non-normative rather than backlog. | | [`RFC 8326`](rfc8326/index.md) Graceful BGP Session Shutdown | blocked | Graceful BGP Session Shutdown. No source text at rfc/full/rfc8326.txt or rfc/drafts/rfc8326.txt, so check_enrolment refuses the enrolment. Fetch https://www.rfc-editor.org/rfc/rfc8326.txt, then extract. | | [`RFC 8362`](rfc8362/index.md) OSPFv3 Link State Advertisement (LSA) Extensibility | out-of-scope | OSPFv3 Link State Advertisement (LSA) Extensibility. OUT OF SCOPE as a document by owner decision, 2026-09-01, marked for future development. The extraction is COMPLETE: the source text is at rfc/full/rfc8362.txt and this summary declares all 50 requirements, 38 of them MUST-level. What Ze has is a by-product of RFC 8666 Segment Routing rather than the framework this document defines: three of the seven Extended LSA types are originated, and only when segment routing is enabled (v6OriginateSR, internal/plugins/ospf/sr_origination_v6.go); receipt decoding is reached from one place and reads Prefix-SIDs alone (v6ReceivedPrefixSIDs, sr_reception_v6.go); no SPF calculation reads an Extended LSA, because the base Router-LSA remains the sole SPF vertex; and there is no RFC 8362 configuration surface and none of its Appendix A or B migration machinery. Nine MUST-level requirements are genuinely unmet and 20 more are met only because Ze performs no action on their subject. The most serious is RFC8362-2-1: the seven LS type constants carry the U-bit clear where Section 2 requires it set, which is recorded as a defect in plan/journal/declared-format-contradicts-payload.md and is a fix rather than a scope question. No {gap} annotation is written here, because the scope decision covers the document and the U-bit defect is tracked where a fix is owed. | | [`RFC 8538`](rfc8538/index.md) Notification Message Support for BGP Graceful Restart | blocked | Notification message support for BGP graceful restart. Written 2026-09-01 so the public row is declared by a summary. It is not enrolled because there is no source text at rfc/full/rfc8538.txt: fetch https://www.rfc-editor.org/rfc/rfc8538.txt, then extract. The row claims Unsupported, so no obligation here is gated. | | [`RFC 9129`](rfc9129/index.md) YANG Data Model for the OSPF Protocol | blocked | YANG Data Model for the OSPF Protocol. No source text at rfc/full/rfc9129.txt or rfc/drafts/rfc9129.txt, so check_enrolment refuses the enrolment. Fetch https://www.rfc-editor.org/rfc/rfc9129.txt, then extract. | | [`RFC 9190`](rfc9190/index.md) EAP-TLS 1.3: Using the Extensible Authentication Protocol with TLS 1.3 | backlog | EAP-TLS 1.3: Using EAP with TLS 1.3. Summary written 2026-08-01, extraction sign-off walked 2026-09-08 (rfc/extraction/rfc9190.json, 52 sites in 36 sections, register prose, 48 mapped and 4 excluded). It declares 52 MUST-level obligations over 20 sections. It is NOT enrolled because 33 of them are not proven in both polarities: 26 carry no tagged test at all and 7 carry a positive only. Measured 2026-09-08 by counting `RFC requirement: RFC9190-<id> <polarity>` tags under internal/, test/, cmd/ and pkg/ against the gated rows of this checklist. Enrolment demands every gated MUST proven in both polarities or annotated, and annotating is the conformance judgement ai/rules/rfc-compliance.md reserves to the owner; the owner ruling of 2026-08-01 chose to implement the features and enrol with everything proven, so the annotation route is closed. WHAT IS BUILT AND PROVEN, all in both polarities. Section 2.3 key derivation: exportEAPTLSMSK (internal/core/eap/eap_tls.go) selects the exporter label EXPORTER_EAP_TLS_Key_Material, the EAP Type octet as context and the 128-octet length whenever the negotiated version is TLS 1.3, and test/interop-ipsec/scenarios/eap-tls13 exercises that path against strongSwan. Section 2.5 protected success indication, both roles: tlsMethod.indicateSuccess (same file) writes the encrypted TLS record carrying application data 0x00 in the round that completes the handshake, and PeerSession.requireSuccessIndication (internal/core/eap/peer_indication.go) refuses the EAP-Success without it; scenario responder-eap-tls13 proves the server half against strongSwan, which logs `missing protected success indication for EAP-TLS with TLS 1.3` when the write is reverted. The peer half is STRICTER than the published RFC, which addresses Section 2.5 only to the server, so no requirement id covers it and its tests carry no RFC requirement tag. Section 2.1.2 and 2.1.3 resumption, both roles: Resumption (internal/core/eap/resumption.go) owns the ticket keys and the client cache per peering, and serverChainCheck.rebuildResumedChains (internal/core/eap/peer_chain.go) rebuilds the chain crypto/tls skips on a resumed handshake so the Section 5.4 check still runs. ALL FIVE Section 5.4 requirements are built as of 2026-09-08: checkChainRevocation (internal/core/eap/revocation.go) walks every certificate on each chain except the trust anchor on both roles (5.4-1), newTLSMethod staples the operator's ocsp-response (5.4-2), checkStapledChainStatus (internal/core/eap/ocsp.go) refuses a CertificateEntry with no valid status while certificate-status-request is set (5.4-3), and startServerCertRecheck (internal/component/ike/engine/postauth_revocation.go) re-checks the chain over https once the Child SA is up, refusing an http responder URL before any connection opens (5.4-4 and 5.4-5). Section 5.10-1 is proven by sixteen tagged units in internal/core/eap/rfc9190_attack_mitigation_test.go over the RFC 7457 Section 2 attacks. THE TWO OBLIGATIONS THAT WERE UNMET IN CODE ARE MET AND PROVEN AS OF 2026-09-08. RFC9190-2.1.9-1, the MUST NOT to set the L bit in an unfragmented message: tlsFragmenter.nextFragment (internal/core/eap/eap_tls.go) is the one producer of outbound EAP-TLS TypeData on both roles, and it now declares a length only where the first fragment is not also the last, so a message that fits in one fragment leaves as a bare flags octet with its TLS data at offset 1. RFC9190-2.1.9-2, the receive half of the same sentence, is proven beside it over tlsFragmenter.reassemble, which takes an unfragmented message with the L bit and without it. RFC9190-1-1, the Section 1 MUST to limit the maximum TLS version to 1.3 unless the administrator enables a later one: newTLSMethod (eap_tls.go) and PeerSession.tlsClientConfig (internal/core/eap/peer.go) each set MaxVersion to tls.VersionTLS13, and ze exposes no leaf that raises that ceiling, so the exception has nothing to switch on. RFC9190-1-1 was added to this checklist by the 2026-09-08 extraction walk, which found the summary had missed it. | | [`RFC 9384`](rfc9384/index.md) A BGP Cease NOTIFICATION Subcode for Bidirectional Forwarding Detection (BFD) | non-normative | A BGP Cease NOTIFICATION Subcode for Bidirectional Forwarding Detection (BFD), IETF category Standards Track. The document carries the RFC 2119 / RFC 8174 key-words paragraph at Section 2, and a capitalised MUST / MUST NOT / SHALL / SHALL NOT / REQUIRED scan over rfc/full/rfc9384.txt hits those five words on one line only, line 101, which is the key-words sentence itself. That sentence tells a reader how to read the other sentences and states no obligation of its own. Outside it the text uses no MUST-level keyword anywhere. The summary written 2026-09-01 therefore captures three requirements and gates none: RFC9384-3-1 at Section 3, and RFC9384-4-1 and RFC9384-4-2 at Section 4, all three at SHOULD. Section 5 says the subcode "is purely informational and has no impact on the BGP Finite State Machine beyond that already documented by [RFC4271], Sections 6.6 and 6.7", so the document adds one registry value and three recommendations about using it. A zero-MUST document can reach the public ledger two ways, as this disposition or as a manual-walk extraction sign-off with a register-reason. This disposition is the route taken, because the sign-off at rfc/extraction/rfc9384.json declares the register the source derives, prose, and the second route would need it to declare the weaker manual-walk grade instead. That sign-off bounds the three-row checklist against the source text. | --- ### Page: DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY - Software Version Capability for BGP https://ze-software.net/quality/rfc-compliance/draft-abraitis-bgp-version-capability/ # DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY - Software Version Capability for BGP Partial. Every requirement this repository extracted from DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 11 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 11 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 11 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 11 | of 15 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 11 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 11 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 11 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 11 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 100.0% | 11 of 11 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 11 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 15 | | Gated MUST-level | 11 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 11 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/draft-abraitis-bgp-version-capability.md` | | Requirement shard | `rfc/requirements/draft-abraitis-bgp-version-capability.md` | | RFC text | `rfc/drafts/draft-abraitis-bgp-version-capability.txt` | ## Enrolment Enrolled: Software Version capability for BGP (code 75): eleven MUST-level requirements. Send side is implemented and gated by config -- encodeValue writes one length octet plus the constant ZeVersion and extractSoftverCapabilities declares code 75 only for a peer or group whose config carries a software-version key that is not disable or refuse (internal/component/bgp/plugins/softver/softver.go), which is 3-1, 3-2, 3-3, 3-6, 3-8 and 4-2. Receive side: parseCapability (internal/core/bgp/capability/capability.go) has no case for code 75, so a received capability becomes an Unknown nothing reads, which is how 3-4, 3-5, 3-7 and 4-1 are met. 3.1-1 requires RFC 9072, which is enrolled separately and Partial. No requirement carries a tagged test yet; the coverage rollup names them. ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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` ([`internal/component/bgp/plugins/softver/softver.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/softver/softver.go)) writes one length octet plus the constant `ZeVersion`, and `extractSoftverCapabilities` (same file) declares code 75 only for a peer or group whose config carries a `software-version` capability key that is not `disable` or `refuse`, so the default is disabled. Receive side: `parseCapability` ([`internal/core/bgp/capability/capability.go`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability.go)) 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. [`docs/architecture/wire/capabilities.md`](https://github.com/ze-software/ze/blob/main/docs/architecture/wire/capabilities.md) had always spelled the real one. **What the ledger says remains:** - ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 11 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **11** | every gated MUST falls in exactly one bucket above | **No test and no annotation (11):** [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-1`](#draft-abraitis-bgp-version-capability-3-1), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-2`](#draft-abraitis-bgp-version-capability-3-2), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-3`](#draft-abraitis-bgp-version-capability-3-3), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-4`](#draft-abraitis-bgp-version-capability-3-4), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-5`](#draft-abraitis-bgp-version-capability-3-5), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-6`](#draft-abraitis-bgp-version-capability-3-6), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-7`](#draft-abraitis-bgp-version-capability-3-7), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-8`](#draft-abraitis-bgp-version-capability-3-8), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3.1-1`](#draft-abraitis-bgp-version-capability-3.1-1), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-1`](#draft-abraitis-bgp-version-capability-4-1), [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-2`](#draft-abraitis-bgp-version-capability-4-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-1` | "If an implementation supports the inclusion of the capability, the implementation MUST include a configuration option to enable or disable its use, and MUST default to disabled" -- the configuration option half (§3) | MUST | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-2` | "If an implementation supports the inclusion of the capability, the implementation MUST include a configuration option to enable or disable its use, and MUST default to disabled" -- the default-to-disabled half (§3) | MUST | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-3` | "The Capability Length for the Software Version Capability MUST be greater than zero" (§3) | MUST | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-4` | "A value of zero SHALL be treated as an encoding error and the Capability MUST be ignored" -- the encoding-error half (§3) | SHALL | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-5` | "A value of zero SHALL be treated as an encoding error and the Capability MUST be ignored" -- the ignore half (§3) | MUST | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-6` | "The Version field MUST be encoded using UTF-8" (§3) | MUST | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-7` | "A receiving BGP speaker MUST NOT interpret invalid UTF-8 sequences" (§3) | MUST NOT | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-8` | "A sender SHOULD limit generated product identifiers to what is necessary to identify the product; a sender MUST NOT generate advertising or other nonessential information within the product identifier" -- the MUST NOT half (§3) | MUST NOT | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-9` | "The Capability Length SHOULD be no greater than 64" (§3) | SHOULD | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-10` | "A sender SHOULD limit generated product identifiers to what is necessary to identify the product" (§3) | SHOULD | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-11` | "A sender SHOULD NOT generate information in product-version that is not a version identifier" (§3) | SHOULD NOT | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-12` | "It is NOT RECOMMENDED for use outside a single Autonomous System, or a set of Autonomous Systems under a common administration" (§3) | NOT RECOMMENDED | 3 - Software Version Capability | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3.1-1` | "Implementations of this specification are REQUIRED Extended Optional Parameters Length for BGP OPEN Message support as defined in [RFC9072]" (§3.1) | REQUIRED | 3.1 - Capabilities Length Overflow | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-1` | "The Software Version Capability MUST only be used for displaying the version of a BGP speaker's router daemon to make troubleshooting easier" (§4) | MUST | 4 - Operation | **positive:** no positive test. **negative:** no negative test | | `DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-2` | "Enabling (i.e., turning on) this capability requires bouncing all existing BGP sessions and the feature MUST be explicitly configured before an implementation advertizes the Software Version Capability" (§4) | MUST | 4 - Operation | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-1`](#draft-abraitis-bgp-version-capability-3-1) "If an implementation supports the inclusion of the capability, the implementation MUST include a configuration option to enable or disable its use, and MUST default to disabled" -- the configuration option half (§3) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-2`](#draft-abraitis-bgp-version-capability-3-2) "If an implementation supports the inclusion of the capability, the implementation MUST include a configuration option to enable or disable its use, and MUST default to disabled" -- the default-to-disabled half (§3) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-3`](#draft-abraitis-bgp-version-capability-3-3) "The Capability Length for the Software Version Capability MUST be greater than zero" (§3) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-4`](#draft-abraitis-bgp-version-capability-3-4) "A value of zero SHALL be treated as an encoding error and the Capability MUST be ignored" -- the encoding-error half (§3) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-5`](#draft-abraitis-bgp-version-capability-3-5) "A value of zero SHALL be treated as an encoding error and the Capability MUST be ignored" -- the ignore half (§3) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-6`](#draft-abraitis-bgp-version-capability-3-6) "The Version field MUST be encoded using UTF-8" (§3) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-7`](#draft-abraitis-bgp-version-capability-3-7) "A receiving BGP speaker MUST NOT interpret invalid UTF-8 sequences" (§3) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-8`](#draft-abraitis-bgp-version-capability-3-8) "A sender SHOULD limit generated product identifiers to what is necessary to identify the product; a sender MUST NOT generate advertising or other nonessential information within the product identifier" -- the MUST NOT half (§3) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3.1-1`](#draft-abraitis-bgp-version-capability-3.1-1) "Implementations of this specification are REQUIRED Extended Optional Parameters Length for BGP OPEN Message support as defined in [RFC9072]" (§3.1) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-1`](#draft-abraitis-bgp-version-capability-4-1) "The Software Version Capability MUST only be used for displaying the version of a BGP speaker's router daemon to make troubleshooting easier" (§4) | no test | no test carries this requirement id | | [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-2`](#draft-abraitis-bgp-version-capability-4-2) "Enabling (i.e., turning on) this capability requires bouncing all existing BGP sessions and the feature MUST be explicitly configured before an implementation advertizes the Software Version Capability" (§4) | no test | no test carries this requirement id | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-1`](#draft-abraitis-bgp-version-capability-3-1) "If an implementation supports the inclusion of the capability, the implementation MUST include a configuration option to enable or disable its use, and MUST default to disabled" -- the configuration option half (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-1, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-2`](#draft-abraitis-bgp-version-capability-3-2) "If an implementation supports the inclusion of the capability, the implementation MUST include a configuration option to enable or disable its use, and MUST default to disabled" -- the default-to-disabled half (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-2, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-3`](#draft-abraitis-bgp-version-capability-3-3) "The Capability Length for the Software Version Capability MUST be greater than zero" (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-3, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-4`](#draft-abraitis-bgp-version-capability-3-4) "A value of zero SHALL be treated as an encoding error and the Capability MUST be ignored" -- the encoding-error half (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-4, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-5`](#draft-abraitis-bgp-version-capability-3-5) "A value of zero SHALL be treated as an encoding error and the Capability MUST be ignored" -- the ignore half (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-5, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-6`](#draft-abraitis-bgp-version-capability-3-6) "The Version field MUST be encoded using UTF-8" (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-6, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-7`](#draft-abraitis-bgp-version-capability-3-7) "A receiving BGP speaker MUST NOT interpret invalid UTF-8 sequences" (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-7, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-8`](#draft-abraitis-bgp-version-capability-3-8) "A sender SHOULD limit generated product identifiers to what is necessary to identify the product; a sender MUST NOT generate advertising or other nonessential information within the product identifier" -- the MUST NOT half (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-8, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3.1-1`](#draft-abraitis-bgp-version-capability-3.1-1) "Implementations of this specification are REQUIRED Extended Optional Parameters Length for BGP OPEN Message support as defined in [RFC9072]" (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3.1-1, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-1`](#draft-abraitis-bgp-version-capability-4-1) "The Software Version Capability MUST only be used for displaying the version of a BGP speaker's router daemon to make troubleshooting easier" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-1, so no unit is bound to it. ### [`DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-2`](#draft-abraitis-bgp-version-capability-4-2) "Enabling (i.e., turning on) this capability requires bouncing all existing BGP sessions and the feature MUST be explicitly configured before an implementation advertizes the Software Version Capability" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-2, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 draft walk, draft-abraitis-bgp-version-capability | | Signed off | 2026-09-01 | | Register | prose | | Source | rfc/drafts/draft-abraitis-bgp-version-capability.txt | | Source fingerprint | 472ca7a757b75dda | | Record | rfc/extraction/draft-abraitis-bgp-version-capability.json | | Mapped sentences | 9 | | Declined as scope | 5 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 2 | skipped (front-matter) | Title block, Abstract, Status of This Memo and Copyright Notice. The Abstract carries the sentence that makes the whole mechanism optional and one boilerplate site; both are excluded below. | | `1` | Introduction | 1 | walked | Introduction. Says data-center routers run different routing-software versions, that knowing them helps root-cause a fault, and that LLDP and CDP are awkward in containerized environments. Its one site restates the Abstract's optionality and is excluded below. No obligation. | | `2` | Specification of Requirements | 0 | walked | Specification of Requirements. The BCP 14 key-words paragraph. It tells a reader how to read the rest and binds nobody, which is why the derivation raises no site here. | | `3` | Software Version Capability | 7 | walked | Software Version Capability. The document's main normative section: seven sites, five mapped to DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3-1, 3-3, 3-4, 3-6, 3-7 and 3-8, and two excluded. It also assigns Capability Code 75 and defines the Capability Length and Capability Value fields, which are value definitions carried by the Wire Format section of rfc/short/draft-abraitis-bgp-version-capability.md. Six ids are declared unsourced here. Two are the second obligation of a sentence already mapped: 3-2, the MUST to default to disabled, shares site 3:1 with 3-1, and 3-5, the MUST to ignore a zero-length capability, shares site 3:4 with 3-4. The other four are advisory levels the capitalised MUST-level scan does not raise: 3-9 (the SHOULD that Capability Length be no greater than 64), 3-10 (the SHOULD to limit the product identifier, the first clause of site 3:7), 3-11 (the SHOULD NOT to put non-version information in product-version) and 3-12 (the NOT RECOMMENDED against use outside one Autonomous System). The opening sentence, that the document is not Standards Track but uses BCP 14 terminology, and the OPTIONAL sentence 'The inclusion of the Software Version Capability is OPTIONAL' state no obligation on a speaker. | | `3.1` | Capabilities Length Overflow | 2 | walked | Capabilities Length Overflow. Two sites: 3.1:1 is mapped to DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-3.1-1 and 3.1:2 is excluded below. The rest recounts that RFC 5492 caps the BGP Capabilities Optional Parameter at 255 bytes and that exceeding it leaves no room for more important capabilities. | | `4` | Operation | 2 | walked | Operation. Two sites, both mapped, to DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-1 and DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY-4-2. The remaining paragraph is the worked example of an operator correlating a bug with a routing-daemon version across upstream nodes. | | `5` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records that IANA has assigned capability number 75 in the BGP Capability Codes registry, with the one-row table naming it. Binds IANA, not a speaker. | | `6` | Security Considerations | 0 | walked | Security Considerations. States that the version should be treated as sensitive, that an attacker who learns it has an easier time, that a forged or snooped value is possible without an integrity- or confidentiality-providing transport, and that GTSM or a firewall on TCP 179 limits the exposure. Every sentence uses a lowercase 'should' the document's own Section 2 gives no normative level, and the derivation raises no site. No obligation on a speaker. | | `A` | Appendix A | 0 | skipped (appendix-non-normative) | Appendix A. Implementation Report. The RFC 7942 running-code list -- FRRouting, GoBGP, freeRouter and ExaBGP commits -- plus three figures of sample CLI output. No obligation. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `front:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Indicative, and permissive rather than obligatory: it states that an implementation need not advertise the version and need not process a received one. It directs no behavior, and it is the sentence that makes both halves of the mechanism optional. Ze offers the send half and declines the receive half, which is why the three receiver requirements are met by ignoring rather than by parsing. | Implementations are not required to advertise the version nor to process received advertisements. | | `front:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | IETF Trust boilerplate the splitter did not strip. The sentence binds a person who extracts Code Components from the document into other software and tells them to carry the Revised BSD License text. It directs no BGP speaker and describes no protocol behavior. Its 'must' is lowercase, so it is raised only by the case-insensitive modal scan the 'prose' register uses. | Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. | | `1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The Introduction's restatement of the Abstract sentence at site front:1, in the same permissive shape: implementations 'are not required to' advertise or to process. Indicative, and it directs no behavior. It is not recorded as a duplicate-of, because duplicate-of names an id another site maps and no site maps this sentence: it states no obligation for an id to hold. | Implementations are not required to advertise their software version nor to process it on receipt. | | `3:2` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 5492, which the sentence cites: a speaker that receives a capability it does not recognize or support must ignore it, RFC 5492 Section 3. The sentence is a back-reference telling a reader what already governs an unrecognized code 75, and its 'must' is lowercase. RFC 5492 is enrolled and that obligation is gated there as RFC5492-3-2. | An implementation that does not recognize or support the Software Version Capability but receives one must ignore it, as described in Section 3 of [RFC5492]. | | `3.1:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Indicative, and a security observation rather than a directive: it states that a rogue node can deny the advertisement of other capabilities by not excluding this one, and that the risk is not new to BGP. The words 'as required in Section 3.1' are a back-reference to the sentence at site 3.1:1, which this walk maps; they impose nothing of their own. | A rogue node can prevent the proper operation of a BGP session, or the advertisement of other Capabilities, by not excluding the Software Version Capability as required in Section 3.1. | ## Superseded No document obsoletes DRAFT-ABRAITIS-BGP-VERSION-CAPABILITY, so its obligations are stated where they were written. --- ### Page: DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT - Scalability Considerations for ADD-PATH with PATHS-LIMIT https://ze-software.net/quality/rfc-compliance/draft-abraitis-idr-addpath-paths-limit/ # DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT - Scalability Considerations for ADD-PATH with PATHS-LIMIT Supported. Every requirement this repository extracted from DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 4 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 47.8% | 11 of 23 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 7 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | Audit verdicts | 7 | of 4 gated MUSTs judged | 1 weak, wrong or unimplemented, 0 no longer current. Each is named below under its own requirement id | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | bad | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 7 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 23 | | Tagged units | 23 | | Recorded audit verdicts | 7 | | Discrimination records | 11 | | Summary | `rfc/short/draft-abraitis-idr-addpath-paths-limit.md` | | Requirement shard | `rfc/requirements/draft-abraitis-idr-addpath-paths-limit.md` | | RFC text | `rfc/drafts/draft-abraitis-idr-addpath-paths-limit.txt` | ## Enrolment Enrolled: BGP ADD-PATH Paths-Limit capability (code 76): four MUST-level requirements with positive and negative test carriers. For 3-1, coalescePathsLimit in internal/component/bgp/reactor/session_negotiate.go merges configuration and plugin declarations; TestBuildOpenCoalescesPathsLimit checks multiple families in one emitted instance and prevents repeated input instances from leaking into OPEN. For 3-2 and 3-3, core capability negotiation tests cover ADD-PATH present/absent and matching/unmatched families; for 3-4, TestParsePathsLimitDuplicateFirstWins covers retaining the first tuple and ignoring later duplicates. SHOULD-level zero handling (3-5) and session-wide sender enforcement (3-6) have separate parser, negotiation, and sender regression carriers. ## What the public ledger says **Status:** Supported **What the ledger says is covered** 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. **What the ledger says remains:** - ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-1`](#draft-abraitis-idr-addpath-paths-limit-3-1), [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-2`](#draft-abraitis-idr-addpath-paths-limit-3-2), [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-3`](#draft-abraitis-idr-addpath-paths-limit-3-3), [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-4`](#draft-abraitis-idr-addpath-paths-limit-3-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-1` | A BGP speaker wishing to indicate support for multiple AFI/SAFIs "MUST do so by including the information in a single instance of the PATHS-LIMIT capability" (§3) | MUST | 3 - PATHS-LIMIT Capability | **positive:** `unit/verify` [`TestBuildOpenCoalescesPathsLimit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L383). **positive:** `unit/verify` [`TestParsePeerCapabilityPathsLimitSingleInstance`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_test.go#L604). **negative:** `unit/verify` [`TestBuildOpenCoalescesPathsLimit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L384) | | `DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-2` | "The PATHS-LIMIT capability MUST be ignored if the ADD-PATH capability is not present" (§3) | MUST | 3 - PATHS-LIMIT Capability | **positive:** `unit/verify` [`TestNegotiatePathsLimit`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L589). **negative:** `unit/verify` [`TestNegotiatePathsLimitNoAddPath`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L644) | | `DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-3` | "An AFI/SAFI tuple MUST be ignored if the same tuple was not received in the ADD-PATH capability" (§3) | MUST | 3 - PATHS-LIMIT Capability | **positive:** `unit/verify` [`TestNegotiatePathsLimitPartialAddPath`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L670). **negative:** `unit/verify` [`TestNegotiatePathsLimitPartialAddPath`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L671) | | `DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-4` | When more than one tuple is received for the same AFI/SAFI pair, only the first tuple is considered and "All others MUST be ignored" (§3) | MUST | 3 - PATHS-LIMIT Capability | **positive:** `unit/verify` [`TestParsePathsLimitDuplicateFirstWins`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L878). **negative:** `unit/verify` [`TestParsePathsLimitDuplicateFirstWins`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L879) | | `DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-5` | "If the received Paths Limit is zero (0), the tuple SHOULD be ignored" (§3) | SHOULD | 3 - PATHS-LIMIT Capability | **positive:** `unit/verify` [`TestNegotiatePathsLimitDuplicateEntries`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L745). **positive:** `unit/verify` [`TestParsePathsLimitSkipZero`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L846). **negative:** `unit/verify` [`TestNegotiatePathsLimitDuplicateEntries`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L746). **negative:** `unit/verify` [`TestParsePathsLimitSkipZero`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L847). **negative:** `unit/verify` [`TestParsePathsLimitZeroFirst`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L906) | | `DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-6` | "A sender advertising multiple paths for the same prefix SHOULD send only the specified maximum number of paths indicated in the PATHS-LIMIT capability" (§3) | SHOULD | 3 - PATHS-LIMIT Capability | **positive:** `unit/verify` [`TestPathsLimitForwardWritersShareState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_paths_limit_test.go#L181). **positive:** `unit/verify` [`TestPathsLimitSessionAcrossUpdates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_paths_limit_test.go#L138). **negative:** `unit/verify` [`TestPathsLimitForwardWritersShareState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_paths_limit_test.go#L182). **negative:** `unit/verify` [`TestPathsLimitSessionAcrossUpdates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_paths_limit_test.go#L139). **positive:** `functional/verify` [`paths-limit-live.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/paths-limit-live.ci#L9). **negative:** `functional/verify` [`paths-limit-live.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/paths-limit-live.ci#L10) | | `DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-7` | "An implementation SHOULD provide a configuration knob to specify the maximum number of paths to accept from a sender" (§3) | SHOULD | 3 - PATHS-LIMIT Capability | **positive:** `unit/verify` [`TestConfiguredPathsLimitOpen`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_paths_limit_test.go#L13). **positive:** `unit/verify` [`TestParsePeerCapabilityPathsLimit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_test.go#L564). **negative:** `unit/verify` [`TestConfiguredPathsLimitOpen`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_paths_limit_test.go#L14) | ## Gaps and untested MUSTs DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-1`](#draft-abraitis-idr-addpath-paths-limit-3-1) A BGP speaker wishing to indicate support for multiple AFI/SAFIs "MUST do so by including the information in a single instance of the PATHS-LIMIT capability" (§3) Audit verdict: enforced (the tests do what the requirement demands), fresh. Re-judged on 2026-09-11 against the draft text and both carriers, after the config-parser carrier's claim was reworded. Section 3: "A BGP speaker that wishes to indicate support for multiple AFI/SAFIs MUST do so by including the information in a single instance of the PATHS-LIMIT capability." TestBuildOpenCoalescesPathsLimit is what proves it. It drives buildOpen, the one producer of ze's OPEN, with four PATHS-LIMIT inputs: an empty configured instance, a configured ipv4/unicast tuple, and two raw code-76 plugin payloads carrying a duplicate and a zero tuple. It re-parses the encoded OPEN and makes two distinct assertions, not one wearing two hats. The negative is the draft's own violation shape offered as input, several instances in and require.Len(limits, 1) out. The positive is that the information for both families travels inside that one instance, pinned as exactly [ipv4/unicast 4, ipv4/multicast 3]. buildOpen runs twice in the loop, so a second OPEN over the same caller-owned capabilities cannot leak a second instance either. The read-back drops the plugin's ipv6/unicast limit-0 tuple under 3-5, which is why the expectation names two families and not three; the instance-count assertion is untouched by that skip, so no neighbouring rule decides this one. coalescePathsLimit (internal/component/bgp/reactor/session_negotiate.go) is the enforcing function and buildOpen calls it on every OPEN. Both polarities carry a discrimination record over it: "merged == nil" -> true emits four instances and reds the negative, "seen[f]" -> true empties the merged instance and reds the positive. TestParsePeerCapabilityPathsLimitSingleInstance is a subordinate positive one hop short of the wire, and its reworded claim now matches its body: it reads the capability list parsePeerFromTree BUILDS, asserts one *capability.PathsLimit holding both configured families, and never encodes or parses an OPEN. It adds no proof of this requirement, because the regression it prevents cannot reach a peer: a parser emitting one instance per family is merged back into one by coalescePathsLimit before any OPEN is encoded, and the other readers of ps.Capabilities (configuredCapabilities, read in reactor.go and reactor_api.go) take Multiprotocol families and ConfigProvider values rather than PATHS-LIMIT framing. It stays tagged as a true config-path regression guard whose claim no longer over-states it. Whether a redundant carrier is worth keeping is an owner decision, not an audit finding. | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildOpenCoalescesPathsLimit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L384) | unit/verify | mutant, verified | | positive | [`TestParsePeerCapabilityPathsLimitSingleInstance`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_test.go#L604) | unit/verify | unproven | | positive | [`TestBuildOpenCoalescesPathsLimit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L383) | unit/verify | mutant, verified | ### [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-2`](#draft-abraitis-idr-addpath-paths-limit-3-2) "The PATHS-LIMIT capability MUST be ignored if the ADD-PATH capability is not present" (§3) Audit verdict: enforced (the tests do what the requirement demands), fresh. TestNegotiatePathsLimit pins both stored directions on exact values, the remote's 10 constraining our send and the local's 5 constraining the peer's send, so a direction swap fails it. TestNegotiatePathsLimitNoAddPath offers the same two PATHS-LIMIT capabilities with no ADD-PATH on either side and requires both maps to stay at zero. The producing branch is "n.addPath[f]&direction == 0" in negotiatePathsLimitDirection (internal/core/bgp/capability/negotiated.go). The negative buffer holds Multiprotocol and PathsLimit only, so no neighbouring rule can trip first. No discrimination record: both tags predate HEAD and the gate grandfathers them. | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegotiatePathsLimitNoAddPath`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L644) | unit/verify | unproven | | positive | [`TestNegotiatePathsLimit`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L589) | unit/verify | unproven | ### [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-3`](#draft-abraitis-idr-addpath-paths-limit-3-3) "An AFI/SAFI tuple MUST be ignored if the same tuple was not received in the ADD-PATH capability" (§3) Audit verdict: enforced (the tests do what the requirement demands), fresh. One test carries both polarities on two distinct assertions rather than one assertion wearing two hats: ipv4/unicast is present in ADD-PATH so its tuple applies in both directions (10 and 5), and ipv6/unicast is absent from ADD-PATH so both directions stay zero while its PATHS-LIMIT tuple is present in the same capability. Same producing branch as 3-2, evaluated per family. No discrimination record: the tags predate HEAD. | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegotiatePathsLimitPartialAddPath`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L671) | unit/verify | unproven | | positive | [`TestNegotiatePathsLimitPartialAddPath`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L670) | unit/verify | unproven | ### [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-4`](#draft-abraitis-idr-addpath-paths-limit-3-4) When more than one tuple is received for the same AFI/SAFI pair, only the first tuple is considered and "All others MUST be ignored" (§3) Audit verdict: enforced (the tests do what the requirement demands), fresh. TestParsePathsLimitDuplicateFirstWins parses two ipv4/unicast tuples, limit 10 then limit 20, and requires one entry whose limit is 10. The positive is the kept value and the negative is the count, which are separate assertions over the same buffer. Removing the "seen[f]" guard in parsePathsLimit (internal/core/bgp/capability/capability.go) yields two entries, so the count assertion discriminates. No discrimination record: the tags predate HEAD. | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParsePathsLimitDuplicateFirstWins`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L879) | unit/verify | unproven | | positive | [`TestParsePathsLimitDuplicateFirstWins`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L878) | unit/verify | unproven | ### [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-5`](#draft-abraitis-idr-addpath-paths-limit-3-5) "If the received Paths Limit is zero (0), the tuple SHOULD be ignored" (§3) Audit verdict: enforced (the tests do what the requirement demands), fresh. parsePathsLimit marks a family seen BEFORE it skips a zero limit, so a first zero tuple also suppresses a later duplicate for that family. TestParsePathsLimitSkipZero covers the plain skip and TestParsePathsLimitZeroFirst covers that interplay with 3-4, requiring ipv4/unicast to stay absent even though a later duplicate advertises 10. TestNegotiatePathsLimitDuplicateEntries runs one set of tuples through the direct API and through the wire parser and requires the negotiated maps to hold ipv6 alone in both directions. All three units carry a discrimination record over "limit == 0" -> false in parsePathsLimit, under which each goes red. | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParsePathsLimitSkipZero`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L847) | unit/verify | mutant, verified | | negative | [`TestParsePathsLimitZeroFirst`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L906) | unit/verify | mutant, verified | | negative | [`TestNegotiatePathsLimitDuplicateEntries`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L746) | unit/verify | unproven | | positive | [`TestParsePathsLimitSkipZero`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L846) | unit/verify | mutant, verified | | positive | [`TestNegotiatePathsLimitDuplicateEntries`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L745) | unit/verify | unproven | ### [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-6`](#draft-abraitis-idr-addpath-paths-limit-3-6) "A sender advertising multiple paths for the same prefix SHOULD send only the specified maximum number of paths indicated in the PATHS-LIMIT capability" (§3) Audit verdict: enforced (the tests do what the requirement demands), fresh. The unit tests assert the exact identifier sequence that reached the wire, for ipv4/unicast and ipv6/unicast: announced [0 1 1 4] and withdrawn [99 0] across eight sends, which pins the withheld path, the replacement at capacity, the unknown withdrawal that frees nothing, and the reuse after a real withdrawal. TestPathsLimitForwardWritersShareState proves raw forwarding and re-encoded forwarding draw on one per-session budget, forwarding [5 7] and never 6. test/plugin/paths-limit-live.ci carries the same requirement at the daemon: it starts ze and a peer that advertises ipv4 1 and ipv6 2, drives the real `ze send bgp * update text` CLI over SSH, and asserts by hex, in sequence groups, that an excess identifier never reaches the wire before the next different-prefix sentinel and appears only after a withdrawal and an explicit retry. Six records: the four unit tags over the admission guard in pathsLimitSection, and the two .ci tags by revert of that same function, each citing the expect directive its red was tied to. The enforcing code is Session.pathsLimitSection, reached from writeRawUpdateBody through filterPathsLimit, which is why the route-server fast path is covered by the same state. | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPathsLimitForwardWritersShareState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_paths_limit_test.go#L182) | unit/verify | mutant, verified | | negative | [`TestPathsLimitSessionAcrossUpdates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_paths_limit_test.go#L139) | unit/verify | mutant, verified | | negative | [`paths-limit-live.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/paths-limit-live.ci#L10) | functional/verify | revert, verified | | positive | [`TestPathsLimitForwardWritersShareState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_paths_limit_test.go#L181) | unit/verify | mutant, verified | | positive | [`TestPathsLimitSessionAcrossUpdates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_paths_limit_test.go#L138) | unit/verify | mutant, verified | | positive | [`paths-limit-live.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/paths-limit-live.ci#L9) | functional/verify | revert, verified | ### [`DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-7`](#draft-abraitis-idr-addpath-paths-limit-3-7) "An implementation SHOULD provide a configuration knob to specify the maximum number of paths to accept from a sender" (§3) Audit verdict: weak (the tests pass over code that does not enforce the requirement), fresh. The pair over TestConfiguredPathsLimitOpen is a strong test of half the sentence and nothing tests the other half. What it checks: the container knob `session > capability > add-path > limit`, the per-family override, and the interaction with disabled, refused and required families decide the tuples encoded in the OPEN, with an omitted limit advertising none and a disabled or refused family unable to carry an orphan tuple. parseAddPathFromTree (internal/component/bgp/reactor/config_capabilities.go) derives the tuples from addPath.Families, the same slice that reaches the wire. What nothing checks, because no code does it: the draft asks for a knob that specifies "the maximum number of paths to accept from a sender", and ze's knob states a number to the peer without bounding what ze accepts. EncodingCaps.PathsLimitRecv is read by internal/core/bgp/context/context.go, internal/component/bgp/format/json.go and internal/component/bgp/reactor/negotiated.go, all of which carry it into a negotiated context or into show output, and no reader drops a received path above it. A peer that ignores the request is not policed. The verdict is weak rather than enforced because the tag ties this body to the whole requirement, and the reading under which "accept" means local enforcement is not proven by anything. The absent behavior is disclosed on the summary's Support remaining row. No discrimination record was written, because the owner's open decision on building the receive side can reword this claim, and rewording stales a record. | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestConfiguredPathsLimitOpen`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_paths_limit_test.go#L14) | unit/verify | unproven | | positive | [`TestConfiguredPathsLimitOpen`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_paths_limit_test.go#L13) | unit/verify | unproven | | positive | [`TestParsePeerCapabilityPathsLimit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_test.go#L564) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6, draft-abraitis-idr-addpath-paths-limit | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/drafts/draft-abraitis-idr-addpath-paths-limit.txt | | Source fingerprint | 87330146d8f7b5c0 | | Record | rfc/extraction/draft-abraitis-idr-addpath-paths-limit.json | | Mapped sentences | 4 | | Declined as scope | 0 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Abstract, Status of This Memo, Copyright Notice and Table of Contents. The Abstract restates section 1 and directs no speaker. | | `1` | Introduction | 0 | walked | Introduction. Indicative prose: ADD-PATH lets a speaker advertise several paths for one prefix, a large number of such paths can exhaust the receiver's memory, and this document defines a capability that tells the sender the receiver's ceiling. No sentence directs a speaker. | | `2` | Specification of Requirements | 0 | walked | Specification of Requirements. The BCP 14 key-words paragraph, which binds no speaker and states how to read the other sections. The derivation excludes it from the site inventory for that reason. | | `3` | PATHS-LIMIT Capability | 4 | walked | PATHS-LIMIT Capability. The document's only normative section: Capability Code 76, the 5-octet AFI/SAFI/Paths-Limit tuple, and four capitalised MUST-level sites mapped below to DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT-3-1 through -3-4. Its three remaining directives are advisory and carry no site: ignore a tuple whose Paths Limit is zero, send no more than the advertised maximum, and offer a configuration knob. Those are the unsourced ids below. Two indicative sentences state no obligation: the Paths Limit field description, and "If the PATHS-LIMIT capability is empty (i.e. the Capability Length field is set to 0), it means that the sender doesn't have any specific limits to communicate", which defines what an empty capability means rather than directing a speaker. | | `4` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records capability number 76 in the BGP Capability Codes registry. Binds IANA, not a speaker. | | `5` | not stated | 0 | walked | Security Considerations, and with it the unnumbered Acknowledgements, References and Authors' Addresses blocks, which carry no section number and so stay in this body under the heading derivation. The security text states that PATHS-LIMIT mitigates some RFC 7911 concerns and that a rogue or misconfigured node can advertise a limit too low for the application. Its one direction, "Users of the PATHS-LIMIT Capability are encouraged to examine the behavior and potential impact", is an encouragement with no RFC 2119 keyword and binds no speaker. | | `A` | Implementation Report | 0 | skipped (appendix-non-normative) | Implementation Report. An RFC 7942 note naming the FRRouting commit that implements the draft. It reports on another implementation and states no obligation. | ### Excluded sentences The walk over DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT declined no sentence: every site it found is mapped to a requirement. ## Superseded No document obsoletes DRAFT-ABRAITIS-IDR-ADDPATH-PATHS-LIMIT, so its obligations are stated where they were written. --- ### Page: DRAFT-IETF-BESS-MUP-SAFI - BGP Extensions for the Mobile User Plane (MUP) SAFI https://ze-software.net/quality/rfc-compliance/draft-ietf-bess-mup-safi/ # DRAFT-IETF-BESS-MUP-SAFI - BGP Extensions for the Mobile User Plane (MUP) SAFI Partial. Every requirement this repository extracted from DRAFT-IETF-BESS-MUP-SAFI, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 2.5% | 1 of 40 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 5.0% | 2 of 40 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 40 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 4 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 40 | of 61 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 40 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 40 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 40 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 40 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 92.5% | 37 of 40 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 40 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 61 | | Gated MUST-level | 40 | | Not applicable, so out of scope | 0 | | Declared gaps | 37 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 4 | | Tagged units | 4 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/draft-ietf-bess-mup-safi.md` | | Requirement shard | `rfc/requirements/draft-ietf-bess-mup-safi.md` | | RFC text | `rfc/drafts/draft-ietf-bess-mup-safi.txt` | ## Enrolment Enrolled: BGP Extensions for Mobile User Plane (MUP) SAFI ## What the public ledger says **Status:** Partial **What the ledger says is covered** The BGP-MUP NLRI codec only: ipv4/mup and ipv6/mup family registration, ISD/DSD/T1ST/T2ST encoding from config and route commands, header plus RD decoding, the RFC 7606 Section 5.4 ruling that discards a route whose Architecture Type is not 1 or whose Route Type is outside 1..4 at ingress ([`internal/component/bgp/plugins/nlri/mup/rfc7606.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc7606.go), RecognizeNLRI), MUP extended-community config syntax, and family-generic MP_REACH announcement (internal/component/bgp/plugins/nlri/mup). Thirty-seven MUST gaps are annotated per line in [`rfc/short/draft-ietf-bess-mup-safi.md`](https://github.com/ze-software/ze/blob/main/rfc/short/draft-ietf-bess-mup-safi.md). Withdrawal: no MUP NLRI reaches the family-generic MP_UNREACH encoder ([`internal/component/bgp/reactor/peer_rib_routes.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/cmd/update/update_text_nlri.go), [`internal/component/bgp/plugins/cmd/announce/announce.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/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`](#draft-ietf-bess-mup-safi-3.3.3-2), 3.3.6-2, 3.3.9-1, 3.3.9-2). ParseMUP keeps the route-type body opaque ([`internal/component/bgp/plugins/nlri/mup/types.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/types.go)), so no RFC 7606 treat-as-withdraw fires on an out-of-range prefix length, wrong-size address, zero TEID, invalid endpoint or source length, over-long T2ST endpoint length, or non-3gpp-5g architecture type (3.1.1-1, 3.1.1-2, 3.1.2-1, 3.1.3-1, 3.1.3.1-1, 3.1.3.1-2, 3.1.3.1-3, 3.1.3.1-4, 3.1.4-1, 3.1.4.1-1, 3.1.4.1-2), nor on a missing Prefix-SID, a nexthop/locator mismatch, or a Type 2 ST route without the BGP MUP Extended Community (3.3.3-1, 3.3.3-3, 3.3.3-4, 3.3.6-1, 3.3.12-1). Send side: ze runs no MUP PE or MUP Controller function, so route targets, the BGP MUP Extended Community, the Prefix-SID, the GTP4.E/GTP6.E function, the required T1ST TEID and Endpoint Address, and the PE or controller IPv6 nexthop are whatever the operator configures rather than derived (3.3.1-1, 3.3.1-2, 3.3.1-3, 3.3.1-4, 3.3.2-1, 3.3.4-1, 3.3.4-2, 3.3.4-3, 3.3.4-4, 3.3.5-1, 3.3.5-2, 3.3.7-1, 3.3.7-3, 3.3.10-1, 3.3.10-2). **What the ledger says remains:** - ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 1 | one part of the gated population | | Annotated instead of tested | 39 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **40** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (1):** [`DRAFT-IETF-BESS-MUP-SAFI-3.3-1`](#draft-ietf-bess-mup-safi-3.3-1) **Annotated instead of tested (39):** [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-1`](#draft-ietf-bess-mup-safi-3.3.1-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-2`](#draft-ietf-bess-mup-safi-3.3.1-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-3`](#draft-ietf-bess-mup-safi-3.3.1-3), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.2-1`](#draft-ietf-bess-mup-safi-3.3.2-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-1`](#draft-ietf-bess-mup-safi-3.3.4-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-2`](#draft-ietf-bess-mup-safi-3.3.4-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-3`](#draft-ietf-bess-mup-safi-3.3.4-3), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-4`](#draft-ietf-bess-mup-safi-3.3.4-4), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.5-1`](#draft-ietf-bess-mup-safi-3.3.5-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.7-1`](#draft-ietf-bess-mup-safi-3.3.7-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.7-2`](#draft-ietf-bess-mup-safi-3.3.7-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.10-1`](#draft-ietf-bess-mup-safi-3.3.10-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.10-2`](#draft-ietf-bess-mup-safi-3.3.10-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.1-1`](#draft-ietf-bess-mup-safi-3.1-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-1`](#draft-ietf-bess-mup-safi-3.3.3-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-2`](#draft-ietf-bess-mup-safi-3.3.3-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.6-1`](#draft-ietf-bess-mup-safi-3.3.6-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.6-2`](#draft-ietf-bess-mup-safi-3.3.6-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.9-1`](#draft-ietf-bess-mup-safi-3.3.9-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.12-1`](#draft-ietf-bess-mup-safi-3.3.12-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.1-1`](#draft-ietf-bess-mup-safi-3.1.1-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.1-2`](#draft-ietf-bess-mup-safi-3.1.1-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.2-1`](#draft-ietf-bess-mup-safi-3.1.2-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3-1`](#draft-ietf-bess-mup-safi-3.1.3-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-1`](#draft-ietf-bess-mup-safi-3.1.3.1-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-2`](#draft-ietf-bess-mup-safi-3.1.3.1-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-3`](#draft-ietf-bess-mup-safi-3.1.3.1-3), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4-1`](#draft-ietf-bess-mup-safi-3.1.4-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-1`](#draft-ietf-bess-mup-safi-3.1.4.1-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-4`](#draft-ietf-bess-mup-safi-3.1.3.1-4), [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-2`](#draft-ietf-bess-mup-safi-3.1.4.1-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-3`](#draft-ietf-bess-mup-safi-3.3.3-3), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-4`](#draft-ietf-bess-mup-safi-3.3.3-4), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-4`](#draft-ietf-bess-mup-safi-3.3.1-4), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.5-2`](#draft-ietf-bess-mup-safi-3.3.5-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.8-1`](#draft-ietf-bess-mup-safi-3.3.8-1), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.7-3`](#draft-ietf-bess-mup-safi-3.3.7-3), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.9-2`](#draft-ietf-bess-mup-safi-3.3.9-2), [`DRAFT-IETF-BESS-MUP-SAFI-3.3.11-1`](#draft-ietf-bess-mup-safi-3.3.11-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `DRAFT-IETF-BESS-MUP-SAFI-3.3.1-1` | PE advertising ISD route must attach export BGP Route Target Extended Community of the associated routing instance (Section 3.3.1) | MUST | 3.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseConfigRoute attaches only the extended communities the operator configures (internal/component/bgp/plugins/nlri/mup/config.go:66) and ze models no routing instance with export route targets, so no export route target is derived for an ISD advertisement | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.1-2` | PE advertising ISD route must use IPv6 address of PE as nexthop in MP_REACH_NLRI (Section 3.3.1) | MUST | 3.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** EncodeRoute takes the next-hop verbatim from the route command (internal/component/bgp/plugins/nlri/mup/encode.go:173-191) and accepts an IPv4 next-hop for an ISD route, so nothing requires the IPv6 address of the PE | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.1-3` | ISD route update must have a prefix SID attribute (Section 3.3.1) | MUST | 3.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseConfigRoute adds the Prefix-SID attribute only when the config supplies one (internal/component/bgp/plugins/nlri/mup/config.go:71), so an ISD route configured without a prefix SID is advertised without one | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.2-1` | PE withdrawing ISD route must attach export BGP Route Target Extended Community (Section 3.3.2) | MUST | 3.3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** a MUP withdrawal is built as a bare MP_UNREACH_NLRI with no path attributes (internal/component/bgp/reactor/peer_rib_routes.go:182-197), so an ISD withdrawal carries no export route target | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.4-1` | DSD address in NLRI must be a unique PE identifier (Section 3.3.4) | MUST | 3.3.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseDSDFields accepts any parsable address as the DSD NLRI address (internal/component/bgp/plugins/nlri/mup/encode.go:301-310) and never checks that it identifies the PE uniquely | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.4-2` | PE announcing DSD route must attach a BGP MUP Extended Community (Section 3.3.4) | MUST | 3.3.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseConfigRoute attaches only the extended communities the operator configures (internal/component/bgp/plugins/nlri/mup/config.go:66), so a DSD advertisement carries a BGP MUP Extended Community only when the operator writes one into the route | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.4-3` | PE advertising DSD route must use IPv6 address of PE as nexthop in MP_REACH_NLRI (Section 3.3.4) | MUST | 3.3.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** EncodeRoute takes the next-hop verbatim from the route command (internal/component/bgp/plugins/nlri/mup/encode.go:173-191) and accepts an IPv4 next-hop for a DSD route, so nothing requires the IPv6 address of the PE | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.4-4` | DSD route update must have a prefix SID attribute (Section 3.3.4) | MUST | 3.3.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseConfigRoute adds the Prefix-SID attribute only when the config supplies one (internal/component/bgp/plugins/nlri/mup/config.go:71), so a DSD route configured without a prefix SID is advertised without one | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.5-1` | BGP speaker announcing T1ST must attach a BGP MUP Extended Community (Section 3.3.5) | MUST | 3.3.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseConfigRoute attaches only the extended communities the operator configures (internal/component/bgp/plugins/nlri/mup/config.go:66), so a Type 1 ST advertisement carries a BGP MUP Extended Community only when the operator writes one into the route | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.7-1` | MUP Controller must set nexthop of T1ST route to the controller address (Section 3.3.7) | MUST | 3.3.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** EncodeRoute takes the next-hop verbatim from the route command (internal/component/bgp/plugins/nlri/mup/encode.go:173-191) for a Type 1 ST route and ze runs no MUP Controller function that substitutes the controller address | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.7-2` | Controller must announce T1ST route using AFI of the route and SAFI BGP-MUP to all BGP speakers in SRv6 domain (Section 3.3.7) | MUST | 3.3.7 | **positive:** `unit/verify` [`TestRFCMUPAnnounceUsesRouteAFIWithMUPSAFI`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc_mup_safi_test.go#L207). **negative:** no negative test. **{single-polarity}:** EncodeRoute emits MUP NLRI only under SAFI 85 with the AFI taken from the route family (internal/component/bgp/plugins/nlri/mup/encode.go:183-199), so no non-conformant AFI/SAFI emission exists to reject | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.10-1` | Controller must attach Route Target Extended Community of routing instances in the PE for T2ST (Section 3.3.10) | MUST | 3.3.10 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseConfigRoute attaches only the extended communities the operator configures (internal/component/bgp/plugins/nlri/mup/config.go:66) and ze models no routing instance with export route targets, so a Type 2 ST advertisement carries a route target only when the operator writes one into the route | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.10-2` | Controller must set nexthop of T2ST route to MUP Controller address (Section 3.3.10) | MUST | 3.3.10 | **positive:** no positive test. **negative:** no negative test. **{gap}:** EncodeRoute takes the next-hop verbatim from the route command (internal/component/bgp/plugins/nlri/mup/encode.go:173-191) for a Type 2 ST route and ze runs no MUP Controller function that substitutes the controller address | | `DRAFT-IETF-BESS-MUP-SAFI-3.1-1` | Unknown Route Types for supported Architecture Types must be silently ignored (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRFCMUPUnknownRouteTypeIsNotRejected`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc_mup_safi_test.go#L161). **negative:** no negative test. **{single-polarity}:** ParseMUP accepts any route type under the 3gpp-5g architecture type and returns the bytes after the declared Length (internal/component/bgp/plugins/nlri/mup/types.go:118-152), so there is no rejection path for an unknown route type to drive negatively | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.3-1` | Receiver must ensure ISD Address field value is address of originator of locator in prefix SID attribute (Section 3.3.3) | MUST | 3.3.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) and ze reads no prefix SID locator anywhere in internal/component/bgp, so the ISD Address field is never checked against the originator of the prefix SID locator | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.3-2` | On MP_UNREACH_NLRI, receiver must delete withdrawn ISD route from routing instance table (Section 3.3.3) | MUST | 3.3.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** nlrisplit now registers SplitMUP for SAFI 85 (internal/core/bgp/nlri/nlrisplit/register.go), so insertPoolNLRIs stores a received MUP NLRI as an opaque entry keyed on the whole NLRI and removePoolNLRIs deletes exactly the NLRI a withdrawal names (internal/component/bgp/plugins/rib/rib.go). What remains is that ze models no MUP routing instance: the entry lives in the peer's Adj-RIB-In alone, and no RFC requirement tagged test drives an ISD withdrawal through it | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.6-1` | Receiver must ensure DSD nexthop in MP_REACH_NLRI is identical to originator of locator in prefix SID attribute (Section 3.3.6) | MUST | 3.3.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) and ze reads no prefix SID locator anywhere in internal/component/bgp, so the DSD nexthop is never compared with the originator of the prefix SID locator | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.6-2` | On MP_UNREACH_NLRI, receiver must delete withdrawn DSD route from routing instance table (Section 3.3.6) | MUST | 3.3.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** nlrisplit now registers SplitMUP for SAFI 85 (internal/core/bgp/nlri/nlrisplit/register.go), so insertPoolNLRIs stores a received MUP NLRI as an opaque entry keyed on the whole NLRI and removePoolNLRIs deletes exactly the NLRI a withdrawal names (internal/component/bgp/plugins/rib/rib.go). What remains is that ze models no MUP routing instance: the entry lives in the peer's Adj-RIB-In alone, and no RFC requirement tagged test drives a DSD withdrawal through it | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.9-1` | PE receiving T1ST routes in MP_UNREACH_NLRI must delete all routes from associated routing instance (Section 3.3.9) | MUST | 3.3.9 | **positive:** no positive test. **negative:** no negative test. **{gap}:** nlrisplit now registers SplitMUP for SAFI 85 (internal/core/bgp/nlri/nlrisplit/register.go), so insertPoolNLRIs stores a received MUP NLRI as an opaque entry keyed on the whole NLRI and removePoolNLRIs deletes exactly the NLRI a withdrawal names (internal/component/bgp/plugins/rib/rib.go). The delete is one NLRI for one NLRI. Nothing reads the Type 1 ST route type to delete every route of the associated routing instance, and ze models no such instance | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.12-1` | PE must handle T2ST without MUP Extended Community as treat-as-withdraw (Section 3.3.12) | MUST | 3.3.12 | **positive:** no positive test. **negative:** no negative test. **{gap}:** DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) without consulting the UPDATE extended communities, so a Type 2 ST route arriving without the BGP MUP Extended Community is not treated as withdrawn | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.1-1` | ISD with prefix length exceeding max for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.1) | MUST | 3.1.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the ISD prefix length, so a prefix length above 32 for AFI 1 or 128 for AFI 2 is accepted as a valid route | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.1-2` | Speaker must skip malformed NLRIs and continue processing rest of Update message (Section 3.1.1) | MUST | 3.1.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP applies no semantic validation (internal/component/bgp/plugins/nlri/mup/types.go:118-152), so no NLRI is ever classed malformed and skipped, and a truncated NLRI aborts the parse with ErrMUPTruncated (internal/component/bgp/plugins/nlri/mup/types.go:127-129) instead of continuing with the rest of the Update | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.2-1` | DSD with wrong address size for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.2) | MUST | 3.1.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never measures the DSD address against the AFI, so a 4-octet address under AFI 2 or a 16-octet address under AFI 1 is accepted | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.3-1` | T1ST with prefix length exceeding max for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.3) | MUST | 3.1.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T1ST prefix length, so a prefix length above 32 for AFI 1 or 128 for AFI 2 is accepted as a valid route | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-1` | T1ST with TEID=0: treat-as-withdraw per RFC 7606 (Section 3.1.3.1) | MUST | 3.1.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T1ST TEID, and parseTEIDWithBits maps an absent TEID to zero bits which writeT1STData then omits from the NLRI (internal/component/bgp/plugins/nlri/mup/encode.go:435-450, internal/component/bgp/plugins/nlri/mup/encode.go:396-399) | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-2` | T1ST with invalid Endpoint Address Length (not 32 or 128): treat-as-withdraw (Section 3.1.3.1) | MUST | 3.1.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T1ST Endpoint Address Length, while writeT1STData derives it from the configured address (internal/component/bgp/plugins/nlri/mup/encode.go:404-409), so a received length other than 32 or 128 is accepted | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-3` | T1ST with invalid Source Address Length (not 0, 32, or 128): treat-as-withdraw (Section 3.1.3.1) | MUST | 3.1.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T1ST Source Address Length, while writeT1STData derives it from the configured address (internal/component/bgp/plugins/nlri/mup/encode.go:411-416), so a received length other than 0, 32 or 128 is accepted | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.4-1` | T2ST with Endpoint Length exceeding max for AFI: treat-as-withdraw (Section 3.1.4) | MUST | 3.1.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T2ST Endpoint Length, so a combined length above 64 for AFI 1 or 160 for AFI 2 is accepted | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-1` | T2ST with TEID=0: treat-as-withdraw per RFC 7606 (Section 3.1.4.1) | MUST | 3.1.4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T2ST TEID, while writeT2STData writes whatever parseTEIDWithBits yields, zero included (internal/component/bgp/plugins/nlri/mup/encode.go:422-431) | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-4` | T1ST NLRI architecture field MUST be encoded as specified for 3gpp-5g; otherwise treat-as-withdraw (Section 3.1.3.1) | MUST | 3.1.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** writeMUPNLRI always writes architecture type 1 (internal/component/bgp/plugins/nlri/mup/encode.go:246) but ParseMUP accepts any architecture byte and keeps parsing (internal/component/bgp/plugins/nlri/mup/types.go:123), so a T1ST NLRI encoded for another architecture is not treated as withdraw | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-2` | T2ST NLRI architecture field MUST be encoded as specified for 3gpp-5g; otherwise treat-as-withdraw (Section 3.1.4.1) | MUST | 3.1.4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** writeMUPNLRI always writes architecture type 1 (internal/component/bgp/plugins/nlri/mup/encode.go:246) but ParseMUP accepts any architecture byte and keeps parsing (internal/component/bgp/plugins/nlri/mup/types.go:123), so a T2ST NLRI encoded for another architecture is not treated as withdraw | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.3-3` | ISD/DSD without prefix SID attribute: treat-as-withdraw (Section 3.3.3, 3.3.6) | MUST | 3.3.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) without consulting the UPDATE path attributes, so an ISD or DSD route arriving without a Prefix-SID attribute is not treated as withdrawn | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.3-4` | Nexthop/locator mismatch on ISD/DSD: treat-as-withdraw (Section 3.3.3, 3.3.6) | MUST | 3.3.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) and ze reads no prefix SID locator anywhere in internal/component/bgp, so a mismatch between the nexthop and the locator originator is never detected on ISD or DSD routes | | `DRAFT-IETF-BESS-MUP-SAFI-3.3-1` | PE and MUP Controller MUST establish a BGP session to exchange BGP-MUP NLRIs for both IPv4 and IPv6 AFIs (Section 3.3) | MUST | 3.3 | **positive:** `unit/verify` [`TestRFCMUPFamiliesCoverBothAFIs`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc_mup_safi_test.go#L84). **negative:** `unit/verify` [`TestRFCMUPRejectsNonMUPFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc_mup_safi_test.go#L133) | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.1-4` | ISD prefix SID function MUST be GTP4.E if BGP AFI is IPv4, or MUST be GTP6.E if BGP AFI is IPv6 (Section 3.3.1) | MUST | 3.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseConfigRoute adds the Prefix-SID attribute only when the config supplies one (internal/component/bgp/plugins/nlri/mup/config.go:71) and passes its bytes through unchanged, and ze decodes no SRv6 endpoint function, so the GTP4.E/GTP6.E function is never tied to the BGP AFI | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.5-2` | When withdrawing DSD route, BGP speaker MUST attach a BGP MUP Extended community of the associated routing instance (Section 3.3.5) | MUST | 3.3.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** a MUP withdrawal is built as a bare MP_UNREACH_NLRI with no path attributes (internal/component/bgp/reactor/peer_rib_routes.go:182-197), so a DSD withdrawal carries no BGP MUP Extended Community | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.8-1` | Controller MUST advertise the withdrawal of the Type 1 ST route (Section 3.3.8) | MUST | 3.3.8 | **positive:** no positive test. **negative:** no negative test. **{gap}:** no MUP NLRI can reach the family-generic MP_UNREACH encoder (internal/component/bgp/reactor/peer_rib_routes.go:170). Its only callers take the NLRI from a PeerOpWithdraw queue entry (internal/component/bgp/reactor/peer_initial_sync.go:237, :377) filled by QueueWithdraw (internal/component/bgp/reactor/peer.go:886-893), and the two withdrawal entry points that feed it parse no SAFI 85: text mode rejects the family in isSupportedFamily, whose list stops at SAFI 73 (internal/component/bgp/plugins/cmd/update/update_text_nlri.go:375-403), and the announce/withdraw registry builds only unicast and FlowSpec NLRIs (internal/component/bgp/plugins/cmd/announce/announce.go:257, :304, :415). NewMUP and NewMUPFull (internal/component/bgp/plugins/nlri/mup/types.go:93, :103) have no non-test caller, and nlrisplit registers no SAFI 85 splitter (internal/core/bgp/nlri/nlrisplit/register.go:9-24) so no received MUP route is stored to be withdrawn either | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.7-3` | Controller MUST advertise the Type 1 ST route with Destination prefix, TEID, QFI, Endpoint Address, and optionally Source Address (Section 3.3.7) | MUST | 3.3.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** parseT1STFields requires only the destination prefix (internal/component/bgp/plugins/nlri/mup/encode.go:313-316) and writeT1STData omits the TEID field when no TEID is configured and the Endpoint Address field when no endpoint is configured (internal/component/bgp/plugins/nlri/mup/encode.go:396-409), so a Type 1 ST route is advertised without them | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.9-2` | PE receiving T1ST in MP_UNREACH_NLRI without Source address MUST delete all matching T1ST routes with different Source addresses (Section 3.3.9) | MUST | 3.3.9 | **positive:** no positive test. **negative:** no negative test. **{gap}:** nlrisplit now registers SplitMUP for SAFI 85 (internal/core/bgp/nlri/nlrisplit/register.go), so insertPoolNLRIs stores a received MUP NLRI as an opaque entry keyed on the whole NLRI and removePoolNLRIs deletes exactly the NLRI a withdrawal names (internal/component/bgp/plugins/rib/rib.go). The opaque key is the whole NLRI, Source address included, so a Source-less Type 1 ST withdrawal matches no stored entry. No wildcard delete over differing Source addresses exists | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.11-1` | Controller MUST advertise the withdrawal of the Type 2 ST route (Section 3.3.11) | MUST | 3.3.11 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the same missing path as DRAFT-IETF-BESS-MUP-SAFI-3.3.8-1 -- the family-generic MP_UNREACH encoder (internal/component/bgp/reactor/peer_rib_routes.go:170) is reachable only through PeerOpWithdraw (internal/component/bgp/reactor/peer.go:886-893, internal/component/bgp/reactor/peer_initial_sync.go:237, :377), and neither withdrawal entry point produces a SAFI 85 NLRI: isSupportedFamily omits it (internal/component/bgp/plugins/cmd/update/update_text_nlri.go:375-403) and the announce registry builds only unicast and FlowSpec NLRIs (internal/component/bgp/plugins/cmd/announce/announce.go:257, :304, :415) | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.3-5` | Receiver of ISD routes should ignore nexthop in MP_REACH_NLRI and use prefix SID locator instead (Section 3.3.3) | SHOULD | 3.3.3 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.7-4` | Controller should attach Route Target Extended Community which PEs are importing for T1ST (Section 3.3.7) | SHOULD | 3.3.7 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.9-3` | PE should use received Tunnel Endpoint Address in T1ST NLRI as key to lookup associated ISD route (Section 3.3.9) | SHOULD | 3.3.9 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.6-3` | Receiver of DSD routes should ignore the nexthop in MP_REACH_NLRI attribute (Section 3.3.6) | SHOULD | 3.3.6 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.9-4` | PE receiving T1ST routes should ignore the received nexthop in MP_REACH_NLRI (Section 3.3.9) | SHOULD | 3.3.9 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.9-5` | PE should generate forwarding SID for GTP4/6.E based on SRv6 MUP procedures (Section 3.3.9) | SHOULD | 3.3.9 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.9-6` | If PE cannot generate prefix SID, it should mark the received T1ST route as invalid (Section 3.3.9) | SHOULD | 3.3.9 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.12-2` | Receiver of T2ST routes should ignore received nexthop in MP_REACH_NLRI (Section 3.3.12) | SHOULD | 3.3.12 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.12-3` | PE receiving T2ST without BGP MUP Extended community should consider the route malformed (Section 3.3.12) | SHOULD | 3.3.12 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.8-2` | When withdrawing T1ST, controller should attach Route Target Extended community for the corresponding Direct segment (Section 3.3.8) | SHOULD | 3.3.8 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.10-3` | When advertising T2ST, controller should attach BGP MUP Extended community for the Direct segment (Section 3.3.10) | SHOULD | 3.3.10 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.11-2` | When withdrawing T2ST, controller should attach BGP MUP Extended community and Route Target Extended community (Section 3.3.11) | SHOULD | 3.3.11 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-4-1` | RFC 5925 authentication should be used where authentication of BGP control packets is needed (Section 4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-4-2` | PEs and MUP Controller should not establish BGP sessions with untrusted domains without explicit configuration (Section 4) | SHOULD NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-4-3` | RFC 5925 procedures should be enforced at untrusted domain boundaries (Section 4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-4-4` | Establishing BGP sessions over encrypted paths should be considered to protect from eavesdropping (Section 4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-4-5` | PEs should impose an upper bound on number of routes stored to protect control plane load (Section 4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.1-2` | Implementation may log an error when unknown Route Types are ignored (Section 3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-5` | BGP speaker may have local configuration for using a Source address when Source Address Length is 0 in T1ST (Section 3.1.3.1) | MAY | 3.1.3.1 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.1-5` | ISD IP prefix may include a gNodeB address connecting to the PE (Section 3.3.1) | MAY | 3.3.1 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-BESS-MUP-SAFI-3.3.4-5` | DSD prefix SID function may be End.DT4/6 or End.DX4/6 (Section 3.3.4) | MAY | 3.3.4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-1`](#draft-ietf-bess-mup-safi-3.3.1-1) PE advertising ISD route must attach export BGP Route Target Extended Community of the associated routing instance (Section 3.3.1) | {gap}, no test | parseConfigRoute attaches only the extended communities the operator configures (internal/component/bgp/plugins/nlri/mup/config.go:66) and ze models no routing instance with export route targets, so no export route target is derived for an ISD advertisement | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-2`](#draft-ietf-bess-mup-safi-3.3.1-2) PE advertising ISD route must use IPv6 address of PE as nexthop in MP_REACH_NLRI (Section 3.3.1) | {gap}, no test | EncodeRoute takes the next-hop verbatim from the route command (internal/component/bgp/plugins/nlri/mup/encode.go:173-191) and accepts an IPv4 next-hop for an ISD route, so nothing requires the IPv6 address of the PE | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-3`](#draft-ietf-bess-mup-safi-3.3.1-3) ISD route update must have a prefix SID attribute (Section 3.3.1) | {gap}, no test | parseConfigRoute adds the Prefix-SID attribute only when the config supplies one (internal/component/bgp/plugins/nlri/mup/config.go:71), so an ISD route configured without a prefix SID is advertised without one | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.2-1`](#draft-ietf-bess-mup-safi-3.3.2-1) PE withdrawing ISD route must attach export BGP Route Target Extended Community (Section 3.3.2) | {gap}, no test | a MUP withdrawal is built as a bare MP_UNREACH_NLRI with no path attributes (internal/component/bgp/reactor/peer_rib_routes.go:182-197), so an ISD withdrawal carries no export route target | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-1`](#draft-ietf-bess-mup-safi-3.3.4-1) DSD address in NLRI must be a unique PE identifier (Section 3.3.4) | {gap}, no test | parseDSDFields accepts any parsable address as the DSD NLRI address (internal/component/bgp/plugins/nlri/mup/encode.go:301-310) and never checks that it identifies the PE uniquely | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-2`](#draft-ietf-bess-mup-safi-3.3.4-2) PE announcing DSD route must attach a BGP MUP Extended Community (Section 3.3.4) | {gap}, no test | parseConfigRoute attaches only the extended communities the operator configures (internal/component/bgp/plugins/nlri/mup/config.go:66), so a DSD advertisement carries a BGP MUP Extended Community only when the operator writes one into the route | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-3`](#draft-ietf-bess-mup-safi-3.3.4-3) PE advertising DSD route must use IPv6 address of PE as nexthop in MP_REACH_NLRI (Section 3.3.4) | {gap}, no test | EncodeRoute takes the next-hop verbatim from the route command (internal/component/bgp/plugins/nlri/mup/encode.go:173-191) and accepts an IPv4 next-hop for a DSD route, so nothing requires the IPv6 address of the PE | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-4`](#draft-ietf-bess-mup-safi-3.3.4-4) DSD route update must have a prefix SID attribute (Section 3.3.4) | {gap}, no test | parseConfigRoute adds the Prefix-SID attribute only when the config supplies one (internal/component/bgp/plugins/nlri/mup/config.go:71), so a DSD route configured without a prefix SID is advertised without one | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.5-1`](#draft-ietf-bess-mup-safi-3.3.5-1) BGP speaker announcing T1ST must attach a BGP MUP Extended Community (Section 3.3.5) | {gap}, no test | parseConfigRoute attaches only the extended communities the operator configures (internal/component/bgp/plugins/nlri/mup/config.go:66), so a Type 1 ST advertisement carries a BGP MUP Extended Community only when the operator writes one into the route | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.7-1`](#draft-ietf-bess-mup-safi-3.3.7-1) MUP Controller must set nexthop of T1ST route to the controller address (Section 3.3.7) | {gap}, no test | EncodeRoute takes the next-hop verbatim from the route command (internal/component/bgp/plugins/nlri/mup/encode.go:173-191) for a Type 1 ST route and ze runs no MUP Controller function that substitutes the controller address | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.10-1`](#draft-ietf-bess-mup-safi-3.3.10-1) Controller must attach Route Target Extended Community of routing instances in the PE for T2ST (Section 3.3.10) | {gap}, no test | parseConfigRoute attaches only the extended communities the operator configures (internal/component/bgp/plugins/nlri/mup/config.go:66) and ze models no routing instance with export route targets, so a Type 2 ST advertisement carries a route target only when the operator writes one into the route | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.10-2`](#draft-ietf-bess-mup-safi-3.3.10-2) Controller must set nexthop of T2ST route to MUP Controller address (Section 3.3.10) | {gap}, no test | EncodeRoute takes the next-hop verbatim from the route command (internal/component/bgp/plugins/nlri/mup/encode.go:173-191) for a Type 2 ST route and ze runs no MUP Controller function that substitutes the controller address | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-1`](#draft-ietf-bess-mup-safi-3.3.3-1) Receiver must ensure ISD Address field value is address of originator of locator in prefix SID attribute (Section 3.3.3) | {gap}, no test | DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) and ze reads no prefix SID locator anywhere in internal/component/bgp, so the ISD Address field is never checked against the originator of the prefix SID locator | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-2`](#draft-ietf-bess-mup-safi-3.3.3-2) On MP_UNREACH_NLRI, receiver must delete withdrawn ISD route from routing instance table (Section 3.3.3) | {gap}, no test | nlrisplit now registers SplitMUP for SAFI 85 (internal/core/bgp/nlri/nlrisplit/register.go), so insertPoolNLRIs stores a received MUP NLRI as an opaque entry keyed on the whole NLRI and removePoolNLRIs deletes exactly the NLRI a withdrawal names (internal/component/bgp/plugins/rib/rib.go). What remains is that ze models no MUP routing instance: the entry lives in the peer's Adj-RIB-In alone, and no RFC requirement tagged test drives an ISD withdrawal through it | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.6-1`](#draft-ietf-bess-mup-safi-3.3.6-1) Receiver must ensure DSD nexthop in MP_REACH_NLRI is identical to originator of locator in prefix SID attribute (Section 3.3.6) | {gap}, no test | DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) and ze reads no prefix SID locator anywhere in internal/component/bgp, so the DSD nexthop is never compared with the originator of the prefix SID locator | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.6-2`](#draft-ietf-bess-mup-safi-3.3.6-2) On MP_UNREACH_NLRI, receiver must delete withdrawn DSD route from routing instance table (Section 3.3.6) | {gap}, no test | nlrisplit now registers SplitMUP for SAFI 85 (internal/core/bgp/nlri/nlrisplit/register.go), so insertPoolNLRIs stores a received MUP NLRI as an opaque entry keyed on the whole NLRI and removePoolNLRIs deletes exactly the NLRI a withdrawal names (internal/component/bgp/plugins/rib/rib.go). What remains is that ze models no MUP routing instance: the entry lives in the peer's Adj-RIB-In alone, and no RFC requirement tagged test drives a DSD withdrawal through it | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.9-1`](#draft-ietf-bess-mup-safi-3.3.9-1) PE receiving T1ST routes in MP_UNREACH_NLRI must delete all routes from associated routing instance (Section 3.3.9) | {gap}, no test | nlrisplit now registers SplitMUP for SAFI 85 (internal/core/bgp/nlri/nlrisplit/register.go), so insertPoolNLRIs stores a received MUP NLRI as an opaque entry keyed on the whole NLRI and removePoolNLRIs deletes exactly the NLRI a withdrawal names (internal/component/bgp/plugins/rib/rib.go). The delete is one NLRI for one NLRI. Nothing reads the Type 1 ST route type to delete every route of the associated routing instance, and ze models no such instance | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.12-1`](#draft-ietf-bess-mup-safi-3.3.12-1) PE must handle T2ST without MUP Extended Community as treat-as-withdraw (Section 3.3.12) | {gap}, no test | DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) without consulting the UPDATE extended communities, so a Type 2 ST route arriving without the BGP MUP Extended Community is not treated as withdrawn | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.1-1`](#draft-ietf-bess-mup-safi-3.1.1-1) ISD with prefix length exceeding max for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.1) | {gap}, no test | ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the ISD prefix length, so a prefix length above 32 for AFI 1 or 128 for AFI 2 is accepted as a valid route | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.1-2`](#draft-ietf-bess-mup-safi-3.1.1-2) Speaker must skip malformed NLRIs and continue processing rest of Update message (Section 3.1.1) | {gap}, no test | ParseMUP applies no semantic validation (internal/component/bgp/plugins/nlri/mup/types.go:118-152), so no NLRI is ever classed malformed and skipped, and a truncated NLRI aborts the parse with ErrMUPTruncated (internal/component/bgp/plugins/nlri/mup/types.go:127-129) instead of continuing with the rest of the Update | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.2-1`](#draft-ietf-bess-mup-safi-3.1.2-1) DSD with wrong address size for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.2) | {gap}, no test | ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never measures the DSD address against the AFI, so a 4-octet address under AFI 2 or a 16-octet address under AFI 1 is accepted | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3-1`](#draft-ietf-bess-mup-safi-3.1.3-1) T1ST with prefix length exceeding max for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.3) | {gap}, no test | ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T1ST prefix length, so a prefix length above 32 for AFI 1 or 128 for AFI 2 is accepted as a valid route | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-1`](#draft-ietf-bess-mup-safi-3.1.3.1-1) T1ST with TEID=0: treat-as-withdraw per RFC 7606 (Section 3.1.3.1) | {gap}, no test | ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T1ST TEID, and parseTEIDWithBits maps an absent TEID to zero bits which writeT1STData then omits from the NLRI (internal/component/bgp/plugins/nlri/mup/encode.go:435-450, internal/component/bgp/plugins/nlri/mup/encode.go:396-399) | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-2`](#draft-ietf-bess-mup-safi-3.1.3.1-2) T1ST with invalid Endpoint Address Length (not 32 or 128): treat-as-withdraw (Section 3.1.3.1) | {gap}, no test | ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T1ST Endpoint Address Length, while writeT1STData derives it from the configured address (internal/component/bgp/plugins/nlri/mup/encode.go:404-409), so a received length other than 32 or 128 is accepted | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-3`](#draft-ietf-bess-mup-safi-3.1.3.1-3) T1ST with invalid Source Address Length (not 0, 32, or 128): treat-as-withdraw (Section 3.1.3.1) | {gap}, no test | ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T1ST Source Address Length, while writeT1STData derives it from the configured address (internal/component/bgp/plugins/nlri/mup/encode.go:411-416), so a received length other than 0, 32 or 128 is accepted | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4-1`](#draft-ietf-bess-mup-safi-3.1.4-1) T2ST with Endpoint Length exceeding max for AFI: treat-as-withdraw (Section 3.1.4) | {gap}, no test | ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T2ST Endpoint Length, so a combined length above 64 for AFI 1 or 160 for AFI 2 is accepted | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-1`](#draft-ietf-bess-mup-safi-3.1.4.1-1) T2ST with TEID=0: treat-as-withdraw per RFC 7606 (Section 3.1.4.1) | {gap}, no test | ParseMUP keeps the route-type-specific bytes opaque after the RD (internal/component/bgp/plugins/nlri/mup/types.go:137-149) and never reads the T2ST TEID, while writeT2STData writes whatever parseTEIDWithBits yields, zero included (internal/component/bgp/plugins/nlri/mup/encode.go:422-431) | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-4`](#draft-ietf-bess-mup-safi-3.1.3.1-4) T1ST NLRI architecture field MUST be encoded as specified for 3gpp-5g; otherwise treat-as-withdraw (Section 3.1.3.1) | {gap}, no test | writeMUPNLRI always writes architecture type 1 (internal/component/bgp/plugins/nlri/mup/encode.go:246) but ParseMUP accepts any architecture byte and keeps parsing (internal/component/bgp/plugins/nlri/mup/types.go:123), so a T1ST NLRI encoded for another architecture is not treated as withdraw | | [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-2`](#draft-ietf-bess-mup-safi-3.1.4.1-2) T2ST NLRI architecture field MUST be encoded as specified for 3gpp-5g; otherwise treat-as-withdraw (Section 3.1.4.1) | {gap}, no test | writeMUPNLRI always writes architecture type 1 (internal/component/bgp/plugins/nlri/mup/encode.go:246) but ParseMUP accepts any architecture byte and keeps parsing (internal/component/bgp/plugins/nlri/mup/types.go:123), so a T2ST NLRI encoded for another architecture is not treated as withdraw | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-3`](#draft-ietf-bess-mup-safi-3.3.3-3) ISD/DSD without prefix SID attribute: treat-as-withdraw (Section 3.3.3, 3.3.6) | {gap}, no test | DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) without consulting the UPDATE path attributes, so an ISD or DSD route arriving without a Prefix-SID attribute is not treated as withdrawn | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-4`](#draft-ietf-bess-mup-safi-3.3.3-4) Nexthop/locator mismatch on ISD/DSD: treat-as-withdraw (Section 3.3.3, 3.3.6) | {gap}, no test | DecodeNLRIHex surfaces only route type, architecture type and RD from a received MUP NLRI (internal/component/bgp/plugins/nlri/mup/mup.go:56-73) and ze reads no prefix SID locator anywhere in internal/component/bgp, so a mismatch between the nexthop and the locator originator is never detected on ISD or DSD routes | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-4`](#draft-ietf-bess-mup-safi-3.3.1-4) ISD prefix SID function MUST be GTP4.E if BGP AFI is IPv4, or MUST be GTP6.E if BGP AFI is IPv6 (Section 3.3.1) | {gap}, no test | parseConfigRoute adds the Prefix-SID attribute only when the config supplies one (internal/component/bgp/plugins/nlri/mup/config.go:71) and passes its bytes through unchanged, and ze decodes no SRv6 endpoint function, so the GTP4.E/GTP6.E function is never tied to the BGP AFI | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.5-2`](#draft-ietf-bess-mup-safi-3.3.5-2) When withdrawing DSD route, BGP speaker MUST attach a BGP MUP Extended community of the associated routing instance (Section 3.3.5) | {gap}, no test | a MUP withdrawal is built as a bare MP_UNREACH_NLRI with no path attributes (internal/component/bgp/reactor/peer_rib_routes.go:182-197), so a DSD withdrawal carries no BGP MUP Extended Community | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.8-1`](#draft-ietf-bess-mup-safi-3.3.8-1) Controller MUST advertise the withdrawal of the Type 1 ST route (Section 3.3.8) | {gap}, no test | no MUP NLRI can reach the family-generic MP_UNREACH encoder (internal/component/bgp/reactor/peer_rib_routes.go:170). Its only callers take the NLRI from a PeerOpWithdraw queue entry (internal/component/bgp/reactor/peer_initial_sync.go:237, :377) filled by QueueWithdraw (internal/component/bgp/reactor/peer.go:886-893), and the two withdrawal entry points that feed it parse no SAFI 85: text mode rejects the family in isSupportedFamily, whose list stops at SAFI 73 (internal/component/bgp/plugins/cmd/update/update_text_nlri.go:375-403), and the announce/withdraw registry builds only unicast and FlowSpec NLRIs (internal/component/bgp/plugins/cmd/announce/announce.go:257, :304, :415). NewMUP and NewMUPFull (internal/component/bgp/plugins/nlri/mup/types.go:93, :103) have no non-test caller, and nlrisplit registers no SAFI 85 splitter (internal/core/bgp/nlri/nlrisplit/register.go:9-24) so no received MUP route is stored to be withdrawn either | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.7-3`](#draft-ietf-bess-mup-safi-3.3.7-3) Controller MUST advertise the Type 1 ST route with Destination prefix, TEID, QFI, Endpoint Address, and optionally Source Address (Section 3.3.7) | {gap}, no test | parseT1STFields requires only the destination prefix (internal/component/bgp/plugins/nlri/mup/encode.go:313-316) and writeT1STData omits the TEID field when no TEID is configured and the Endpoint Address field when no endpoint is configured (internal/component/bgp/plugins/nlri/mup/encode.go:396-409), so a Type 1 ST route is advertised without them | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.9-2`](#draft-ietf-bess-mup-safi-3.3.9-2) PE receiving T1ST in MP_UNREACH_NLRI without Source address MUST delete all matching T1ST routes with different Source addresses (Section 3.3.9) | {gap}, no test | nlrisplit now registers SplitMUP for SAFI 85 (internal/core/bgp/nlri/nlrisplit/register.go), so insertPoolNLRIs stores a received MUP NLRI as an opaque entry keyed on the whole NLRI and removePoolNLRIs deletes exactly the NLRI a withdrawal names (internal/component/bgp/plugins/rib/rib.go). The opaque key is the whole NLRI, Source address included, so a Source-less Type 1 ST withdrawal matches no stored entry. No wildcard delete over differing Source addresses exists | | [`DRAFT-IETF-BESS-MUP-SAFI-3.3.11-1`](#draft-ietf-bess-mup-safi-3.3.11-1) Controller MUST advertise the withdrawal of the Type 2 ST route (Section 3.3.11) | {gap}, no test | the same missing path as DRAFT-IETF-BESS-MUP-SAFI-3.3.8-1 -- the family-generic MP_UNREACH encoder (internal/component/bgp/reactor/peer_rib_routes.go:170) is reachable only through PeerOpWithdraw (internal/component/bgp/reactor/peer.go:886-893, internal/component/bgp/reactor/peer_initial_sync.go:237, :377), and neither withdrawal entry point produces a SAFI 85 NLRI: isSupportedFamily omits it (internal/component/bgp/plugins/cmd/update/update_text_nlri.go:375-403) and the announce registry builds only unicast and FlowSpec NLRIs (internal/component/bgp/plugins/cmd/announce/announce.go:257, :304, :415) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-1`](#draft-ietf-bess-mup-safi-3.3.1-1) PE advertising ISD route must attach export BGP Route Target Extended Community of the associated routing instance (Section 3.3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.1-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-2`](#draft-ietf-bess-mup-safi-3.3.1-2) PE advertising ISD route must use IPv6 address of PE as nexthop in MP_REACH_NLRI (Section 3.3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.1-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-3`](#draft-ietf-bess-mup-safi-3.3.1-3) ISD route update must have a prefix SID attribute (Section 3.3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.1-3, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.2-1`](#draft-ietf-bess-mup-safi-3.3.2-1) PE withdrawing ISD route must attach export BGP Route Target Extended Community (Section 3.3.2) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.2-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-1`](#draft-ietf-bess-mup-safi-3.3.4-1) DSD address in NLRI must be a unique PE identifier (Section 3.3.4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.4-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-2`](#draft-ietf-bess-mup-safi-3.3.4-2) PE announcing DSD route must attach a BGP MUP Extended Community (Section 3.3.4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.4-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-3`](#draft-ietf-bess-mup-safi-3.3.4-3) PE advertising DSD route must use IPv6 address of PE as nexthop in MP_REACH_NLRI (Section 3.3.4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.4-3, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.4-4`](#draft-ietf-bess-mup-safi-3.3.4-4) DSD route update must have a prefix SID attribute (Section 3.3.4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.4-4, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.5-1`](#draft-ietf-bess-mup-safi-3.3.5-1) BGP speaker announcing T1ST must attach a BGP MUP Extended Community (Section 3.3.5) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.5-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.7-1`](#draft-ietf-bess-mup-safi-3.3.7-1) MUP Controller must set nexthop of T1ST route to the controller address (Section 3.3.7) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.7-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.7-2`](#draft-ietf-bess-mup-safi-3.3.7-2) Controller must announce T1ST route using AFI of the route and SAFI BGP-MUP to all BGP speakers in SRv6 domain (Section 3.3.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFCMUPAnnounceUsesRouteAFIWithMUPSAFI`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc_mup_safi_test.go#L207) | unit/verify | unproven | ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.10-1`](#draft-ietf-bess-mup-safi-3.3.10-1) Controller must attach Route Target Extended Community of routing instances in the PE for T2ST (Section 3.3.10) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.10-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.10-2`](#draft-ietf-bess-mup-safi-3.3.10-2) Controller must set nexthop of T2ST route to MUP Controller address (Section 3.3.10) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.10-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1-1`](#draft-ietf-bess-mup-safi-3.1-1) Unknown Route Types for supported Architecture Types must be silently ignored (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFCMUPUnknownRouteTypeIsNotRejected`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc_mup_safi_test.go#L161) | unit/verify | unproven | ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-1`](#draft-ietf-bess-mup-safi-3.3.3-1) Receiver must ensure ISD Address field value is address of originator of locator in prefix SID attribute (Section 3.3.3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.3-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-2`](#draft-ietf-bess-mup-safi-3.3.3-2) On MP_UNREACH_NLRI, receiver must delete withdrawn ISD route from routing instance table (Section 3.3.3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.3-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.6-1`](#draft-ietf-bess-mup-safi-3.3.6-1) Receiver must ensure DSD nexthop in MP_REACH_NLRI is identical to originator of locator in prefix SID attribute (Section 3.3.6) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.6-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.6-2`](#draft-ietf-bess-mup-safi-3.3.6-2) On MP_UNREACH_NLRI, receiver must delete withdrawn DSD route from routing instance table (Section 3.3.6) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.6-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.9-1`](#draft-ietf-bess-mup-safi-3.3.9-1) PE receiving T1ST routes in MP_UNREACH_NLRI must delete all routes from associated routing instance (Section 3.3.9) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.9-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.12-1`](#draft-ietf-bess-mup-safi-3.3.12-1) PE must handle T2ST without MUP Extended Community as treat-as-withdraw (Section 3.3.12) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.12-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.1-1`](#draft-ietf-bess-mup-safi-3.1.1-1) ISD with prefix length exceeding max for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.1-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.1-2`](#draft-ietf-bess-mup-safi-3.1.1-2) Speaker must skip malformed NLRIs and continue processing rest of Update message (Section 3.1.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.1-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.2-1`](#draft-ietf-bess-mup-safi-3.1.2-1) DSD with wrong address size for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.2) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.2-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3-1`](#draft-ietf-bess-mup-safi-3.1.3-1) T1ST with prefix length exceeding max for AFI: treat-as-withdraw per RFC 7606 (Section 3.1.3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.3-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-1`](#draft-ietf-bess-mup-safi-3.1.3.1-1) T1ST with TEID=0: treat-as-withdraw per RFC 7606 (Section 3.1.3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-2`](#draft-ietf-bess-mup-safi-3.1.3.1-2) T1ST with invalid Endpoint Address Length (not 32 or 128): treat-as-withdraw (Section 3.1.3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-3`](#draft-ietf-bess-mup-safi-3.1.3.1-3) T1ST with invalid Source Address Length (not 0, 32, or 128): treat-as-withdraw (Section 3.1.3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-3, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4-1`](#draft-ietf-bess-mup-safi-3.1.4-1) T2ST with Endpoint Length exceeding max for AFI: treat-as-withdraw (Section 3.1.4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.4-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-1`](#draft-ietf-bess-mup-safi-3.1.4.1-1) T2ST with TEID=0: treat-as-withdraw per RFC 7606 (Section 3.1.4.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-4`](#draft-ietf-bess-mup-safi-3.1.3.1-4) T1ST NLRI architecture field MUST be encoded as specified for 3gpp-5g; otherwise treat-as-withdraw (Section 3.1.3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.3.1-4, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-2`](#draft-ietf-bess-mup-safi-3.1.4.1-2) T2ST NLRI architecture field MUST be encoded as specified for 3gpp-5g; otherwise treat-as-withdraw (Section 3.1.4.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.1.4.1-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-3`](#draft-ietf-bess-mup-safi-3.3.3-3) ISD/DSD without prefix SID attribute: treat-as-withdraw (Section 3.3.3, 3.3.6) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.3-3, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.3-4`](#draft-ietf-bess-mup-safi-3.3.3-4) Nexthop/locator mismatch on ISD/DSD: treat-as-withdraw (Section 3.3.3, 3.3.6) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.3-4, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3-1`](#draft-ietf-bess-mup-safi-3.3-1) PE and MUP Controller MUST establish a BGP session to exchange BGP-MUP NLRIs for both IPv4 and IPv6 AFIs (Section 3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFCMUPRejectsNonMUPFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc_mup_safi_test.go#L133) | unit/verify | unproven | | positive | [`TestRFCMUPFamiliesCoverBothAFIs`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/mup/rfc_mup_safi_test.go#L84) | unit/verify | unproven | ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.1-4`](#draft-ietf-bess-mup-safi-3.3.1-4) ISD prefix SID function MUST be GTP4.E if BGP AFI is IPv4, or MUST be GTP6.E if BGP AFI is IPv6 (Section 3.3.1) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.1-4, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.5-2`](#draft-ietf-bess-mup-safi-3.3.5-2) When withdrawing DSD route, BGP speaker MUST attach a BGP MUP Extended community of the associated routing instance (Section 3.3.5) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.5-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.8-1`](#draft-ietf-bess-mup-safi-3.3.8-1) Controller MUST advertise the withdrawal of the Type 1 ST route (Section 3.3.8) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.8-1, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.7-3`](#draft-ietf-bess-mup-safi-3.3.7-3) Controller MUST advertise the Type 1 ST route with Destination prefix, TEID, QFI, Endpoint Address, and optionally Source Address (Section 3.3.7) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.7-3, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.9-2`](#draft-ietf-bess-mup-safi-3.3.9-2) PE receiving T1ST in MP_UNREACH_NLRI without Source address MUST delete all matching T1ST routes with different Source addresses (Section 3.3.9) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.9-2, so no unit is bound to it. ### [`DRAFT-IETF-BESS-MUP-SAFI-3.3.11-1`](#draft-ietf-bess-mup-safi-3.3.11-1) Controller MUST advertise the withdrawal of the Type 2 ST route (Section 3.3.11) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-BESS-MUP-SAFI-3.3.11-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for DRAFT-IETF-BESS-MUP-SAFI, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes DRAFT-IETF-BESS-MUP-SAFI, so its obligations are stated where they were written. --- ### Page: DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE - BGP BFD Strict-Mode https://ze-software.net/quality/rfc-compliance/draft-ietf-idr-bgp-bfd-strict-mode/ # DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE - BGP BFD Strict-Mode Supported. Every requirement this repository extracted from DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 4 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 100.0% | 8 of 8 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 4 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 4 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 8 | | Tagged units | 8 | | Recorded audit verdicts | 0 | | Discrimination records | 8 | | Summary | `rfc/short/draft-ietf-idr-bgp-bfd-strict-mode.md` | | Requirement shard | `rfc/requirements/draft-ietf-idr-bgp-bfd-strict-mode.md` | | RFC text | `rfc/drafts/draft-ietf-idr-bgp-bfd-strict-mode.txt` | ## Enrolment Enrolled: BFD Strict-Mode for BGP (capability code 74): four MUST-level requirements, all four implemented and each proven by a tagged test. Ze advertises the capability from the peer's own bfd block (parsePeerFromTree, internal/component/bgp/reactor/config.go), negotiates it as BfdStrictNegotiated (Negotiate, internal/core/bgp/capability/negotiated.go), and runs the Section 8 FSM procedures in internal/component/bgp/fsm/fsm.go with their wire half in internal/component/bgp/reactor/session_bfd_strict.go. The two Event 20 sections, 8.3.5 and 8.4.5, are conditional on the RFC 4271 DelayOpenTimer, which Ze does not implement (permitted by RFC 4271 Section 8.2.1.3), and they carry no MUST-level keyword site. ## What the public ledger says **Status:** Supported **What the ledger says is covered** - 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 `internal/component/bgp/fsm/`. 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 ([`internal/component/bgp/reactor/session_bfd_strict.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict.go)). 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. **What the ledger says remains:** - ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-4-1`](#draft-ietf-idr-bgp-bfd-strict-mode-4-1), [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-6-1`](#draft-ietf-idr-bgp-bfd-strict-mode-6-1), [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-10-1`](#draft-ietf-idr-bgp-bfd-strict-mode-10-1), [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-10-2`](#draft-ietf-idr-bgp-bfd-strict-mode-10-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-4-1` | "If BfdEnabled is FALSE, this event MUST NOT occur. When BFD has been disabled, the local system will trigger a BfdAdminDown event instead" (§4, Event 35) | MUST NOT | 4 | **positive:** `unit/verify` [`TestSessionBFDStrictConfigChangedUsesConfigSubcode`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L413). **negative:** `unit/verify` [`TestSessionBFDStrictConfigChangedWithBFDDisabled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L495) | | `DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-6-1` | "A BGP speaker which supports capabilities advertisement and has BFD strict-mode enabled MUST include the BFD Strict-Mode Capability in its OPEN message" (§6) | MUST | 6 | **positive:** `unit/verify` [`TestBFDSettingsStrictParse`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_bfd_strict_test.go#L57). **negative:** `unit/verify` [`TestBFDSettingsStrictDisabledAdvertisesNothing`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_bfd_strict_test.go#L108) | | `DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-10-1` | "To avoid deadlock when utilizing both BFD hold-down and BFD strict-mode, when strict-mode is enabled for a peer, the BGP FSM MUST be enabled" (§10) | MUST | 10 | **positive:** `unit/verify` [`TestSessionBFDStrictWithholdsKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L185). **negative:** `unit/verify` [`TestSessionBFDStrictSendsKeepaliveOnBFDUp`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L221) | | `DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-10-2` | "That is, BFD hold-down procedures MUST NOT prevent BGP from establishing a connection with the remote BGP speaker" (§10) | MUST NOT | 10 | **positive:** `unit/verify` [`TestSessionBFDStrictWithholdsKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L190). **negative:** `unit/verify` [`TestSessionBFDStrictEstablishesWhenPeerDoesNotAdvertise`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L289) | ## Gaps and untested MUSTs DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-4-1`](#draft-ietf-idr-bgp-bfd-strict-mode-4-1) "If BfdEnabled is FALSE, this event MUST NOT occur. When BFD has been disabled, the local system will trigger a BfdAdminDown event instead" (§4, Event 35) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSessionBFDStrictConfigChangedWithBFDDisabled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L495) | unit/verify | revert, verified | | positive | [`TestSessionBFDStrictConfigChangedUsesConfigSubcode`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L413) | unit/verify | revert, verified | ### [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-6-1`](#draft-ietf-idr-bgp-bfd-strict-mode-6-1) "A BGP speaker which supports capabilities advertisement and has BFD strict-mode enabled MUST include the BFD Strict-Mode Capability in its OPEN message" (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBFDSettingsStrictDisabledAdvertisesNothing`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_bfd_strict_test.go#L108) | unit/verify | revert, verified | | positive | [`TestBFDSettingsStrictParse`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_bfd_strict_test.go#L57) | unit/verify | revert, verified | ### [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-10-1`](#draft-ietf-idr-bgp-bfd-strict-mode-10-1) "To avoid deadlock when utilizing both BFD hold-down and BFD strict-mode, when strict-mode is enabled for a peer, the BGP FSM MUST be enabled" (§10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSessionBFDStrictSendsKeepaliveOnBFDUp`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L221) | unit/verify | revert, verified | | positive | [`TestSessionBFDStrictWithholdsKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L185) | unit/verify | revert, verified | ### [`DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE-10-2`](#draft-ietf-idr-bgp-bfd-strict-mode-10-2) "That is, BFD hold-down procedures MUST NOT prevent BGP from establishing a connection with the remote BGP speaker" (§10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSessionBFDStrictEstablishesWhenPeerDoesNotAdvertise`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L289) | unit/verify | revert, verified | | positive | [`TestSessionBFDStrictWithholdsKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_bfd_strict_test.go#L190) | unit/verify | revert, verified | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement (spec-bgp-bfd-strict) | | Signed off | 2026-09-08 | | Register | rfc2119 | | Source | rfc/drafts/draft-ietf-idr-bgp-bfd-strict-mode.txt | | Source fingerprint | d68744366556d25a | | Record | rfc/extraction/draft-ietf-idr-bgp-bfd-strict-mode.json | | Mapped sentences | 4 | | Declined as scope | 0 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, abstract, status of this memo, copyright notice and table of contents. | | `1` | not stated | 0 | walked | not stated | | `2` | not stated | 0 | walked | not stated | | `3` | not stated | 0 | walked | not stated | | `4` | not stated | 1 | walked | not stated | | `5` | not stated | 0 | walked | not stated | | `6` | not stated | 1 | walked | not stated | | `7` | not stated | 0 | walked | not stated | | `8` | not stated | 0 | walked | not stated | | `8.1` | not stated | 0 | walked | not stated | | `8.2` | not stated | 0 | walked | not stated | | `8.3` | not stated | 0 | walked | not stated | | `8.3.1` | not stated | 0 | walked | not stated | | `8.3.2` | not stated | 0 | walked | not stated | | `8.3.3` | not stated | 0 | walked | not stated | | `8.3.4` | not stated | 0 | walked | not stated | | `8.3.5` | not stated | 0 | walked | not stated | | `8.4` | not stated | 0 | walked | not stated | | `8.4.1` | not stated | 0 | walked | not stated | | `8.4.2` | not stated | 0 | walked | not stated | | `8.4.3` | not stated | 0 | walked | not stated | | `8.4.4` | not stated | 0 | walked | not stated | | `8.4.5` | not stated | 0 | walked | not stated | | `8.5` | not stated | 0 | walked | not stated | | `8.5.1` | not stated | 0 | walked | not stated | | `8.5.2` | not stated | 0 | walked | not stated | | `8.5.3` | not stated | 0 | walked | not stated | | `8.5.4` | not stated | 0 | walked | not stated | | `8.5.5` | not stated | 0 | walked | not stated | | `8.5.6` | not stated | 0 | walked | not stated | | `8.6` | not stated | 0 | walked | not stated | | `8.6.1` | not stated | 0 | walked | not stated | | `8.6.2` | not stated | 0 | walked | not stated | | `8.6.3` | not stated | 0 | walked | not stated | | `8.7` | not stated | 0 | walked | not stated | | `8.7.1` | not stated | 0 | walked | not stated | | `8.7.2` | not stated | 0 | walked | not stated | | `8.7.3` | not stated | 0 | walked | not stated | | `9` | not stated | 0 | walked | not stated | | `10` | not stated | 2 | walked | not stated | | `11` | not stated | 0 | walked | not stated | | `12` | not stated | 0 | walked | not stated | | `13` | not stated | 0 | skipped (iana) | IANA Considerations: the registrations this document requests. They bind IANA, not an implementation. | | `13.1` | not stated | 0 | skipped (iana) | IANA Considerations: the registrations this document requests. They bind IANA, not an implementation. | | `13.2` | not stated | 0 | skipped (iana) | IANA Considerations: the registrations this document requests. They bind IANA, not an implementation. | | `13.3` | not stated | 0 | skipped (iana) | IANA Considerations: the registrations this document requests. They bind IANA, not an implementation. | | `14` | Acknowledgement of reviewers | 0 | skipped (acknowledgements) | Acknowledgement of reviewers. | | `15` | Reference list entries | 0 | skipped (references) | Reference list entries. | | `16` | Reference list entries | 0 | skipped (references) | Reference list entries. | | `A` | RFC 7942 implementation status of other vendors | 0 | skipped (appendix-non-normative) | RFC 7942 implementation status of other vendors. Removed on publication by the document's own note. | | `A.1` | RFC 7942 implementation status of other vendors | 0 | skipped (appendix-non-normative) | RFC 7942 implementation status of other vendors. Removed on publication by the document's own note. | | `A.2` | RFC 7942 implementation status of other vendors | 0 | skipped (appendix-non-normative) | RFC 7942 implementation status of other vendors. Removed on publication by the document's own note. | | `A.3` | RFC 7942 implementation status of other vendors | 0 | skipped (appendix-non-normative) | RFC 7942 implementation status of other vendors. Removed on publication by the document's own note. | ### Excluded sentences The walk over DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE declined no sentence: every site it found is mapped to a requirement. ## Superseded No document obsoletes DRAFT-IETF-IDR-BGP-BFD-STRICT-MODE, so its obligations are stated where they were written. --- ### Page: DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY - Link-Local Next Hop Capability for BGP https://ze-software.net/quality/rfc-compliance/draft-ietf-idr-linklocal-capability/ # DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY - Link-Local Next Hop Capability for BGP Partial. Every requirement this repository extracted from DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 13 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 13 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 13 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 13 | of 25 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 13 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 13 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 13 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 13 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 100.0% | 13 of 13 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 13 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 25 | | Gated MUST-level | 13 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 13 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/draft-ietf-idr-linklocal-capability.md` | | Requirement shard | `rfc/requirements/draft-ietf-idr-linklocal-capability.md` | | RFC text | `rfc/drafts/draft-ietf-idr-linklocal-capability.txt` | ## Enrolment Enrolled: Link-Local Next Hop capability for BGP (code 77): twelve MUST-level requirements, all conditioned by Section 2 on the capability being negotiated. Ze advertises the capability (extractLLNHCapabilities, internal/component/bgp/plugins/llnh/llnh.go) and implements none of the 16-octet Link-Local-only next-hop procedures behind it: linkScope.linkLocalNextHop and applyLinkLocalNextHop (internal/component/bgp/reactor/link_scope.go) emit only the RFC 2545 forms, and parseNextHops (internal/core/bgp/attribute/mpnlri.go) runs no fe80::/10 classification on a 16-octet next hop. 3-2 is met by the RFC 2545 32-octet path; 4-2 and 4-8 are met because a link-local is appended only when the peer shares a connected subnet. The rest are outstanding and named in the coverage rollup. ## What the public ledger says **Status:** Partial **What the ledger says is covered** Capability declaration only. `extractLLNHCapabilities` ([`internal/component/bgp/plugins/llnh/llnh.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/llnh/llnh.go)) advertises the empty code 77 capability for a peer or group whose config carries a `link-local-nexthop` key that is not `disable`. The draft's own procedures are the 16-octet Link-Local-ONLY Next Hop form, and ze produces neither side of it. Send: `linkScope.linkLocalNextHop` and `applyLinkLocalNextHop` ([`internal/component/bgp/reactor/link_scope.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/link_scope.go)) emit the RFC 2545 forms only, a 16-octet GLOBAL address or the 32-octet global-then-link-local pair, and `attribute.ValidateGlobalNextHop` ([`internal/core/bgp/attribute/nexthop_form.go`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/nexthop_form.go)) refuses a link-local address in the first slot. Receive: `parseNextHops` ([`internal/core/bgp/attribute/mpnlri.go`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri.go)) performs no `fe80::/10` test, so a 16-octet link-local-only Next Hop is read as a Global IPv6 next hop. `parseCapability` ([`internal/core/bgp/capability/capability.go`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability.go)) has no case for code 77, so ze records no negotiated state and no ze path is conditioned on the capability. The requirements are listed per line in [`rfc/short/draft-ietf-idr-linklocal-capability.md`](https://github.com/ze-software/ze/blob/main/rfc/short/draft-ietf-idr-linklocal-capability.md) and the walk is bounded by [`rfc/extraction/draft-ietf-idr-linklocal-capability.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/draft-ietf-idr-linklocal-capability.json). **What the ledger says remains:** - ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 13 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **13** | every gated MUST falls in exactly one bucket above | **No test and no annotation (13):** [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-1`](#draft-ietf-idr-linklocal-capability-3-1), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-2`](#draft-ietf-idr-linklocal-capability-3-2), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-1`](#draft-ietf-idr-linklocal-capability-4-1), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-2`](#draft-ietf-idr-linklocal-capability-4-2), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-3`](#draft-ietf-idr-linklocal-capability-4-3), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-4`](#draft-ietf-idr-linklocal-capability-4-4), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-5`](#draft-ietf-idr-linklocal-capability-4-5), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-6`](#draft-ietf-idr-linklocal-capability-4-6), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-7`](#draft-ietf-idr-linklocal-capability-4-7), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-8`](#draft-ietf-idr-linklocal-capability-4-8), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-9`](#draft-ietf-idr-linklocal-capability-4-9), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-5-1`](#draft-ietf-idr-linklocal-capability-5-1), [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-6-1`](#draft-ietf-idr-linklocal-capability-6-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-1-1` | "BGP speakers SHOULD NOT advertise a route whose Next Hop is a Link-Local address that is in the tentative state (Section 5.4 of [RFC4862]); this applies both to a first-party Next Hop (the speaker's own Link-Local address) and to a third-party Next Hop re-advertised from another peer" (§1) | SHOULD NOT | 1 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-2-1` | "A BGP speaker that is willing to use (send and receive) IPv6 Link-Local-only next hops SHOULD advertise the Link-Local Next Hop Capability to its peers only when: 1. It is capable of sending IPv6 Link-Local-only next hops for a route. 2. IPv6 Link-Local neighbors are associated with interfaces as part of their configuration to assist in determining the interface scope of received IPv6 Link-Local-only next hops" (§2) | SHOULD | 2 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-1` | "If an implementation intends to send a single IPv6 Link-Local forwarding address in the Next Hop field of the MP_REACH_NLRI, it MUST set the length of the Next Hop field to 16 and include only the IPv6 Link-Local address in the Next Hop field" (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-2` | "If an implementation intends to send both a IPv6 Global and Link-Local forwarding address in the Next Hop field of the MP_REACH_NLRI, it MUST set the length of the Next Hop field to 32 and include both the IPv6 Global and Link-Local addresses in the Next Hop field" (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-1` | "If, after completing these procedures, there are no IPv6 next hop addresses included in the next hop, the BGP route MUST not be advertised to its peer" (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-2` | "If the internal peer is more than one IP hop away, the BGP speaker MUST NOT include a Link-Local IPv6 next hop" (§4) | MUST NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-3` | "If the route is directly connected to the speaker, or if the interface address of the router through which the announced network is reachable for the speaker is the internal peer's address, the next hop MUST include its own Link-Local IPv6 address" (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-4` | "If, after evaluating the above procedures, there are no IPv6 next hops included with the route, the route MUST NOT be announced to the remote BGP speaker" (§4) | MUST NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-5` | "A Route Reflector (RR) reflecting a route with a link-local-only next hop MUST NOT advertise that route to a client unless the client shares the same link-layer segment as the original advertiser" (§4) | MUST NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-6` | "For all other clients, the RR MUST either rewrite the next hop to its own address (next-hop-self) or consider the route ineligible for advertisement to that specific peer" (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-7` | "If no next hops are included, the route MUST NOT be announced (treat-as-withdraw)" (§4) | MUST NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-8` | "Link-Local IPv6 next hops MUST NOT be included" for an external peer that "is multiple IP hops away from the speaker (aka \\"multihop EBGP\\")" (§4) | MUST NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-9` | "If a Global IPv6 next hop is not included, the route MUST NOT be advertised to the external peer (treat-as-withdraw)" (§4) | MUST NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-10` | "When sending a message to an internal peer, if the route is not locally-originated, the BGP speaker SHOULD NOT modify the Global IPv6 next hop, if one is present, unless it has been explicitly configured to announce its own IP address as the next hop" (§4) | SHOULD NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-11` | "implementations SHOULD log this suppression, or otherwise expose it through operator notification (e.g., via BMP or YANG telemetry), so that unexpected reachability gaps can be detected" (§4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-12` | "If the external peer is one IP hop away, the announcing BGP speaker SHOULD include a Link-Local IPv6 next hop" (§4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-13` | "If a BGP speaker receives a route with a link-local-only next hop, the route SHOULD be considered unusable for forwarding, consistent with the next-hop resolvability requirements described in [RFC4271]" (§4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-14` | "By default, the BGP speaker SHOULD use the Global IPv6 address of the interface that the speaker uses in the next hop to establish the BGP connection to peer X" (§4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-5-1` | "When this combination has not been negotiated, a sender MUST follow the rules in Section 3 of [RFC8950] and encode the Next Hop as 32 octets" (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-6-1` | "If the Next Hop field is malformed, the implementation MUST handle the malformed UPDATE message using the approach of \\"treat-as-withdraw\\", as described in section 7.3 of [RFC7606]" (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-6-2` | "Receivers SHOULD use the second Link-Local IPv6 address for forwarding, because the second slot is the position that carries the Link-Local address in the conforming Global-then-Link-Local layout defined by [RFC2545], and thus is the value the sender most likely intended as the Link-Local next hop" (§6) | SHOULD | 6 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-6-3` | "If the Next Hop field is properly formed, but the IPv6 Link-Local next hop is not reachable (as determined by an examination of the IPv6 neighbor table), the route SHOULD be considered unusable for forwarding purposes, in accordance with the next hop resolvability conditions described in [RFC4271]" (§6) | SHOULD | 6 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-7-1` | "Implementations SHOULD support BGP Add-Path [RFC7911] and Extended Next-Hop Encoding [RFC8950] to ensure full path utilization in IPv4-over-IPv6 underlays" (§7) | SHOULD | 7 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-7-2` | "Implementations SHOULD provide specific telemetry via the BGP Monitoring Protocol (BMP) [RFC7854] or a BGP YANG model (e.g., [I-D.ietf-idr-bgp-model]) to expose the state of link-local capability negotiation" (§7) | SHOULD | 7 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-7-3` | "implementations SHOULD treat a change in the local Link-Local address as a session reset rather than as a graceful restart event" (§7) | SHOULD | 7 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-1`](#draft-ietf-idr-linklocal-capability-3-1) "If an implementation intends to send a single IPv6 Link-Local forwarding address in the Next Hop field of the MP_REACH_NLRI, it MUST set the length of the Next Hop field to 16 and include only the IPv6 Link-Local address in the Next Hop field" (§3) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-2`](#draft-ietf-idr-linklocal-capability-3-2) "If an implementation intends to send both a IPv6 Global and Link-Local forwarding address in the Next Hop field of the MP_REACH_NLRI, it MUST set the length of the Next Hop field to 32 and include both the IPv6 Global and Link-Local addresses in the Next Hop field" (§3) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-1`](#draft-ietf-idr-linklocal-capability-4-1) "If, after completing these procedures, there are no IPv6 next hop addresses included in the next hop, the BGP route MUST not be advertised to its peer" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-2`](#draft-ietf-idr-linklocal-capability-4-2) "If the internal peer is more than one IP hop away, the BGP speaker MUST NOT include a Link-Local IPv6 next hop" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-3`](#draft-ietf-idr-linklocal-capability-4-3) "If the route is directly connected to the speaker, or if the interface address of the router through which the announced network is reachable for the speaker is the internal peer's address, the next hop MUST include its own Link-Local IPv6 address" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-4`](#draft-ietf-idr-linklocal-capability-4-4) "If, after evaluating the above procedures, there are no IPv6 next hops included with the route, the route MUST NOT be announced to the remote BGP speaker" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-5`](#draft-ietf-idr-linklocal-capability-4-5) "A Route Reflector (RR) reflecting a route with a link-local-only next hop MUST NOT advertise that route to a client unless the client shares the same link-layer segment as the original advertiser" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-6`](#draft-ietf-idr-linklocal-capability-4-6) "For all other clients, the RR MUST either rewrite the next hop to its own address (next-hop-self) or consider the route ineligible for advertisement to that specific peer" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-7`](#draft-ietf-idr-linklocal-capability-4-7) "If no next hops are included, the route MUST NOT be announced (treat-as-withdraw)" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-8`](#draft-ietf-idr-linklocal-capability-4-8) "Link-Local IPv6 next hops MUST NOT be included" for an external peer that "is multiple IP hops away from the speaker (aka \\"multihop EBGP\\")" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-9`](#draft-ietf-idr-linklocal-capability-4-9) "If a Global IPv6 next hop is not included, the route MUST NOT be advertised to the external peer (treat-as-withdraw)" (§4) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-5-1`](#draft-ietf-idr-linklocal-capability-5-1) "When this combination has not been negotiated, a sender MUST follow the rules in Section 3 of [RFC8950] and encode the Next Hop as 32 octets" (§5) | no test | no test carries this requirement id | | [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-6-1`](#draft-ietf-idr-linklocal-capability-6-1) "If the Next Hop field is malformed, the implementation MUST handle the malformed UPDATE message using the approach of \\"treat-as-withdraw\\", as described in section 7.3 of [RFC7606]" (§6) | no test | no test carries this requirement id | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-1`](#draft-ietf-idr-linklocal-capability-3-1) "If an implementation intends to send a single IPv6 Link-Local forwarding address in the Next Hop field of the MP_REACH_NLRI, it MUST set the length of the Next Hop field to 16 and include only the IPv6 Link-Local address in the Next Hop field" (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-1, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-2`](#draft-ietf-idr-linklocal-capability-3-2) "If an implementation intends to send both a IPv6 Global and Link-Local forwarding address in the Next Hop field of the MP_REACH_NLRI, it MUST set the length of the Next Hop field to 32 and include both the IPv6 Global and Link-Local addresses in the Next Hop field" (§3) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-3-2, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-1`](#draft-ietf-idr-linklocal-capability-4-1) "If, after completing these procedures, there are no IPv6 next hop addresses included in the next hop, the BGP route MUST not be advertised to its peer" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-1, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-2`](#draft-ietf-idr-linklocal-capability-4-2) "If the internal peer is more than one IP hop away, the BGP speaker MUST NOT include a Link-Local IPv6 next hop" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-2, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-3`](#draft-ietf-idr-linklocal-capability-4-3) "If the route is directly connected to the speaker, or if the interface address of the router through which the announced network is reachable for the speaker is the internal peer's address, the next hop MUST include its own Link-Local IPv6 address" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-3, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-4`](#draft-ietf-idr-linklocal-capability-4-4) "If, after evaluating the above procedures, there are no IPv6 next hops included with the route, the route MUST NOT be announced to the remote BGP speaker" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-4, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-5`](#draft-ietf-idr-linklocal-capability-4-5) "A Route Reflector (RR) reflecting a route with a link-local-only next hop MUST NOT advertise that route to a client unless the client shares the same link-layer segment as the original advertiser" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-5, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-6`](#draft-ietf-idr-linklocal-capability-4-6) "For all other clients, the RR MUST either rewrite the next hop to its own address (next-hop-self) or consider the route ineligible for advertisement to that specific peer" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-6, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-7`](#draft-ietf-idr-linklocal-capability-4-7) "If no next hops are included, the route MUST NOT be announced (treat-as-withdraw)" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-7, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-8`](#draft-ietf-idr-linklocal-capability-4-8) "Link-Local IPv6 next hops MUST NOT be included" for an external peer that "is multiple IP hops away from the speaker (aka \"multihop EBGP\")" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-8, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-9`](#draft-ietf-idr-linklocal-capability-4-9) "If a Global IPv6 next hop is not included, the route MUST NOT be advertised to the external peer (treat-as-withdraw)" (§4) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-4-9, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-5-1`](#draft-ietf-idr-linklocal-capability-5-1) "When this combination has not been negotiated, a sender MUST follow the rules in Section 3 of [RFC8950] and encode the Next Hop as 32 octets" (§5) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-5-1, so no unit is bound to it. ### [`DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-6-1`](#draft-ietf-idr-linklocal-capability-6-1) "If the Next Hop field is malformed, the implementation MUST handle the malformed UPDATE message using the approach of \"treat-as-withdraw\", as described in section 7.3 of [RFC7606]" (§6) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY-6-1, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 draft walk, draft-ietf-idr-linklocal-capability | | Signed off | 2026-09-01 | | Register | prose | | Source | rfc/drafts/draft-ietf-idr-linklocal-capability.txt | | Source fingerprint | f6a372b9b6ab4db5 | | Record | rfc/extraction/draft-ietf-idr-linklocal-capability.json | | Mapped sentences | 13 | | Declined as scope | 0 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Abstract, Status of This Memo, Copyright Notice and Table of Contents. The Abstract says the document updates RFC 2545 to clarify the next-hop encoding when only an IPv6 Link-Local address is available, and defines a capability signalling support for it. It directs no speaker. Its one site is the Copyright Notice, excluded below. | | `1` | not stated | 0 | walked | not stated | | `2` | not stated | 0 | walked | not stated | | `3` | not stated | 2 | walked | not stated | | `4` | not stated | 9 | walked | not stated | | `5` | not stated | 1 | walked | not stated | | `6` | not stated | 1 | walked | not stated | | `7` | not stated | 0 | walked | not stated | | `8` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. Thanks, plus the note that the work builds on draft-kumar-idr-link-local-nexthop and draft-kato-bgp-ipv6-link-local. | | `9` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records that IANA has assigned capability number 77 in the BGP Capability Codes registry, with the one-row table naming it. Binds IANA, not a speaker. | | `10` | not stated | 0 | walked | not stated | | `11` | References | 0 | skipped (references) | References. The heading over sections 11.1 and 11.2. | | `11.1` | Normative References | 0 | skipped (references) | Normative References. | | `11.2` | Informative References | 0 | skipped (references) | Informative References. | | `A` | Appendix A | 0 | skipped (appendix-non-normative) | Appendix A. Motivations for a Capability. Two sentences saying Link-Local-only next hops have been inconsistently supported and that the capability lets two conforming implementations interoperate without extra configuration. | | `B` | Appendix B | 0 | skipped (appendix-non-normative) | Appendix B. Inconsistency Reports. The RFC 7942 running-code notes for FRRouting and Bird. | | `C` | Appendix C | 0 | skipped (appendix-non-normative) | Appendix C. Implementation Report. The RFC 7942 running-code note for FRRouting. | ### Excluded sentences The walk over DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY declined no sentence: every site it found is mapped to a requirement. ## Superseded No document obsoletes DRAFT-IETF-IDR-LINKLOCAL-CAPABILITY, so its obligations are stated where they were written. --- ### Page: DRAFT-IETF-SIDROPS-8210BIS - The Resource Public Key Infrastructure (RPKI) to Router Protocol, Version 2 https://ze-software.net/quality/rfc-compliance/draft-ietf-sidrops-8210bis/ # DRAFT-IETF-SIDROPS-8210BIS - The Resource Public Key Infrastructure (RPKI) to Router Protocol, Version 2 No row in the public ledger. Every requirement this repository extracted from DRAFT-IETF-SIDROPS-8210BIS, the tests bound to it, and what a reader has verified about them. This summary is not enrolled. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 10 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 10.0% | 1 of 10 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 10 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | MUSTs declared | 10 | of 13 this summary declares | MUST-level requirements this summary DECLARES. The gate holds none of them, because this RFC is not enrolled (backlog), so every share below reads what the summary records rather than what the gate enforces | | Out of scope | 3 | of 10 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 30.0% | 3 of 10 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 10 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 10 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 60.0% | 6 of 10 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 10 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | MUSTs declared | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Not enrolled (backlog) | | Requirements | 13 | | Gated MUST-level | 10 | | Not applicable, so out of scope | 3 | | Declared gaps | 0 | | Gated with no test | 6 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/draft-ietf-sidrops-8210bis.md` | | Requirement shard | `rfc/requirements/draft-ietf-sidrops-8210bis.md` | | RFC text | `rfc/drafts/draft-ietf-sidrops-8210bis.txt` | ## Enrolment Not enrolled (backlog, the requirements have not been extracted from the document yet; this is work owed rather than a decision): The RPKI to Router Protocol, Version 2. Split out of rfc9582 on 2026-09-01 because the obligations below are stated by this draft and not by RFC 9582, which profiles the ROA certificate. It is not enrolled because its obligations are not yet proven and the draft is still in the RFC Editor queue, so a version bump can restate them. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for DRAFT-IETF-SIDROPS-8210BIS. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 6 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **10** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (4):** [`DRAFT-IETF-SIDROPS-8210BIS-5.12-4`](#draft-ietf-sidrops-8210bis-5.12-4), [`DRAFT-IETF-SIDROPS-8210BIS-5.12-5`](#draft-ietf-sidrops-8210bis-5.12-5), [`DRAFT-IETF-SIDROPS-8210BIS-7-1`](#draft-ietf-sidrops-8210bis-7-1), [`DRAFT-IETF-SIDROPS-8210BIS-7-3`](#draft-ietf-sidrops-8210bis-7-3) **No test and no annotation (6):** [`DRAFT-IETF-SIDROPS-8210BIS-5.12-1`](#draft-ietf-sidrops-8210bis-5.12-1), [`DRAFT-IETF-SIDROPS-8210BIS-5.12-2`](#draft-ietf-sidrops-8210bis-5.12-2), [`DRAFT-IETF-SIDROPS-8210BIS-5.12-3`](#draft-ietf-sidrops-8210bis-5.12-3), [`DRAFT-IETF-SIDROPS-8210BIS-5.12-6`](#draft-ietf-sidrops-8210bis-5.12-6), [`DRAFT-IETF-SIDROPS-8210BIS-5.12-7`](#draft-ietf-sidrops-8210bis-5.12-7), [`DRAFT-IETF-SIDROPS-8210BIS-7-2`](#draft-ietf-sidrops-8210bis-7-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `DRAFT-IETF-SIDROPS-8210BIS-5.12-1` | Provider AS set in ASPA PDU MUST contain at least one provider ASN (§5.12) | MUST | 5.12 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-8210BIS-5.12-2` | Customer AS MUST NOT appear in its own provider set (§5.12) | MUST NOT | 5.12 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-8210BIS-5.12-3` | Provider ASNs MUST be in ascending order within the PDU (§5.12) | MUST | 5.12 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-8210BIS-5.12-4` | Cache MUST ensure one ASPA PDU per (Customer-AS, AFI) pair (§5.12) | MUST | 5.12 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this is a cache-side emission/deduplication guarantee; ze is the RTR router that only consumes ASPA PDUs (no ASPA PDU writer exists, only query writers) and never enforces or emits this pairing (internal/component/bgp/plugins/rpki/rtr_pdu.go:92, :103) | | `DRAFT-IETF-SIDROPS-8210BIS-5.12-5` | Withdraw ASPA MUST match exact (Customer-AS, AFI) pair (§5.12) | MUST | 5.12 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** exact-(Customer-AS,AFI) withdraw matching binds the cache's emission and a per-AFI record model; ze consumes withdraws keyed on Customer-AS alone (the §5.12 router option to ignore AFI) and maintains no AFI dimension to match (internal/component/bgp/plugins/rpki/rtr_session.go:283, aspa_cache.go:114) | | `DRAFT-IETF-SIDROPS-8210BIS-5.12-6` | Router MUST ignore ASPA PDUs with unknown AFI values (§5.12) | MUST | 5.12 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-8210BIS-5.12-7` | Customer AS 0 is reserved, MUST NOT appear (§5.12) | MUST NOT | 5.12 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-8210BIS-7-1` | Router starting a v2 session MUST send query with version=2 (§7) | MUST | 7 | **positive:** no positive test. **negative:** no negative test. **{single-polarity}:** ze constructs every session at rtrVersionMax and writes that version unconditionally into the initial query, so the emitted version byte is observable but there is no malformed input that yields a wrong-version query to test negatively | | `DRAFT-IETF-SIDROPS-8210BIS-7-2` | On Unsupported Protocol Version error, router MUST downgrade or disconnect (§7) | MUST | 7 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-8210BIS-7-3` | Cache receiving a version it does not support MUST send error code 4 (§7) | MUST | 7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this binds the RTR cache/server role; ze runs only an RTR client that dials out and reads error reports, with no listener and no error-report writer, so it never receives queries or sends error code 4 (internal/component/bgp/plugins/rpki/rtr_session.go:125) | | `DRAFT-IETF-SIDROPS-8210BIS-7-4` | Router SHOULD start at highest supported version (§7) | SHOULD | 7 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-8210BIS-5.12-8` | Provider ASNs SHOULD be sorted ascending; cache MUST sort, router SHOULD verify (§5.12) | SHOULD | 5.12 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-8210BIS-5.12-9` | Router MAY ignore AFI field and apply ASPA to all address families (§5.12) | MAY | 5.12 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`DRAFT-IETF-SIDROPS-8210BIS-5.12-1`](#draft-ietf-sidrops-8210bis-5.12-1) Provider AS set in ASPA PDU MUST contain at least one provider ASN (§5.12) | no test | no test carries this requirement id | | [`DRAFT-IETF-SIDROPS-8210BIS-5.12-2`](#draft-ietf-sidrops-8210bis-5.12-2) Customer AS MUST NOT appear in its own provider set (§5.12) | no test | no test carries this requirement id | | [`DRAFT-IETF-SIDROPS-8210BIS-5.12-3`](#draft-ietf-sidrops-8210bis-5.12-3) Provider ASNs MUST be in ascending order within the PDU (§5.12) | no test | no test carries this requirement id | | [`DRAFT-IETF-SIDROPS-8210BIS-5.12-4`](#draft-ietf-sidrops-8210bis-5.12-4) Cache MUST ensure one ASPA PDU per (Customer-AS, AFI) pair (§5.12) | no test | no test carries this requirement id; annotated {not-applicable}: this is a cache-side emission/deduplication guarantee; ze is the RTR router that only consumes ASPA PDUs (no ASPA PDU writer exists, only query writers) and never enforces or emits this pairing (internal/component/bgp/plugins/rpki/rtr_pdu.go:92, :103) | | [`DRAFT-IETF-SIDROPS-8210BIS-5.12-5`](#draft-ietf-sidrops-8210bis-5.12-5) Withdraw ASPA MUST match exact (Customer-AS, AFI) pair (§5.12) | no test | no test carries this requirement id; annotated {not-applicable}: exact-(Customer-AS,AFI) withdraw matching binds the cache's emission and a per-AFI record model; ze consumes withdraws keyed on Customer-AS alone (the §5.12 router option to ignore AFI) and maintains no AFI dimension to match (internal/component/bgp/plugins/rpki/rtr_session.go:283, aspa_cache.go:114) | | [`DRAFT-IETF-SIDROPS-8210BIS-5.12-6`](#draft-ietf-sidrops-8210bis-5.12-6) Router MUST ignore ASPA PDUs with unknown AFI values (§5.12) | no test | no test carries this requirement id | | [`DRAFT-IETF-SIDROPS-8210BIS-5.12-7`](#draft-ietf-sidrops-8210bis-5.12-7) Customer AS 0 is reserved, MUST NOT appear (§5.12) | no test | no test carries this requirement id | | [`DRAFT-IETF-SIDROPS-8210BIS-7-1`](#draft-ietf-sidrops-8210bis-7-1) Router starting a v2 session MUST send query with version=2 (§7) | no test | no test carries this requirement id; annotated {single-polarity}: ze constructs every session at rtrVersionMax and writes that version unconditionally into the initial query, so the emitted version byte is observable but there is no malformed input that yields a wrong-version query to test negatively | | [`DRAFT-IETF-SIDROPS-8210BIS-7-2`](#draft-ietf-sidrops-8210bis-7-2) On Unsupported Protocol Version error, router MUST downgrade or disconnect (§7) | no test | no test carries this requirement id | | [`DRAFT-IETF-SIDROPS-8210BIS-7-3`](#draft-ietf-sidrops-8210bis-7-3) Cache receiving a version it does not support MUST send error code 4 (§7) | no test | no test carries this requirement id; annotated {not-applicable}: this binds the RTR cache/server role; ze runs only an RTR client that dials out and reads error reports, with no listener and no error-report writer, so it never receives queries or sends error code 4 (internal/component/bgp/plugins/rpki/rtr_session.go:125) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`DRAFT-IETF-SIDROPS-8210BIS-5.12-1`](#draft-ietf-sidrops-8210bis-5.12-1) Provider AS set in ASPA PDU MUST contain at least one provider ASN (§5.12) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-5.12-1, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-5.12-2`](#draft-ietf-sidrops-8210bis-5.12-2) Customer AS MUST NOT appear in its own provider set (§5.12) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-5.12-2, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-5.12-3`](#draft-ietf-sidrops-8210bis-5.12-3) Provider ASNs MUST be in ascending order within the PDU (§5.12) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-5.12-3, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-5.12-4`](#draft-ietf-sidrops-8210bis-5.12-4) Cache MUST ensure one ASPA PDU per (Customer-AS, AFI) pair (§5.12) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-5.12-4, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-5.12-5`](#draft-ietf-sidrops-8210bis-5.12-5) Withdraw ASPA MUST match exact (Customer-AS, AFI) pair (§5.12) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-5.12-5, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-5.12-6`](#draft-ietf-sidrops-8210bis-5.12-6) Router MUST ignore ASPA PDUs with unknown AFI values (§5.12) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-5.12-6, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-5.12-7`](#draft-ietf-sidrops-8210bis-5.12-7) Customer AS 0 is reserved, MUST NOT appear (§5.12) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-5.12-7, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-7-1`](#draft-ietf-sidrops-8210bis-7-1) Router starting a v2 session MUST send query with version=2 (§7) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-7-1, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-7-2`](#draft-ietf-sidrops-8210bis-7-2) On Unsupported Protocol Version error, router MUST downgrade or disconnect (§7) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-7-2, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-8210BIS-7-3`](#draft-ietf-sidrops-8210bis-7-3) Cache receiving a version it does not support MUST send error code 4 (§7) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-8210BIS-7-3, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for DRAFT-IETF-SIDROPS-8210BIS, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes DRAFT-IETF-SIDROPS-8210BIS, so its obligations are stated where they were written. --- ### Page: DRAFT-IETF-SIDROPS-ASPA-VERIFICATION - Verification of AS_PATH Using the Resource Certificate PKI and Autonomous System Provider Authorization https://ze-software.net/quality/rfc-compliance/draft-ietf-sidrops-aspa-verification/ # DRAFT-IETF-SIDROPS-ASPA-VERIFICATION - Verification of AS_PATH Using the Resource Certificate PKI and Autonomous System Provider Authorization Partial. Every requirement this repository extracted from DRAFT-IETF-SIDROPS-ASPA-VERIFICATION, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 50.0% | 4 of 8 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 25.0% | 2 of 8 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 8 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 11 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 8 | of 14 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 8 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 8 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 8 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 8 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 25.0% | 2 of 8 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 8 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 14 | | Gated MUST-level | 8 | | Not applicable, so out of scope | 0 | | Declared gaps | 2 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 11 | | Tagged units | 11 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/draft-ietf-sidrops-aspa-verification.md` | | Requirement shard | `rfc/requirements/draft-ietf-sidrops-aspa-verification.md` | | RFC text | `rfc/drafts/draft-ietf-sidrops-aspa-verification.txt` | ## Enrolment Enrolled: ASPA AS_PATH verification: 4 MET + 2 single-polarity (6-1, 7-2) + 2 gap (6-4 per-AFI records, 8-1 Invalid-not-preferred) ## What the public ledger says **Status:** Partial **What the ledger says is covered** - Section 6 verification algorithm (upstream/downstream), AS_SET to Unknown, prepend collapse, AS0-in-provider rejection, RTR ASPA PDU (Type 11) consumption, and re-validation on cache change. Two MUSTs unmet: per-AFI ASPA records (6-4, the AFI flag is parsed then discarded and the cache is keyed by customer AS alone, so per-AFI records overwrite each other) - Invalid-not-preferred (8-1, ASPA state drives only reject/keep with default LogOnly, so an accepted Invalid route can outrank a Valid one for the same prefix). **What the ledger says remains:** - ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **8** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-2`](#draft-ietf-sidrops-aspa-verification-6-2), [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-3`](#draft-ietf-sidrops-aspa-verification-6-3), [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-x-1`](#draft-ietf-sidrops-aspa-verification-x-1), [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-7-1`](#draft-ietf-sidrops-aspa-verification-7-1) **Annotated instead of tested (4):** [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-1`](#draft-ietf-sidrops-aspa-verification-6-1), [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-4`](#draft-ietf-sidrops-aspa-verification-6-4), [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-1`](#draft-ietf-sidrops-aspa-verification-8-1), [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-7-2`](#draft-ietf-sidrops-aspa-verification-7-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-1` | Apply upstream verification to routes received from customers and lateral peers (Section 6) | MUST | 6 | **positive:** `unit/verify` [`TestASPAStateForPath`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L223). **negative:** no negative test. **{single-polarity}:** verifyASPA runs on every received UPDATE carrying an AS_PATH whenever ASPA is enabled (a superset that includes customer and peer routes), and there is no required case where such a route must NOT be verified (internal/component/bgp/plugins/rpki/rpki.go:338) | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-2` | AS_SET in path must result in Unknown validation state (Section 6) | MUST | 6 | **positive:** `unit/verify` [`TestASPAStateForPath`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L245). **positive:** `unit/verify` [`TestASPAVerifyASSet`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L62). **negative:** `unit/verify` [`TestASPAVerifyValid`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L16) | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-3` | Prepend removal must only collapse consecutive duplicates (Section 6) | MUST | 6 | **positive:** `unit/verify` [`TestASPANormalizePrepends`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L100). **negative:** `unit/verify` [`TestASPANormalizePrepends`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L102) | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-4` | Support per-AFI ASPA records if provided by cache (Section 6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the ASPA PDU AFI-flags byte is read only to reject unknown AFI and is then discarded; ASPARecord carries no AFI field and the cache is keyed by customer AS alone, so per-AFI records for one customer overwrite each other and one AS_PATH state is applied to both IPv4 and IPv6 NLRIs (internal/component/bgp/plugins/rpki/aspa_cache.go:10-13, rpki.go:344) | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-x-1` | AS0 in ASPA provider set must be ignored (Pitfalls) | MUST | x | **positive:** `unit/verify` [`TestParseASPAPDUReservedProviderAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/rtr_pdu_test.go#L384). **negative:** `unit/verify` [`TestParseASPAPDU`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/rtr_pdu_test.go#L228) | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-1` | Invalid routes must not be preferred over Valid or Unknown routes (Section 8) | MUST NOT | 8 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ASPA state drives only a binary reject/keep decision with no local-pref demotion or best-path tiebreak, and the default Invalid action is LogOnly (retain), so an accepted ASPA-Invalid route competes on ordinary BGP attributes and can outrank an ASPA-Valid route for the same prefix (internal/component/bgp/plugins/rpki/rpki.go:92-100, rpki_config.go:110) | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-7-1` | Re-run verification when ASPA data changes (Section 7) | MUST | 7 | **positive:** `unit/verify` [`TestASPATrackerRevalidate`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_tracker_test.go#L54). **negative:** `unit/verify` [`TestASPATrackerRevalidateNoChange`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_tracker_test.go#L117) | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-7-2` | Use the most recent ASPA data available (Section 7) | MUST | 7 | **positive:** `unit/verify` [`TestASPAApplyDeltaMostRecent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_cache_test.go#L171). **negative:** no negative test. **{single-polarity}:** ApplyDelta atomically replaces cache entries at each End of Data and verifyASPA reads the live cache under lock, so verification always reflects the applied delta; there is no stale-data mode to test negatively (internal/component/bgp/plugins/rpki/aspa_cache.go:110-129) | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-2` | Reject Invalid routes by default (Section 8) | SHOULD | 8 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-3` | Accept Unknown routes as unverified (Section 8) | SHOULD | 8 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-4` | Log Invalid results for operational visibility (Section 8) | SHOULD | 8 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-5` | Assign local-pref based on ASPA validation state (Section 8) | MAY | 8 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-5` | Skip verification for routes from upstream providers (Section 6) | MAY | 6 | **positive:** no positive test. **negative:** no negative test | | `DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-6` | Apply verification to IBGP-learned routes (Section 6) | MAY | 6 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-4`](#draft-ietf-sidrops-aspa-verification-6-4) Support per-AFI ASPA records if provided by cache (Section 6) | {gap}, no test | the ASPA PDU AFI-flags byte is read only to reject unknown AFI and is then discarded; ASPARecord carries no AFI field and the cache is keyed by customer AS alone, so per-AFI records for one customer overwrite each other and one AS_PATH state is applied to both IPv4 and IPv6 NLRIs (internal/component/bgp/plugins/rpki/aspa_cache.go:10-13, rpki.go:344) | | [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-1`](#draft-ietf-sidrops-aspa-verification-8-1) Invalid routes must not be preferred over Valid or Unknown routes (Section 8) | {gap}, no test | ASPA state drives only a binary reject/keep decision with no local-pref demotion or best-path tiebreak, and the default Invalid action is LogOnly (retain), so an accepted ASPA-Invalid route competes on ordinary BGP attributes and can outrank an ASPA-Valid route for the same prefix (internal/component/bgp/plugins/rpki/rpki.go:92-100, rpki_config.go:110) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-1`](#draft-ietf-sidrops-aspa-verification-6-1) Apply upstream verification to routes received from customers and lateral peers (Section 6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestASPAStateForPath`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L223) | unit/verify | unproven | ### [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-2`](#draft-ietf-sidrops-aspa-verification-6-2) AS_SET in path must result in Unknown validation state (Section 6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestASPAVerifyValid`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L16) | unit/verify | unproven | | positive | [`TestASPAStateForPath`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L245) | unit/verify | unproven | | positive | [`TestASPAVerifyASSet`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L62) | unit/verify | unproven | ### [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-3`](#draft-ietf-sidrops-aspa-verification-6-3) Prepend removal must only collapse consecutive duplicates (Section 6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestASPANormalizePrepends`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L102) | unit/verify | unproven | | positive | [`TestASPANormalizePrepends`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_verify_test.go#L100) | unit/verify | unproven | ### [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-4`](#draft-ietf-sidrops-aspa-verification-6-4) Support per-AFI ASPA records if provided by cache (Section 6) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-6-4, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-x-1`](#draft-ietf-sidrops-aspa-verification-x-1) AS0 in ASPA provider set must be ignored (Pitfalls) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseASPAPDU`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/rtr_pdu_test.go#L228) | unit/verify | unproven | | positive | [`TestParseASPAPDUReservedProviderAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/rtr_pdu_test.go#L384) | unit/verify | unproven | ### [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-1`](#draft-ietf-sidrops-aspa-verification-8-1) Invalid routes must not be preferred over Valid or Unknown routes (Section 8) Audit verdict: not audited: no reader has judged these tests No test carries DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-8-1, so no unit is bound to it. ### [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-7-1`](#draft-ietf-sidrops-aspa-verification-7-1) Re-run verification when ASPA data changes (Section 7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestASPATrackerRevalidateNoChange`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_tracker_test.go#L117) | unit/verify | unproven | | positive | [`TestASPATrackerRevalidate`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_tracker_test.go#L54) | unit/verify | unproven | ### [`DRAFT-IETF-SIDROPS-ASPA-VERIFICATION-7-2`](#draft-ietf-sidrops-aspa-verification-7-2) Use the most recent ASPA data available (Section 7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestASPAApplyDeltaMostRecent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rpki/aspa_cache_test.go#L171) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for DRAFT-IETF-SIDROPS-ASPA-VERIFICATION, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes DRAFT-IETF-SIDROPS-ASPA-VERIFICATION, so its obligations are stated where they were written. --- ### Page: DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY - Hostname Capability for BGP https://ze-software.net/quality/rfc-compliance/draft-walton-bgp-hostname-capability/ # DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY - Hostname Capability for BGP Supported. Every requirement this repository extracted from DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 0 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 0 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 0 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 0 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | | Audit verdicts | 0 | of 0 gated MUSTs judged | 0 weak, wrong or unimplemented, 0 no longer current. Each is named below under its own requirement id | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 0 | of 1 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 0 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 0 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 0 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 0 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | No card above is a share of a population, so there is nothing to add up. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | ok | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 1 | | Gated MUST-level | 0 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/draft-walton-bgp-hostname-capability.md` | | Requirement shard | `rfc/requirements/draft-walton-bgp-hostname-capability.md` | | RFC text | `rfc/drafts/draft-walton-bgp-hostname-capability.txt` | ## Enrolment Enrolled: FQDN capability for BGP (code 73). 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, so the summary declares zero gated rows and rfc/extraction/draft-walton-bgp-hostname-capability.json signs off under 'manual-walk' with the register-reason that says why zero is a property of the document. Ze sends the capability from per-peer config (encodeValue, internal/component/bgp/plugins/hostname/hostname.go), encodes it with (*FQDN).WriteTo and parses a received one with parseFQDN (internal/core/bgp/capability/capability.go). ## What the public ledger says **Status:** Supported **What the ledger says is covered** - 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 [`rfc/extraction/draft-walton-bgp-hostname-capability.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/draft-walton-bgp-hostname-capability.json) signs off under `manual-walk`. Capability code 73, decode support, and per-peer hostname and domain advertisement (`FQDN`, [`internal/core/bgp/capability/capability.go`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability.go)). 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. **What the ledger says remains:** - ## Coverage DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY declares no MUST-level requirement, so the gate counts nothing here. ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY-4-1` | "The FQDN Capability SHOULD only be used for displaying the hostname and/or domain name of a speaker in order to make troubleshooting easier" (§4) | SHOULD | 4 - Operation | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY carries no gated, tagged or audited requirement, so there is no proof state to state. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 draft walk, draft-walton-bgp-hostname-capability | | Signed off | 2026-09-01 | | Register | manual-walk | | Source | rfc/drafts/draft-walton-bgp-hostname-capability.txt | | Source fingerprint | 83f56181a3009274 | | Record | rfc/extraction/draft-walton-bgp-hostname-capability.json | | Mapped sentences | 0 | | Declined as scope | 1 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 1 | skipped (front-matter) | Title block, Abstract, Status of This Memo and Copyright Notice. The Abstract says the document introduces a BGP capability carrying a speaker's hostname. It directs no speaker. The one site the scan raises here is the Copyright Notice sentence, excluded below. | | `1` | Introduction | 0 | walked | Introduction. Says BGP is used inside the data center, that displaying a speaker's hostname eases troubleshooting, and that this document defines a capability exchanging a speaker's FQDN. Indicative throughout; no keyword site and no obligation. | | `2` | Specification of Requirements | 0 | walked | Specification of Requirements. The RFC 2119 key-words paragraph. It tells a reader how to read the rest and binds nobody, which is why the derivation excludes it from the capitalised site inventory. | | `3` | FQDN Capability | 0 | walked | FQDN Capability. Defines the wire format: Hostname Length, Hostname, Domain Name Length, Domain Name, with the two strings 'encoded via UTF-8' and the capability code deferred to the IANA Considerations section. Every sentence is a field definition in indicative prose, carried by the Wire Format table of rfc/short/draft-walton-bgp-hostname-capability.md. No RFC 2119 keyword and no obligation on a speaker. | | `4` | Operation | 0 | walked | Operation. Carries the document's one normative sentence, the SHOULD captured as DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY-4-1: 'The FQDN Capability SHOULD only be used for displaying the hostname and/or domain name of a speaker in order to make troubleshooting easier.' The scan raises no site for it because the row is advisory and the sentence sits below the gated levels this artifact counts, so the id is declared unsourced here. The rest of the section is two pages of sample 'cl-bgp summary' output and the sentence that the hostname and domain are assumed to be taken from the ones set on the device. | | `5` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records that IANA has assigned capability number 73 in the BGP Capability Codes registry. Binds IANA, not a speaker. | | `6` | Security Considerations | 0 | walked | Security Considerations. One sentence: the document 'introduces no new security concerns to BGP or other specifications referenced in this document'. No countermeasure is directed at a speaker. | | `7` | References | 0 | skipped (references) | References. The heading over sections 7.1 and 7.2. | | `7.1` | Normative References: RFC 5492 and RFC 2119 | 0 | skipped (references) | Normative References: RFC 5492 and RFC 2119. | | `7.2` | not stated | 0 | skipped (references) | Implementation References: the Quagga BGP FQDN Capability commit. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `front:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | IETF Trust boilerplate the splitter did not strip. The sentence binds a person who extracts Code Components from the document into other software and tells them to carry the Simplified BSD License text; it directs no BGP speaker and describes no protocol behavior. Its 'must' is lowercase, so it is raised only by the case-insensitive modal scan the 'prose' register uses. | Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License. | ## Superseded No document obsoletes DRAFT-WALTON-BGP-HOSTNAME-CAPABILITY, so its obligations are stated where they were written. --- ### Page: RFC 1035 - Domain Names - Implementation and Specification https://ze-software.net/quality/rfc-compliance/rfc1035/ # RFC 1035 - Domain Names - Implementation and Specification Partial. Every requirement this repository extracted from RFC 1035, the tests bound to it, and what a reader has verified about them. This summary is not enrolled. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 92.6% | 25 of 27 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 27 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 27 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 1.6% | 1 of 62 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | MUSTs declared | 27 | of 33 this summary declares | MUST-level requirements this summary DECLARES. The gate holds none of them, because this RFC is not enrolled (backlog), so every share below reads what the summary records rather than what the gate enforces | | Out of scope | 0 | of 27 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 27 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 27 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 27 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 7.4% | 2 of 27 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 27 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | MUSTs declared | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Not enrolled (backlog) | | Requirements | 33 | | Gated MUST-level | 27 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 2 | | Nightly-only evidence | 0 | | Test tags | 62 | | Tagged units | 62 | | Recorded audit verdicts | 0 | | Discrimination records | 1 | | Summary | `rfc/short/rfc1035.md` | | Requirement shard | `rfc/requirements/rfc1035.md` | | RFC text | `rfc/full/rfc1035.txt` | ## Enrolment Not enrolled (backlog, the requirements have not been extracted from the document yet; this is work owed rather than a decision): Domain Names: Implementation and Specification. Re-authored 2026-07-30 and it now declares 27 MUST-level obligations read from the indicative prose of a 1987 document (0 capitalised keywords, 23 lowercase must), so this is no longer an empty checklist. It is not enrolled because the obligations are not all proven and the unproven ones need an owner ruling, not an implementer's annotation. The obligation with no code path in Ze 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. The 512-octet UDP bound and the TC bit ARE enforced -- send calls Msg.Truncate(udpReplyLimit(r)) for a datagram reply in internal/core/dnsserver/handler.go, and udpReplyLimit holds the Section 2.3.4 floor while letting an RFC 6891 Section 6.2.3 OPT record raise it. An unsupported inverse query DOES draw Not Implemented: Authoritative branches on the opcode before any zone lookup, in the same file. It was the one obligation the 73-section walk found OUTSIDE the summary's declared scope and added to it. The response TTL is deliberately not raised to the SOA MINIMUM -- RFC 2308 Section 4 withdrew that rule, hdr in internal/plugins/geodns/server.go applies no floor, and TestRFC2308_NoZoneWideTTLFloor holds the decision. About 6 requirements admit only a positive polarity because miekg/dns owns the wire codec and no Ze-side change can break them. Escalated for scoping per OR-1b. ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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 [`internal/core/dnsserver/handler.go`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/handler.go)). 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 [`rfc/short/rfc1035.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc1035.md). The walk of all 73 sections is recorded in [`rfc/extraction/rfc1035.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/rfc1035.json). **What the ledger says remains** 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 [`internal/plugins/geodns/server.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server.go) applies no floor, and `TestRFC2308_NoZoneWideTTLFloor` holds the decision. The DNS wire codec is `github.com/miekg/dns`, so several encoding obligations admit no negative test through Ze at all. Declared `backlog` while an owner ruling is outstanding. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 25 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 2 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **27** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (25):** [`RFC1035-2.3.3-1`](#rfc1035-2.3.3-1), [`RFC1035-2.3.3-2`](#rfc1035-2.3.3-2), [`RFC1035-2.3.4-1`](#rfc1035-2.3.4-1), [`RFC1035-2.3.4-2`](#rfc1035-2.3.4-2), [`RFC1035-3.1-1`](#rfc1035-3.1-1), [`RFC1035-3.1-2`](#rfc1035-3.1-2), [`RFC1035-3.1-3`](#rfc1035-3.1-3), [`RFC1035-3.1-4`](#rfc1035-3.1-4), [`RFC1035-3.1-5`](#rfc1035-3.1-5), [`RFC1035-3.1-6`](#rfc1035-3.1-6), [`RFC1035-4.1.1-1`](#rfc1035-4.1.1-1), [`RFC1035-4.1.1-2`](#rfc1035-4.1.1-2), [`RFC1035-4.1.1-3`](#rfc1035-4.1.1-3), [`RFC1035-4.1.3-1`](#rfc1035-4.1.3-1), [`RFC1035-4.1.3-2`](#rfc1035-4.1.3-2), [`RFC1035-4.1.4-1`](#rfc1035-4.1.4-1), [`RFC1035-4.1.4-2`](#rfc1035-4.1.4-2), [`RFC1035-4.1.4-3`](#rfc1035-4.1.4-3), [`RFC1035-4.1.4-4`](#rfc1035-4.1.4-4), [`RFC1035-4.1.4-5`](#rfc1035-4.1.4-5), [`RFC1035-4.2.1-1`](#rfc1035-4.2.1-1), [`RFC1035-4.2.1-2`](#rfc1035-4.2.1-2), [`RFC1035-4.2.1-3`](#rfc1035-4.2.1-3), [`RFC1035-4.2.2-1`](#rfc1035-4.2.2-1), [`RFC1035-6.4-1`](#rfc1035-6.4-1) **No test and no annotation (2):** [`RFC1035-3.3.13-1`](#rfc1035-3.3.13-1), [`RFC1035-4.2-1`](#rfc1035-4.2-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1035-2.3.3-1` | For all parts of the DNS that are part of the official protocol, all comparisons between character strings (e.g., labels, domain names, etc.) are done in a case-insensitive manner (§2.3.3) | MUST | 2.3.3 - Character Case | **positive:** `unit/verify` [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L54). **negative:** `unit/verify` [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L97) | | `RFC1035-2.3.3-2` | Loss of case sensitive data must be minimized (§2.3.3) | MUST | 2.3.3 - Character Case | **positive:** `unit/verify` [`TestRFC1035_QueryNameCasePreservedInTheReply`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L133). **negative:** `unit/verify` [`TestRFC1035_QueryNameCasePreservedInTheReply`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L148) | | `RFC1035-2.3.4-1` | Size limit -- TTL: positive values of a signed 32 bit number (§2.3.4) | MUST | 2.3.4 - Size limits | **positive:** `unit/verify` [`TestRFC1035_ConfiguredTTLBoundedToASigned32BitPositive`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L144). **negative:** `unit/verify` [`TestRFC1035_ConfiguredTTLBoundedToASigned32BitPositive`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L159) | | `RFC1035-2.3.4-2` | Size limit -- UDP messages: 512 octets or less (§2.3.4) | MUST | 2.3.4 - Size limits | **positive:** `unit/verify` [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L102). **negative:** `unit/verify` [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L132) | | `RFC1035-3.1-1` | Each label is represented as a one octet length field followed by that number of octets (§3.1) | MUST | 3.1 - Name space definitions: the wire form of a domain name | **positive:** `unit/verify` [`TestRFC1035_NameEncodedAsLengthPrefixedLabelsEndingAtRoot`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L179). **negative:** `unit/verify` [`TestRFC1035_NameEncodedAsLengthPrefixedLabelsEndingAtRoot`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L204) | | `RFC1035-3.1-2` | Since every domain name ends with the null label of the root, a domain name is terminated by a length byte of zero (§3.1) | MUST | 3.1 - Name space definitions: the wire form of a domain name | **positive:** `unit/verify` [`TestRFC1035_NameEncodedAsLengthPrefixedLabelsEndingAtRoot`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L186). **negative:** `unit/verify` [`TestRFC1035_NameEncodedAsLengthPrefixedLabelsEndingAtRoot`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L192) | | `RFC1035-3.1-3` | The high order two bits of every length octet must be zero, and the remaining six bits of the length field limit the label to 63 octets or less (§3.1) | MUST | 3.1 - Name space definitions: the wire form of a domain name | **positive:** `unit/verify` [`TestRFC1035_ConfiguredLabelBoundedTo63Octets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L57). **negative:** `unit/verify` [`TestRFC1035_ConfiguredLabelBoundedTo63Octets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L73). **positive:** `functional/verify` [`dns-name-too-long.ci`](https://github.com/ze-software/ze/blob/main/test/parse/dns-name-too-long.ci#L24). **negative:** `functional/verify` [`dns-name-too-long.ci`](https://github.com/ze-software/ze/blob/main/test/parse/dns-name-too-long.ci#L67) | | `RFC1035-3.1-4` | The total length of a domain name (i.e., label octets and label length octets) is restricted to 255 octets or less (§3.1) | MUST | 3.1 - Name space definitions: the wire form of a domain name | **positive:** `unit/verify` [`TestRFC1035_ConfiguredNameBoundedTo255WireOctets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L134). **negative:** `unit/verify` [`TestRFC1035_ConfiguredNameBoundedTo255WireOctets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L115). **positive:** `functional/verify` [`dns-name-too-long.ci`](https://github.com/ze-software/ze/blob/main/test/parse/dns-name-too-long.ci#L43). **negative:** `functional/verify` [`dns-name-too-long.ci`](https://github.com/ze-software/ze/blob/main/test/parse/dns-name-too-long.ci#L65) | | `RFC1035-3.1-5` | Name servers and resolvers must compare labels in a case-insensitive manner (i.e., A=a), assuming ASCII with zero parity (§3.1) | MUST | 3.1 - Name space definitions: the wire form of a domain name | **positive:** `unit/verify` [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L59). **negative:** `unit/verify` [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L105) | | `RFC1035-3.1-6` | Non-alphabetic codes must match exactly (§3.1) | MUST | 3.1 - Name space definitions: the wire form of a domain name | **positive:** `unit/verify` [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L87). **negative:** `unit/verify` [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L110) | | `RFC1035-3.3.13-1` | Whenever a RR is sent in a response to a query, the TTL field is set to the maximum of the TTL field from the RR and the MINIMUM field in the appropriate SOA (§3.3.13) | MUST | 3.3.13 | **positive:** no positive test. **negative:** no negative test | | `RFC1035-4.1.1-1` | Z is reserved for future use and must be zero in all queries and responses (§4.1.1) | MUST | 4.1.1 - Header section format | **positive:** `unit/verify` [`TestRFC1035_ReservedZFieldIsZero`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_header_test.go#L69). **negative:** `unit/verify` [`TestRFC1035_ReservedZFieldIsZero`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_header_test.go#L102) | | `RFC1035-4.1.1-2` | AA (Authoritative Answer) is valid in responses and specifies that the responding name server is an authority for the domain name in question section (§4.1.1) | MUST | 4.1.1 - Header section format | **positive:** `unit/verify` [`TestRFC1035_AuthoritativeAnswerBitOnEveryReply`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_header_test.go#L137). **negative:** `unit/verify` [`TestRFC1035_AuthoritativeAnswerBitOnEveryReply`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_header_test.go#L162). **negative:** `unit/verify` [`TestRFC1035_ResponseCodeByNameAndClient`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_negative_test.go#L117). **negative:** `unit/verify` [`TestZoneAnswer_ResponseCodeByNamePosition`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/zones_test.go#L253) | | `RFC1035-4.1.1-3` | RCODE 3 (Name Error), meaningful only for responses from an authoritative name server, signifies that the domain name referenced in the query does not exist (§4.1.1) | MUST | 4.1.1 - Header section format | **positive:** `unit/verify` [`TestRFC1035_ResponseCodeByNameAndClient`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_negative_test.go#L102). **positive:** `unit/verify` [`TestZoneAnswer_ResponseCodeByNamePosition`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/zones_test.go#L237). **negative:** `unit/verify` [`TestRFC1035_ResponseCodeByNameAndClient`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_negative_test.go#L107). **negative:** `unit/verify` [`TestZoneAnswer_ResponseCodeByNamePosition`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/zones_test.go#L242) | | `RFC1035-4.1.3-1` | TTL is a 32 bit unsigned integer that specifies the time interval in seconds that the resource record may be cached (§4.1.3) | MUST | 4.1.3 - Resource record format | **positive:** `unit/verify` [`TestRFC1035_RecordTTLIsA32BitUnsignedSecondCount`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L109). **negative:** `unit/verify` [`TestRFC1035_RecordTTLIsA32BitUnsignedSecondCount`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L127) | | `RFC1035-4.1.3-2` | RDLENGTH is an unsigned 16 bit integer that specifies the length in octets of the RDATA field (§4.1.3) | MUST | 4.1.3 - Resource record format | **positive:** `unit/verify` [`TestRFC1035_RDLengthCountsTheRDataOctets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L181). **negative:** `unit/verify` [`TestRFC1035_RDLengthCountsTheRDataOctets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L201) | | `RFC1035-4.1.4-1` | A compression pointer takes the form of a two octet sequence whose first two bits are ones, distinguishing it from a label, which must begin with two zero bits (§4.1.4) | MUST | 4.1.4 - Message compression | **positive:** `unit/verify` [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L150). **negative:** `unit/verify` [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L225) | | `RFC1035-4.1.4-2` | The OFFSET field specifies an offset from the start of the message, i.e. the first octet of the ID field in the domain header (§4.1.4) | MUST | 4.1.4 - Message compression | **positive:** `unit/verify` [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L169). **negative:** `unit/verify` [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L229) | | `RFC1035-4.1.4-3` | Pointers can only be used for occurances of a domain name where the format is not class specific (§4.1.4) | MUST | 4.1.4 - Message compression | **positive:** `unit/verify` [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L208). **negative:** `unit/verify` [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L232) | | `RFC1035-4.1.4-4` | If a domain name is contained in a part of the message subject to a length field, such as the RDATA section of an RR, and compression is used, the length of the compressed name is used in the length calculation, rather than the length of the expanded name (§4.1.4) | MUST | 4.1.4 - Message compression | **positive:** `unit/verify` [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L184). **negative:** `unit/verify` [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L235) | | `RFC1035-4.1.4-5` | All programs are required to understand arriving messages that contain pointers (§4.1.4) | REQUIRED | 4.1.4 - Message compression | **positive:** `unit/verify` [`TestRFC1035_InboundCompressionPointerUnderstood`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L345). **negative:** `unit/verify` [`TestRFC1035_InboundCompressionPointerUnderstood`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L365) | | `RFC1035-4.2-1` | Zone refresh activities must use virtual circuits because of the need for reliable transfer (§4.2) | MUST | 4.2 - Transport preamble | **positive:** no positive test. **negative:** no negative test | | `RFC1035-4.2.1-1` | Messages carried by UDP are restricted to 512 bytes, not counting the IP or UDP headers (§4.2.1) | MUST | 4.2.1 - UDP usage | **positive:** `unit/verify` [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L97). **positive:** `unit/verify` [`TestRFC1035_UDPTruncatedTCPWholeOverRealSockets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_server_transport_test.go#L81). **negative:** `unit/verify` [`TestRFC1035_UDPBoundFollowsAdvertisedEDNSSize`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L164) | | `RFC1035-4.2.1-2` | Longer messages are truncated and the TC bit is set in the header (§4.2.1) | MUST | 4.2.1 - UDP usage | **positive:** `unit/verify` [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L107). **positive:** `unit/verify` [`TestRFC1035_UDPTruncatedTCPWholeOverRealSockets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_server_transport_test.go#L90). **negative:** `unit/verify` [`TestRFC1035_StreamTransportNotTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L195). **negative:** `unit/verify` [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L127). **negative:** `unit/verify` [`TestRFC1035_UDPTruncatedTCPWholeOverRealSockets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_server_transport_test.go#L104) | | `RFC1035-4.2.1-3` | Messages sent using UDP user server port 53 (decimal) (§4.2.1) <!-- "user" is verbatim: RFC 1035 rfc/full/rfc1035.txt:1754 has a typo for "use", and the id contract pins the quoted text, so it is reproduced rather than silently corrected. Compare :1783, which reads "use server port 53" for TCP. --> | MUST | 4.2.1 - UDP usage | **positive:** `unit/verify` [`TestRFC1035_DNSTransportsUseServerPort53`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/rfc1035_port_test.go#L26). **negative:** `unit/verify` [`TestRFC1035_DNSTransportsUseServerPort53`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/rfc1035_port_test.go#L49) | | `RFC1035-4.2.2-1` | Messages sent over TCP connections use server port 53 decimal, and the message is prefixed with a two byte length field which gives the message length (§4.2.2) | MUST | 4.2.2 - TCP usage | **positive:** `unit/verify` [`TestRFC1035_TCPRepliesCarryATwoOctetLengthPrefix`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L421). **negative:** `unit/verify` [`TestRFC1035_TCPRepliesCarryATwoOctetLengthPrefix`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L445) | | `RFC1035-6.4-1` | While inverse query support is optional, all name servers must be at least able to return the error response (§6.4) | MUST | 6.4 - Inverse queries (Optional) | **positive:** `unit/verify` [`TestRFC1035_UnsupportedOpcodeReturnsNotImplemented`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L241). **negative:** `unit/verify` [`TestRFC1035_QueryOpcodeAnsweredNormally`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L290) | | `RFC1035-2.3.1-1` | Preferred name syntax: labels must start with a letter, end with a letter or digit, and have as interior characters only letters, digits, and hyphen (§2.3.1) | SHOULD | 2.3.1 - Preferred name syntax | **positive:** no positive test. **negative:** no negative test | | `RFC1035-2.3.3-3` | When data enters the domain system, its original case should be preserved whenever possible (§2.3.3) | SHOULD | 2.3.3 - Character Case | **positive:** no positive test. **negative:** no negative test | | `RFC1035-2.3.3-4` | Attempts to store domain names in 7-bit ASCII or use of special bytes to terminate labels, etc., should be avoided (§2.3.3) | SHOULD NOT | 2.3.3 - Character Case | **positive:** no positive test. **negative:** no negative test | | `RFC1035-3.1-7` | Although labels can contain any 8 bit values in octets that make up a label, it is strongly recommended that labels follow the preferred syntax described elsewhere in this memo (§3.1) | RECOMMENDED | 3.1 - Name space definitions: the wire form of a domain name | **positive:** no positive test. **negative:** no negative test | | `RFC1035-3.3.13-2` | This use of MINIMUM should occur when the RRs are copied into the response and not when the zone is loaded from a master file or via a zone transfer (§3.3.13) | SHOULD | 3.3.13 | **positive:** no positive test. **negative:** no negative test | | `RFC1035-4.1.3-3` | Zero TTL values are interpreted to mean that the RR can only be used for the transaction in progress, and should not be cached (§4.1.3) | SHOULD NOT | 4.1.3 - Resource record format | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC1035-3.3.13-1`](#rfc1035-3.3.13-1) Whenever a RR is sent in a response to a query, the TTL field is set to the maximum of the TTL field from the RR and the MINIMUM field in the appropriate SOA (§3.3.13) | no test | no test carries this requirement id | | [`RFC1035-4.2-1`](#rfc1035-4.2-1) Zone refresh activities must use virtual circuits because of the need for reliable transfer (§4.2) | no test | no test carries this requirement id | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1035-2.3.3-1`](#rfc1035-2.3.3-1) For all parts of the DNS that are part of the official protocol, all comparisons between character strings (e.g., labels, domain names, etc.) are done in a case-insensitive manner (§2.3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L97) | unit/verify | unproven | | positive | [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L54) | unit/verify | unproven | ### [`RFC1035-2.3.3-2`](#rfc1035-2.3.3-2) Loss of case sensitive data must be minimized (§2.3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_QueryNameCasePreservedInTheReply`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L148) | unit/verify | unproven | | positive | [`TestRFC1035_QueryNameCasePreservedInTheReply`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L133) | unit/verify | unproven | ### [`RFC1035-2.3.4-1`](#rfc1035-2.3.4-1) Size limit -- TTL: positive values of a signed 32 bit number (§2.3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_ConfiguredTTLBoundedToASigned32BitPositive`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L159) | unit/verify | unproven | | positive | [`TestRFC1035_ConfiguredTTLBoundedToASigned32BitPositive`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L144) | unit/verify | unproven | ### [`RFC1035-2.3.4-2`](#rfc1035-2.3.4-2) Size limit -- UDP messages: 512 octets or less (§2.3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L132) | unit/verify | unproven | | positive | [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L102) | unit/verify | unproven | ### [`RFC1035-3.1-1`](#rfc1035-3.1-1) Each label is represented as a one octet length field followed by that number of octets (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_NameEncodedAsLengthPrefixedLabelsEndingAtRoot`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L204) | unit/verify | unproven | | positive | [`TestRFC1035_NameEncodedAsLengthPrefixedLabelsEndingAtRoot`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L179) | unit/verify | unproven | ### [`RFC1035-3.1-2`](#rfc1035-3.1-2) Since every domain name ends with the null label of the root, a domain name is terminated by a length byte of zero (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_NameEncodedAsLengthPrefixedLabelsEndingAtRoot`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L192) | unit/verify | unproven | | positive | [`TestRFC1035_NameEncodedAsLengthPrefixedLabelsEndingAtRoot`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L186) | unit/verify | unproven | ### [`RFC1035-3.1-3`](#rfc1035-3.1-3) The high order two bits of every length octet must be zero, and the remaining six bits of the length field limit the label to 63 octets or less (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_ConfiguredLabelBoundedTo63Octets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L73) | unit/verify | unproven | | negative | [`dns-name-too-long.ci`](https://github.com/ze-software/ze/blob/main/test/parse/dns-name-too-long.ci#L67) | functional/verify | unproven | | positive | [`TestRFC1035_ConfiguredLabelBoundedTo63Octets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L57) | unit/verify | unproven | | positive | [`dns-name-too-long.ci`](https://github.com/ze-software/ze/blob/main/test/parse/dns-name-too-long.ci#L24) | functional/verify | revert, verified | ### [`RFC1035-3.1-4`](#rfc1035-3.1-4) The total length of a domain name (i.e., label octets and label length octets) is restricted to 255 octets or less (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_ConfiguredNameBoundedTo255WireOctets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L115) | unit/verify | unproven | | negative | [`dns-name-too-long.ci`](https://github.com/ze-software/ze/blob/main/test/parse/dns-name-too-long.ci#L65) | functional/verify | unproven | | positive | [`TestRFC1035_ConfiguredNameBoundedTo255WireOctets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_limits_test.go#L134) | unit/verify | unproven | | positive | [`dns-name-too-long.ci`](https://github.com/ze-software/ze/blob/main/test/parse/dns-name-too-long.ci#L43) | functional/verify | unproven | ### [`RFC1035-3.1-5`](#rfc1035-3.1-5) Name servers and resolvers must compare labels in a case-insensitive manner (i.e., A=a), assuming ASCII with zero parity (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L105) | unit/verify | unproven | | positive | [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L59) | unit/verify | unproven | ### [`RFC1035-3.1-6`](#rfc1035-3.1-6) Non-alphabetic codes must match exactly (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L110) | unit/verify | unproven | | positive | [`TestRFC1035_NameComparisonIsCaseInsensitiveForLettersOnly`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_name_case_test.go#L87) | unit/verify | unproven | ### [`RFC1035-3.3.13-1`](#rfc1035-3.3.13-1) Whenever a RR is sent in a response to a query, the TTL field is set to the maximum of the TTL field from the RR and the MINIMUM field in the appropriate SOA (§3.3.13) Audit verdict: not audited: no reader has judged these tests No test carries RFC1035-3.3.13-1, so no unit is bound to it. ### [`RFC1035-4.1.1-1`](#rfc1035-4.1.1-1) Z is reserved for future use and must be zero in all queries and responses (§4.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_ReservedZFieldIsZero`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_header_test.go#L102) | unit/verify | unproven | | positive | [`TestRFC1035_ReservedZFieldIsZero`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_header_test.go#L69) | unit/verify | unproven | ### [`RFC1035-4.1.1-2`](#rfc1035-4.1.1-2) AA (Authoritative Answer) is valid in responses and specifies that the responding name server is an authority for the domain name in question section (§4.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_AuthoritativeAnswerBitOnEveryReply`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_header_test.go#L162) | unit/verify | unproven | | negative | [`TestZoneAnswer_ResponseCodeByNamePosition`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/zones_test.go#L253) | unit/verify | unproven | | negative | [`TestRFC1035_ResponseCodeByNameAndClient`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_negative_test.go#L117) | unit/verify | unproven | | positive | [`TestRFC1035_AuthoritativeAnswerBitOnEveryReply`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_header_test.go#L137) | unit/verify | unproven | ### [`RFC1035-4.1.1-3`](#rfc1035-4.1.1-3) RCODE 3 (Name Error), meaningful only for responses from an authoritative name server, signifies that the domain name referenced in the query does not exist (§4.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestZoneAnswer_ResponseCodeByNamePosition`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/zones_test.go#L242) | unit/verify | unproven | | negative | [`TestRFC1035_ResponseCodeByNameAndClient`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_negative_test.go#L107) | unit/verify | unproven | | positive | [`TestZoneAnswer_ResponseCodeByNamePosition`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/zones_test.go#L237) | unit/verify | unproven | | positive | [`TestRFC1035_ResponseCodeByNameAndClient`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_negative_test.go#L102) | unit/verify | unproven | ### [`RFC1035-4.1.3-1`](#rfc1035-4.1.3-1) TTL is a 32 bit unsigned integer that specifies the time interval in seconds that the resource record may be cached (§4.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_RecordTTLIsA32BitUnsignedSecondCount`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L127) | unit/verify | unproven | | positive | [`TestRFC1035_RecordTTLIsA32BitUnsignedSecondCount`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L109) | unit/verify | unproven | ### [`RFC1035-4.1.3-2`](#rfc1035-4.1.3-2) RDLENGTH is an unsigned 16 bit integer that specifies the length in octets of the RDATA field (§4.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_RDLengthCountsTheRDataOctets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L201) | unit/verify | unproven | | positive | [`TestRFC1035_RDLengthCountsTheRDataOctets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_rr_test.go#L181) | unit/verify | unproven | ### [`RFC1035-4.1.4-1`](#rfc1035-4.1.4-1) A compression pointer takes the form of a two octet sequence whose first two bits are ones, distinguishing it from a label, which must begin with two zero bits (§4.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L225) | unit/verify | unproven | | positive | [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L150) | unit/verify | unproven | ### [`RFC1035-4.1.4-2`](#rfc1035-4.1.4-2) The OFFSET field specifies an offset from the start of the message, i.e. the first octet of the ID field in the domain header (§4.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L229) | unit/verify | unproven | | positive | [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L169) | unit/verify | unproven | ### [`RFC1035-4.1.4-3`](#rfc1035-4.1.4-3) Pointers can only be used for occurances of a domain name where the format is not class specific (§4.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L232) | unit/verify | unproven | | positive | [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L208) | unit/verify | unproven | ### [`RFC1035-4.1.4-4`](#rfc1035-4.1.4-4) If a domain name is contained in a part of the message subject to a length field, such as the RDATA section of an RR, and compression is used, the length of the compressed name is used in the length calculation, rather than the length of the expanded name (§4.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L235) | unit/verify | unproven | | positive | [`TestRFC1035_CompressionPointersInATruncatedDatagram`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L184) | unit/verify | unproven | ### [`RFC1035-4.1.4-5`](#rfc1035-4.1.4-5) All programs are required to understand arriving messages that contain pointers (§4.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_InboundCompressionPointerUnderstood`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L365) | unit/verify | unproven | | positive | [`TestRFC1035_InboundCompressionPointerUnderstood`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L345) | unit/verify | unproven | ### [`RFC1035-4.2-1`](#rfc1035-4.2-1) Zone refresh activities must use virtual circuits because of the need for reliable transfer (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC1035-4.2-1, so no unit is bound to it. ### [`RFC1035-4.2.1-1`](#rfc1035-4.2.1-1) Messages carried by UDP are restricted to 512 bytes, not counting the IP or UDP headers (§4.2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_UDPBoundFollowsAdvertisedEDNSSize`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L164) | unit/verify | unproven | | positive | [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L97) | unit/verify | unproven | | positive | [`TestRFC1035_UDPTruncatedTCPWholeOverRealSockets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_server_transport_test.go#L81) | unit/verify | unproven | ### [`RFC1035-4.2.1-2`](#rfc1035-4.2.1-2) Longer messages are truncated and the TC bit is set in the header (§4.2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_StreamTransportNotTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L195) | unit/verify | unproven | | negative | [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L127) | unit/verify | unproven | | negative | [`TestRFC1035_UDPTruncatedTCPWholeOverRealSockets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_server_transport_test.go#L104) | unit/verify | unproven | | positive | [`TestRFC1035_UDPReplyBoundedAndTruncated`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L107) | unit/verify | unproven | | positive | [`TestRFC1035_UDPTruncatedTCPWholeOverRealSockets`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_server_transport_test.go#L90) | unit/verify | unproven | ### [`RFC1035-4.2.1-3`](#rfc1035-4.2.1-3) Messages sent using UDP user server port 53 (decimal) (§4.2.1) <!-- "user" is verbatim: RFC 1035 rfc/full/rfc1035.txt:1754 has a typo for "use", and the id contract pins the quoted text, so it is reproduced rather than silently corrected. Compare :1783, which reads "use server port 53" for TCP. --> Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_DNSTransportsUseServerPort53`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/rfc1035_port_test.go#L49) | unit/verify | unproven | | positive | [`TestRFC1035_DNSTransportsUseServerPort53`](https://github.com/ze-software/ze/blob/main/internal/plugins/as112/rfc1035_port_test.go#L26) | unit/verify | unproven | ### [`RFC1035-4.2.2-1`](#rfc1035-4.2.2-1) Messages sent over TCP connections use server port 53 decimal, and the message is prefixed with a two byte length field which gives the message length (§4.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_TCPRepliesCarryATwoOctetLengthPrefix`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L445) | unit/verify | unproven | | positive | [`TestRFC1035_TCPRepliesCarryATwoOctetLengthPrefix`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc1035_compression_test.go#L421) | unit/verify | unproven | ### [`RFC1035-6.4-1`](#rfc1035-6.4-1) While inverse query support is optional, all name servers must be at least able to return the error response (§6.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1035_QueryOpcodeAnsweredNormally`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L290) | unit/verify | unproven | | positive | [`TestRFC1035_UnsupportedOpcodeReturnsNotImplemented`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc1035_handler_test.go#L241) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement agent, spec-rfcgate-4-ledger phase 6 | | Signed off | 2026-07-30 | | Register | prose | | Source | rfc/full/rfc1035.txt | | Source fingerprint | b8efa09ba52db966 | | Record | rfc/extraction/rfc1035.json | | Mapped sentences | 11 | | Declined as scope | 20 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Network Working Group header, obsoletes/updates block and the table of contents. | | `1` | STATUS OF THIS MEMO | 0 | skipped (front-matter) | STATUS OF THIS MEMO. Distribution statement and the note that this RFC obsoletes RFC 882 and RFC 883. | | `2` | INTRODUCTION heading, with no body of its own | 0 | walked | INTRODUCTION heading, with no body of its own. | | `2.1` | not stated | 0 | walked | Overview of the three-part domain system (name space, name servers, resolvers). Descriptive. | | `2.2` | not stated | 1 | walked | Common configurations: the deployment shapes a resolver and a name server can take. Its one site is a characterisation, excluded below. | | `2.3` | Conventions | 1 | walked | Conventions. The section preamble is its one site, an applicability statement excluded below; the conventions themselves are in 2.3.1 to 2.3.4. | | `2.3.1` | Preferred name syntax | 3 | walked | Preferred name syntax. Two sites map to the single [SHOULD] row that carries both halves; the third restates section 3.1's 63-octet limit. | | `2.3.2` | not stated | 0 | walked | Data Transmission Order: the big-endian octet and bit order of every multi-octet field. Stated as a description of the diagrams, with no modal verb, so the prose scan finds no site. Ze inherits the encoding from miekg/dns and adds nothing. | | `2.3.3` | Character Case | 1 | walked | Character Case. Its one site is the case-preservation obligation; the case-insensitive comparison rule and the two 'should' clauses are read from the surrounding prose, which carries no scanned keyword. | | `2.3.4` | Size limits | 0 | walked | Size limits. Stated as a bare list ('labels 63 octets or less', 'names 255 octets or less', 'TTL positive values of a signed 32 bit number', 'UDP messages 512 octets or less') with no modal verb, so the scan finds no site here even though two of the four are declared as requirements. | | `3` | DOMAIN NAME SPACE AND RR DEFINITIONS heading | 0 | walked | DOMAIN NAME SPACE AND RR DEFINITIONS heading. | | `3.1` | Name space definitions: the wire form of a domain name | 3 | walked | Name space definitions: the wire form of a domain name. Three sites map to the length-octet, case-folding and exact-match rules; the label/terminator/total-length statements and the 'strongly recommended' clause are indicative prose. | | `3.2` | RR definitions heading | 0 | walked | RR definitions heading. | | `3.2.1` | not stated | 0 | walked | Format: the NAME/TYPE/CLASS/TTL/RDLENGTH/RDATA layout, given as a diagram plus field descriptions with no modal verb. | | `3.2.2` | TYPE values: the registry table | 0 | walked | TYPE values: the registry table. | | `3.2.3` | QTYPE values: the registry table | 0 | walked | QTYPE values: the registry table. | | `3.2.4` | CLASS values: the registry table | 0 | walked | CLASS values: the registry table. | | `3.2.5` | QCLASS values: the registry table | 0 | walked | QCLASS values: the registry table. | | `3.3` | not stated | 0 | walked | Standard RRs preamble, defining the <domain-name> and <character-string> sub-formats. | | `3.3.1` | CNAME RDATA format | 0 | walked | CNAME RDATA format. Ze emits no CNAME. | | `3.3.2` | HINFO RDATA format | 0 | walked | HINFO RDATA format. Ze emits no HINFO. | | `3.3.3` | MB RDATA format (EXPERIMENTAL) | 0 | walked | MB RDATA format (EXPERIMENTAL). Ze emits no MB. | | `3.3.4` | MD RDATA format (Obsolete) | 0 | walked | MD RDATA format (Obsolete). Ze emits no MD. | | `3.3.5` | MF RDATA format (Obsolete) | 0 | walked | MF RDATA format (Obsolete). Ze emits no MF. | | `3.3.6` | MG RDATA format (EXPERIMENTAL) | 0 | walked | MG RDATA format (EXPERIMENTAL). Ze emits no MG. | | `3.3.7` | MINFO RDATA format (EXPERIMENTAL) | 0 | walked | MINFO RDATA format (EXPERIMENTAL). Ze emits no MINFO. | | `3.3.8` | MR RDATA format (EXPERIMENTAL) | 0 | walked | MR RDATA format (EXPERIMENTAL). Ze emits no MR. | | `3.3.9` | MX RDATA format | 0 | walked | MX RDATA format. Ze emits no MX. | | `3.3.10` | NULL RDATA format (EXPERIMENTAL) | 0 | walked | NULL RDATA format (EXPERIMENTAL). Ze emits no NULL. | | `3.3.11` | NS RDATA format | 0 | walked | NS RDATA format. Ze emits NS for the zone apex and for synthesised nameserver glue; the section states the RDATA layout with no modal verb. | | `3.3.12` | PTR RDATA format | 0 | walked | PTR RDATA format. Ze's as112 zones answer PTR queries with NODATA and emit no PTR record. | | `3.3.13` | not stated | 0 | walked | SOA RDATA format, and the source of the MINIMUM-versus-TTL rule. Both its statements are indicative ('the TTL field is set to the maximum of', 'this use of MINIMUM should occur'), so the scan finds no site. | | `3.3.14` | TXT RDATA format | 0 | walked | TXT RDATA format. Ze's as112 hostname zone emits TXT. | | `3.4` | Internet specific RRs heading | 0 | walked | Internet specific RRs heading. | | `3.4.1` | A RDATA format: a single 32-bit address | 0 | walked | A RDATA format: a single 32-bit address. Ze emits A records. | | `3.4.2` | WKS RDATA format | 1 | walked | WKS RDATA format. Its one site is the bit-map alignment rule, excluded below because Ze emits no WKS record. | | `25` | not stated | 0 | walked | NOT A SECTION. The derivation's heading pattern over-matches a column-0 line inside section 3.4.2's WKS bit-map explanation ('25 (SMTP). If this bit is set, a SMTP server should be listening on TCP port 25'), which _SECTION_HEADING_RE documents as unavoidable by shape alone. It carries no site and no obligation; it is classified so the artifact stays complete against the derivation. | | `3.5` | IN-ADDR.ARPA domain | 1 | walked | IN-ADDR.ARPA domain. Its one site binds a host bootstrapping routing from DNS. | | `3.6` | Defining new types, classes, and special namespaces | 1 | walked | Defining new types, classes, and special namespaces. Its one site binds whoever defines a new TYPE or CLASS. | | `4` | MESSAGES heading | 0 | walked | MESSAGES heading. | | `4.1` | Format: the five-section message layout | 0 | walked | Format: the five-section message layout. | | `4.1.1` | Header section format | 1 | walked | Header section format. Its one site is the Z field; AA and RCODE 3 are described field by field with no modal verb. | | `4.1.2` | Question section format: QNAME, QTYPE, QCLASS | 0 | walked | Question section format: QNAME, QTYPE, QCLASS. | | `4.1.3` | Resource record format | 0 | walked | Resource record format. TTL and RDLENGTH are given as field descriptions, and the zero-TTL clause is a 'should not', so the scan finds no site. | | `4.1.4` | Message compression | 3 | walked | Message compression. Three sites; the pointer form and the receive-side obligation map, the third is rationale. The OFFSET base, the class-specific restriction and the enclosing-length rule are indicative prose. | | `4.2` | Transport preamble | 1 | walked | Transport preamble. Its one site is the zone-refresh rule. | | `4.2.1` | UDP usage | 1 | walked | UDP usage. Its one site is a querier obligation; the 512-octet limit, the truncation-plus-TC rule and the port are indicative prose. | | `4.2.2` | TCP usage | 0 | walked | TCP usage. The port and the two-octet length prefix are stated indicatively. | | `5` | MASTER FILES heading | 0 | walked | MASTER FILES heading. Ze parses no master file: a repo-wide search for NewZoneParser, ZoneParser, ReadRR and dns.NewRR finds no production call site. | | `5.1` | Format | 1 | walked | Format. Its one site is master-file quoting syntax. | | `5.2` | Use of master files to define zones | 1 | walked | Use of master files to define zones. Its one site concerns glue. | | `5.3` | Master file example | 0 | walked | Master file example. Illustrative. | | `6` | NAME SERVER IMPLEMENTATION heading | 0 | walked | NAME SERVER IMPLEMENTATION heading. The sections under it describe one suggested internal architecture, and Ze implements none of the recursion, caching or zone refresh they assume. | | `6.1` | not stated | 0 | walked | Architecture, whose preamble states that the optimal structure depends on the host operating system and that the section 'discusses implementation considerations'. | | `6.1.1` | Control | 1 | walked | Control. Its one site constrains internal task structure. | | `6.1.2` | Database | 1 | walked | Database. Its one site is conditioned on choosing the suggested hash structure. | | `6.1.3` | not stated | 0 | walked | Time: the suggestion to keep absolute rather than relative times internally. | | `6.2` | Standard query processing | 1 | walked | Standard query processing. Its one site is the 'should' governing truncation order. | | `6.3` | Zone refresh and reload processing | 0 | walked | Zone refresh and reload processing. Ze performs no zone transfer of any kind: a repo-wide search for AXFR, IXFR, TypeAXFR, TypeIXFR, dns.Transfer, SetAxfr, SetNotify and OpcodeNotify finds nothing. | | `6.4` | Inverse queries (Optional) | 2 | walked | Inverse queries (Optional). Two sites: the permission not to implement them, and the obligation that survives declining -- the one obligation this walk added to the summary. | | `6.4.1` | The contents of inverse queries and responses | 0 | walked | The contents of inverse queries and responses. Descriptive. | | `6.4.2` | Inverse query and response example | 0 | walked | Inverse query and response example. Illustrative. | | `6.4.3` | Inverse query processing | 0 | walked | Inverse query processing. Describes the optional algorithm. | | `6.5` | not stated | 0 | walked | Completion queries and responses: obsoleted by this RFC and retained for historical reference only. | | `7` | RESOLVER IMPLEMENTATION heading | 0 | walked | RESOLVER IMPLEMENTATION heading. Every obligation under it binds a resolver, a role the authoritative-server surface this summary scopes does not play. | | `7.1` | Transforming a user request into a query | 4 | walked | Transforming a user request into a query. Four sites, all resolver obligations or descriptions of the suggested request structure. | | `7.2` | Sending the queries | 1 | walked | Sending the queries. Its one site is resolver round-trip-time estimation. | | `7.3` | Processing responses | 1 | walked | Processing responses. Its one site is resolver request matching. | | `7.4` | Using the cache | 0 | walked | Using the cache. Ze holds no resolver cache. | | `8` | MAIL SUPPORT heading | 0 | walked | MAIL SUPPORT heading. Ze emits no MX, MB, MG, MR or MINFO record and implements no mail binding. | | `8.1` | Mail exchange binding | 0 | walked | Mail exchange binding. Describes the MX transition from MD/MF. | | `8.2` | Mailbox binding (Experimental) | 0 | walked | Mailbox binding (Experimental). Experimental and unimplemented everywhere. | | `9` | not stated | 0 | skipped (references) | REFERENCES and BIBLIOGRAPHY, followed by the index and the author's address. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `2.2:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Section 2.2 Common configurations, describing a stub-resolver host that forwards to a recursive server. 'the amount of new network code which is required' characterises one deployment choice; it directs nobody. | This can be appropriate for PCs or hosts which want to minimize the amount of new network code which is required. | | `2.3:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The applicability statement that opens section 2.3 Conventions: 'While the implementor is free to violate these conventions WITHIN HIS OWN SYSTEM, he must observe these conventions in ALL behavior observed from other hosts.' It scopes the subsections that follow rather than stating an obligation of its own, and each of those conventions is captured separately. | While the implementor is free to violate these conventions WITHIN HIS OWN SYSTEM, he must observe these conventions in ALL behavior observed from other hosts. | | `2.3.1:3` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | 'Labels must be 63 characters or less' restates in the preferred-syntax section the limit section 3.1 states normatively as the six-bit length field. Captured once, at the site that gives it its wire form. | Labels must be 63 characters or less. | | `3.4.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The WKS RDATA bit-map alignment rule. It binds a server that serves WKS records; Ze emits A, AAAA, SRV, SOA and NS only (internal/plugins/geodns/server.go builds those five and no other type), so no Ze code path can produce a WKS bit map at all. | The bit map must be a multiple of 8 bits long. | | `3.5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Section 3.5 IN-ADDR.ARPA, on a host that bootstraps its ROUTING TABLE from the domain database and must therefore already know a gateway. Ze's routing tables come from BGP, IS-IS, OSPF and the kernel; nothing in Ze reads DNS to build one. The producer that would act as it if ze did is ze's only DNS code, the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which answers a query out of the configured zone data: it sends no query, holds no resolver cache and reads no master file. | - Systems that use the domain database to initialize their routing tables must start with enough gateway information to guarantee that they can access the appropriate name server. | | `3.6:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Section 3.6 Defining new types, classes, and special namespaces. It binds whoever DEFINES a new TYPE or CLASS, constraining the registry relationship between TYPE and QTYPE. Ze defines none. The role is a registry act rather than an implementation, so no producer could perform it. Ze's DNS code is the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which reads whatever TYPE and CLASS values it is given. | TYPE and CLASS values must be a proper subset of QTYPEs and QCLASSes respectively. | | `4.1.4:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The rationale for the preceding restriction ('If this were not the case, a name server or resolver would be required to know the format of all RRs it handles'). The restriction itself is captured as RFC1035-4.1.4-3; this sentence explains why it exists. | If this were not the case, a name server or resolver would be required to know the format of all RRs it handled. | | `4.2.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The retransmission strategy for queries SENT over UDP. It binds the querier; this summary's declared scope is the authoritative-server surface (see 'Extraction scope' in rfc/short/rfc1035.md), which answers queries and originates none. The producer that would act as it if ze did is ze's only DNS code, the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which answers a query out of the configured zone data: it sends no query, holds no resolver cache and reads no master file. | Queries sent using UDP may be lost, and hence a retransmission strategy is required. | | `5.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Master-file quoting syntax. It binds a master-file PARSER. Ze parses none: geodns and as112 are configured from the YANG tree, and a repo-wide search for NewZoneParser, ZoneParser, ReadRR and dns.NewRR finds no production call site. | Inside a " delimited string any character can occur, except for a " itself, which must be quoted using \\ (back slash). | | `5.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Glue in a master file that defines a zone. Same role as the site above: Ze has no master-file path. Its own level is 'should' in any case. The producer that would act as it if ze did is ze's only DNS code, the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which answers a query out of the configured zone data: it sends no query, holds no resolver cache and reads no master file. | If delegations are present and glue information is required, it should be present. 4. | | `6.1.1:1` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | Section 6.1 Architecture opens 'The optimal structure for the name server will depend on the host operating system ... This section discusses implementation considerations'. The sentence constrains a server's internal task structure, has no wire-visible form, and nothing about it can be observed by a peer. | A name server must employ multiple concurrent activities, whether they are implemented as separate tasks in the host's OS or multiplexing inside a single name server program. | | `6.1.2:1` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | Section 6.1.2 Database opens 'While name server implementations are free to use any internal data structures they choose, the suggested structure consists of'. The sentence is conditioned on having chosen the suggested hash structure, and it restates the case-preservation rule already captured as RFC1035-2.3.3-2. | In any case, hash structures used to store tree sections must insure that hash functions and procedures preserve the casing conventions of the DNS. | | `6.2:1` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The ORDER in which a response should be truncated, at level 'should'. It never gates. The obligation to truncate at all and to set TC is captured separately as RFC1035-4.2.1-2. | When a response is so long that truncation is required, the truncation should start at the end of the response and work forward in the datagram. | | `6.4:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | 'Name servers are not required to support any form of inverse queries' is a permission, and the sentence that qualifies it is the real obligation -- captured from the next site as RFC1035-6.4-1. | Name servers are not required to support any form of inverse queries. | | `7.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Section 7 RESOLVER IMPLEMENTATION: a resolver multiplexing concurrent client requests. The authoritative server this summary scopes serves one query per response and originates none. The producer that would act as it if ze did is ze's only DNS code, the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which answers a query out of the configured zone data: it sends no query, holds no resolver cache and reads no master file. | Since a resolver must be able to multiplex multiple requests if it is to perform its function efficiently, each pending request is usually represented in some block of state information. | | `7.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Resolver cache timeliness for a relative-time TTL. Ze's authoritative server holds no resolver cache. The producer that would act as it if ze did is ze's only DNS code, the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which answers a query out of the configured zone data: it sends no query, holds no resolver cache and reads no master file. | Note that when an RRs TTL indicates a relative time, the RR must be timely, since it is part of a zone. | | `7.1:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | A bound on the work a RESOLVER does per client request, to guard against errors in the database. Same role as above. The producer that would act as it if ze did is ze's only DNS code, the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which answers a query out of the configured zone data: it sends no query, holds no resolver cache and reads no master file. | The amount of work which a resolver will do in response to a client request must be limited to guard against errors in the database, such as circular CNAME references, and operational problems, such as network partition which prevents the resolver from accessing the name servers it needs. | | `7.1:4` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A descriptive sentence about the resolver's per-request state structure ('This structure keeps track of the state of a request if it must wait for answers'). It describes the suggested structure rather than obliging anyone. | This structure keeps track of the state of a request if it must wait for answers from foreign name servers. | | `7.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Round-trip-time estimation when a resolver has no history for an address. A resolver obligation with no authoritative-server counterpart. The producer that would act as it if ze did is ze's only DNS code, the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which answers a query out of the configured zone data: it sends no query, holds no resolver cache and reads no master file. | Part of this step must deal with addresses which have no such history; in this case an expected round trip time of 5-10 seconds should be the worst case, with lower estimates for the same local network, etc. | | `7.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Matching a response to its outstanding request in order to sample round-trip time. Again a resolver obligation: the authoritative server sends responses, never receives them. The producer that would act as it if ze did is ze's only DNS code, the authoritative `answerQuery` (`internal/plugins/geodns/server.go`, `internal/plugins/as112/server.go`), which answers a query out of the configured zone data: it sends no query, holds no resolver cache and reads no master file. | However, if it is using the response to sample the round trip time to access the name server, it must be able to determine which transmission matches the response (and keep transmission times for each outgoing message), or only calculate round trip times based on initial transmissions. | ## Superseded No document obsoletes RFC 1035, so its obligations are stated where they were written. --- ### Page: RFC 1071 - Computing the Internet Checksum https://ze-software.net/quality/rfc-compliance/rfc1071/ # RFC 1071 - Computing the Internet Checksum No row in the public ledger. Every requirement this repository extracted from RFC 1071, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 25.0% | 2 of 8 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 75.0% | 6 of 8 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 8 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 8 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 11 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 8 | of 11 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 8 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 8 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 8 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 8 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 8 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 11 | | Gated MUST-level | 8 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 11 | | Tagged units | 11 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc1071.md` | | Requirement shard | `rfc/requirements/rfc1071.md` | | RFC text | `rfc/full/rfc1071.txt` | ## Enrolment Enrolled: Computing the Internet Checksum (ones-complement 16-bit): eight MUST-level requirements, all met across ze's four checksum implementations (OSPF header/LSA, VRRP, ICMP probe, RSVP-TE). 1-5 (verification: the sum including the checksum folds to 0xffff) and x-1 (OSPF excludes the 8-octet Authentication field) carry positive+negative tags. The arithmetic-shape MUSTs 1-1 (zero the field before computing, store the complement), 1-2 (ones-complement sum with end-around carry), 1-3 (store the bitwise complement of the sum), 1-4 (odd-length zero-pad in the sum only, transmitted length unchanged), 1-6 (fold carries until none remain), and x-2 (AuType2 checksum field is zero) are {single-polarity: positive}, each having no reject path. Tags on internal/plugins/ospf/packet, internal/plugins/ospf/types, internal/core/probe, internal/plugins/vrrp/packet tests. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 1071. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 6 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **8** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC1071-1-5`](#rfc1071-1-5), [`RFC1071-x-1`](#rfc1071-x-1) **Annotated instead of tested (6):** [`RFC1071-1-1`](#rfc1071-1-1), [`RFC1071-1-2`](#rfc1071-1-2), [`RFC1071-1-3`](#rfc1071-1-3), [`RFC1071-1-4`](#rfc1071-1-4), [`RFC1071-1-6`](#rfc1071-1-6), [`RFC1071-x-2`](#rfc1071-x-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1071-1-1` | Zero the checksum field before computing the one's-complement sum (§1). | MUST | 1 | **positive:** `unit/verify` [`TestOSPFPacketChecksum`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L14). **negative:** no negative test. **{single-polarity}:** generate-side shape -- header.go:301 zeroes the Checksum field and header.go:322-323 stores the complemented PacketChecksum, pinned by the round-trip test; a generate rule has no reject path, corruption detection being requirement 1-5 | | `RFC1071-1-2` | Sum 16-bit words with end-around carry (overflow folded into the low-order bit) (§1). | MUST | 1 | **positive:** `unit/verify` [`TestChecksumRFC1071`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/checksum_test.go#L30). **negative:** no negative test. **{single-polarity}:** pure accumulator -- vrrp/packet/checksum.go:19-38 onesComplementSum+fold is cross-checked against an independent straight-line RFC 1071 reference on even and odd inputs, which discriminates a missing end-around carry; a summation function has no reject path | | `RFC1071-1-3` | Store the one's complement (bitwise NOT) of the 16-bit sum in the checksum field (§1). | MUST | 1 | **positive:** `unit/verify` [`TestInternetChecksumRFC1071Vectors`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/checksum_test.go#L61). **negative:** no negative test. **{single-polarity}:** generate-side shape -- internetChecksum returns the bitwise-NOT of the folded sum (ospf/types/checksum.go:102) and the exact vector 0x1411 fails if the complement is dropped; a generate rule has no reject path | | `RFC1071-1-4` | Pad an odd-length region with one zero octet for the sum only; do not transmit the pad (§1). | MUST | 1 | **positive:** `unit/verify` [`TestInternetChecksumOddLength`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/checksum_test.go#L97). **positive:** `unit/verify` [`TestRFC792ChecksumOddLength`](https://github.com/ze-software/ze/blob/main/internal/core/probe/icmp_test.go#L231). **negative:** no negative test. **{single-polarity}:** generate-side shape -- internetSum pads an odd tail with one zero octet for the sum only (ospf/types/checksum.go:146-148) while the transmitted length stays odd, pinned by the odd-length vectors; a generate rule has no reject path | | `RFC1071-1-5` | Verify by summing over all octets including the checksum field and checking for an all-ones (0xFFFF) result (§1). | MUST | 1 | **positive:** `unit/verify` [`TestRFC792ChecksumValid`](https://github.com/ze-software/ze/blob/main/internal/core/probe/icmp_test.go#L210). **negative:** `unit/verify` [`TestRFC792ChecksumRejectsCorruption`](https://github.com/ze-software/ze/blob/main/internal/core/probe/icmp_test.go#L220) | | `RFC1071-1-6` | Fold a wide (32/64-bit) accumulator down to 16 bits, repeating until no high bits remain, before inverting (§1, §2). | MUST | 1 | **positive:** `unit/verify` [`TestInternetChecksumRFC1071Vectors`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/checksum_test.go#L62). **negative:** no negative test. **{single-polarity}:** pure arithmetic -- internetChecksum folds the wide accumulator until no high bits remain before inverting (ospf/types/checksum.go:99-101), exercised by a carry-producing vector; a fold has no reject path | | `RFC1071-2-1` | Respect the even/odd octet assignment when splitting or reordering partial sums (commutative/associative property, §2). | SHOULD | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC1071-2-2` | Sum in native byte order and byte-swap only the final 16-bit result (byte-swap property, §2). | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC1071-2-3` | Update the checksum incrementally from a changed field rather than recomputing (§2; exact form per RFC 1624). | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC1071-x-1` | (OSPF) Compute over the entire OSPF packet **excluding** the 64-bit Authentication field, with the Checksum field zeroed during computation (RFC 2328 §A.3.1). | MUST | x | **positive:** `unit/verify` [`TestOSPFPacketChecksumExcludesAuth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L30). **negative:** `unit/verify` [`TestOSPFPacketChecksumExcludesAuth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L31) | | `RFC1071-x-2` | (OSPF, AuType 2) Set the Checksum field to zero entirely; do not use it (RFC 2328 §A.3.1). | MUST | x | **positive:** `unit/verify` [`TestOSPFPacketChecksumZeroForAuType2`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L55). **negative:** no negative test. **{single-polarity}:** generate-side convention -- WriteTo leaves the AuType2 Checksum field zero (ospf/packet/header.go:317-321) and VerifyPacketChecksum accepts the zero checksum (ospf/packet/checksum.go:28-30); only the field-is-zero positive is asserted | ## Gaps and untested MUSTs RFC 1071 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1071-1-1`](#rfc1071-1-1) Zero the checksum field before computing the one's-complement sum (§1). Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestOSPFPacketChecksum`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L14) | unit/verify | unproven | ### [`RFC1071-1-2`](#rfc1071-1-2) Sum 16-bit words with end-around carry (overflow folded into the low-order bit) (§1). Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChecksumRFC1071`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/checksum_test.go#L30) | unit/verify | unproven | ### [`RFC1071-1-3`](#rfc1071-1-3) Store the one's complement (bitwise NOT) of the 16-bit sum in the checksum field (§1). Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInternetChecksumRFC1071Vectors`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/checksum_test.go#L61) | unit/verify | unproven | ### [`RFC1071-1-4`](#rfc1071-1-4) Pad an odd-length region with one zero octet for the sum only; do not transmit the pad (§1). Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC792ChecksumOddLength`](https://github.com/ze-software/ze/blob/main/internal/core/probe/icmp_test.go#L231) | unit/verify | unproven | | positive | [`TestInternetChecksumOddLength`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/checksum_test.go#L97) | unit/verify | unproven | ### [`RFC1071-1-5`](#rfc1071-1-5) Verify by summing over all octets including the checksum field and checking for an all-ones (0xFFFF) result (§1). Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC792ChecksumRejectsCorruption`](https://github.com/ze-software/ze/blob/main/internal/core/probe/icmp_test.go#L220) | unit/verify | unproven | | positive | [`TestRFC792ChecksumValid`](https://github.com/ze-software/ze/blob/main/internal/core/probe/icmp_test.go#L210) | unit/verify | unproven | ### [`RFC1071-1-6`](#rfc1071-1-6) Fold a wide (32/64-bit) accumulator down to 16 bits, repeating until no high bits remain, before inverting (§1, §2). Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInternetChecksumRFC1071Vectors`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/checksum_test.go#L62) | unit/verify | unproven | ### [`RFC1071-x-1`](#rfc1071-x-1) (OSPF) Compute over the entire OSPF packet **excluding** the 64-bit Authentication field, with the Checksum field zeroed during computation (RFC 2328 §A.3.1). Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFPacketChecksumExcludesAuth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L31) | unit/verify | unproven | | positive | [`TestOSPFPacketChecksumExcludesAuth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L30) | unit/verify | unproven | ### [`RFC1071-x-2`](#rfc1071-x-2) (OSPF, AuType 2) Set the Checksum field to zero entirely; do not use it (RFC 2328 §A.3.1). Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestOSPFPacketChecksumZeroForAuType2`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L55) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 1071, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 1071, so its obligations are stated where they were written. --- ### Page: RFC 1195 - Use of OSI IS-IS for Routing in TCP/IP and Dual Environments https://ze-software.net/quality/rfc-compliance/rfc1195/ # RFC 1195 - Use of OSI IS-IS for Routing in TCP/IP and Dual Environments Experimental. Every requirement this repository extracted from RFC 1195, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 10.0% | 1 of 10 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 50.0% | 5 of 10 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 10 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 10 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 8 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 10 | of 11 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 4 | of 10 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 40.0% | 4 of 10 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 10 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 10 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 10 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 11 | | Gated MUST-level | 10 | | Not applicable, so out of scope | 4 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 8 | | Tagged units | 8 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc1195.md` | | Requirement shard | `rfc/requirements/rfc1195.md` | | RFC text | `rfc/full/rfc1195.txt` | ## Enrolment Enrolled: Use of OSI IS-IS for Routing in TCP/IP and Dual Environments (Integrated IS-IS): ten MUST-level requirements. Six are met in internal/plugins/isis: 5.2-1 (advertise the Protocols Supported TLV 129 with NLPID 0xCC for IPv4), 5.2-2 (include the IP Interface Address TLV 132), 5.2-3 (advertise the same IP interface addresses at Level 1 and Level 2), and 5.2-4 (every IP reachability entry carries a metric) are {single-polarity: positive}; 3.9-1 (discard a PDU that fails authentication) carries positive+negative tags; 3.1-1 (flood LSPs with unrecognized TLVs preserved verbatim) is {single-polarity: positive}. Four are {not-applicable}: 5.3.4-1 (the I/E bit in the narrow TLV 128), 5.3.4-2 (narrow TLVs 128/130/131 in a pseudonode LSP), and 3.2-1 (cap the narrow 6-bit metric at 63) -- ze is wide-metric-only and encodes only the RFC 5305 extended TLV 135, never the narrow TLVs; and 1.4-1 (the dual OSI/IP topology constraint) -- ze runs pure-IP Integrated IS-IS and routes no OSI/CLNP. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** Native IS-IS over Layer 2, L1/L2, broadcast and point-to-point circuits, TLV 132. **What the ledger says remains:** Feature remains Experimental pending production hardening and deployment evidence. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 1 | one part of the gated population | | Annotated instead of tested | 9 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **10** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (1):** [`RFC1195-3.9-1`](#rfc1195-3.9-1) **Annotated instead of tested (9):** [`RFC1195-5.2-1`](#rfc1195-5.2-1), [`RFC1195-5.2-2`](#rfc1195-5.2-2), [`RFC1195-5.2-3`](#rfc1195-5.2-3), [`RFC1195-5.3.4-1`](#rfc1195-5.3.4-1), [`RFC1195-5.3.4-2`](#rfc1195-5.3.4-2), [`RFC1195-5.2-4`](#rfc1195-5.2-4), [`RFC1195-3.2-1`](#rfc1195-3.2-1), [`RFC1195-3.1-1`](#rfc1195-3.1-1), [`RFC1195-1.4-1`](#rfc1195-1.4-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1195-5.2-1` | Include Protocols Supported (129) in all Hellos, all LSP number 0, and point-to-point ISHs (Section 5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestISISOriginateOnAdjacencyUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_test.go#L122). **negative:** no negative test. **{single-polarity}:** ze unconditionally emits the Protocols Supported TLV 129 with NLPID 0xCC for every IP-capable circuit IIH (internal/plugins/isis/circuit/hello.go:97-104) and LSP fragment 0 (internal/plugins/isis/lsdb/origination.go:407-409); there is no code path that omits TLV 129 or emits a non-0xCC IPv4 NLPID, so there is no negative form to reject | | `RFC1195-5.2-2` | Include IP Interface Address (132) in every IP-capable router's LSPs (Section 5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestISISOriginateOnAdjacencyUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_test.go#L110). **positive:** `unit/verify` [`TestISISTLVIPv4InterfaceAddr`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_ipv4_test.go#L19). **negative:** no negative test. **{single-polarity}:** ze packs the IP Interface Address TLV 132 into LSP fragment 0 from the node's own interface addresses (internal/plugins/isis/lsdb/origination.go:433-438) and the codec reads it back verbatim (internal/plugins/isis/packet/tlv_ipv4.go DecodeIPv4InterfaceAddrTLV); this is an emit obligation with no decode-side reject-on-absence path, so there is no negative form to drive | | `RFC1195-5.2-3` | Advertise the same IP address(es) at Level 1 and Level 2 when the router is both (Section 5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestISISEngineOriginateOnAdjacencyUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb_wiring_test.go#L81). **negative:** no negative test. **{single-polarity}:** ze collects interface addresses level-independently (internal/plugins/isis/lsdb_wiring.go:436-448) so the same set is advertised at both levels by construction; there is no per-level divergence code path to drive a negative | | `RFC1195-5.3.4-1` | Set the I/E bit to 0 in IP Internal Reachability (128) entries (Section 5.3.4) | MUST | 5.3.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is wide-metric-only and its IS-IS codec never encodes the narrow IP Reachability TLV 128 (internal/plugins/isis/packet/tlv.go recognizes no type 128; internal/plugins/isis/types/metric.go uses 24-bit and 32-bit metrics, not the 6-bit narrow field). It advertises IP reachability via the RFC 5305 extended TLV 135 (already enrolled), which has no I/E bit, so there is no narrow-TLV I/E-bit code path | | `RFC1195-5.3.4-2` | Place codes 128, 130, 131 in pseudonode LSPs (Section 5.3.4, Section 5.3.5) | MUST NOT | 5.3.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never emits the narrow IP reachability TLVs 128/130/131 (wide-metric-only), and its pseudonode LSP builder emits only TLV 22 (internal/plugins/isis/lsdb/pseudonode.go:186-193), so the literal-code prohibition has no applicable code path | | `RFC1195-5.2-4` | Carry a default metric in every reachability entry (Section 5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestISISTLVIPv4RoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_ipv4_test.go#L66). **negative:** no negative test. **{single-polarity}:** TLV 135 WriteTo unconditionally writes the 4-octet prefix metric for every entry (internal/plugins/isis/packet/tlv_ipv4.go ExtendedIPReachTLV.WriteTo) and the metric is a mandatory struct field (internal/plugins/isis/types/metric.go PrefixMetric), so a metric-less entry is unrepresentable and there is no negative form to reject | | `RFC1195-3.2-1` | Cap a Level 2 metric sum at 63 (Section 3.2) | MUST | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze uses RFC 5305 wide metrics (24-bit IS reachability and 32-bit IP reachability, internal/plugins/isis/types/metric.go) and has no 6-bit narrow metric field to cap at 63 | | `RFC1195-3.9-1` | Discard a packet with invalid authentication information (Section 3.9) | MUST | 3.9 | **positive:** `unit/verify` [`TestISISAuthSignVerifyHMACMD5`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L134). **negative:** `unit/verify` [`TestISISAuthConstantTimeCompare`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L611) | | `RFC1195-3.1-1` | Ignore unrecognised codes and pass them unchanged in forwarded LSPs (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestISISUnknownTLVPassthrough`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_opaque_test.go#L16). **negative:** no negative test. **{single-polarity}:** the codec retains every TLV type it does not recognize as an opaque span and re-encodes it byte-for-byte (internal/plugins/isis/packet/tlv.go:66-89 iterator plus DecodeTLVs / writeTLVs) so the LSDB re-floods unknown TLVs verbatim (internal/plugins/isis/lsdb/flooding.go:342-360); preserving unrecognized codes IS the requirement and there is no reject path for an unknown TLV, so no negative form exists | | `RFC1195-1.4-1` | Use only dual routers in a dual area, and dual Level 2 routers when routing both IP and OSI between areas (Section 1.4) | MUST | 1.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs pure-IP Integrated IS-IS and routes no OSI/CLNP traffic (no CLNP forwarding code path), so the dual-domain topology constraint does not apply | | `RFC1195-5.3.5-1` | Set the I/E bit to 0 or 1 in IP External Reachability (130) entries (Section 5.3.5) | MAY | 5.3.5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC1195-5.3.4-1`](#rfc1195-5.3.4-1) Set the I/E bit to 0 in IP Internal Reachability (128) entries (Section 5.3.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze is wide-metric-only and its IS-IS codec never encodes the narrow IP Reachability TLV 128 (internal/plugins/isis/packet/tlv.go recognizes no type 128; internal/plugins/isis/types/metric.go uses 24-bit and 32-bit metrics, not the 6-bit narrow field). It advertises IP reachability via the RFC 5305 extended TLV 135 (already enrolled), which has no I/E bit, so there is no narrow-TLV I/E-bit code path | | [`RFC1195-5.3.4-2`](#rfc1195-5.3.4-2) Place codes 128, 130, 131 in pseudonode LSPs (Section 5.3.4, Section 5.3.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze never emits the narrow IP reachability TLVs 128/130/131 (wide-metric-only), and its pseudonode LSP builder emits only TLV 22 (internal/plugins/isis/lsdb/pseudonode.go:186-193), so the literal-code prohibition has no applicable code path | | [`RFC1195-3.2-1`](#rfc1195-3.2-1) Cap a Level 2 metric sum at 63 (Section 3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze uses RFC 5305 wide metrics (24-bit IS reachability and 32-bit IP reachability, internal/plugins/isis/types/metric.go) and has no 6-bit narrow metric field to cap at 63 | | [`RFC1195-1.4-1`](#rfc1195-1.4-1) Use only dual routers in a dual area, and dual Level 2 routers when routing both IP and OSI between areas (Section 1.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs pure-IP Integrated IS-IS and routes no OSI/CLNP traffic (no CLNP forwarding code path), so the dual-domain topology constraint does not apply | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1195-5.2-1`](#rfc1195-5.2-1) Include Protocols Supported (129) in all Hellos, all LSP number 0, and point-to-point ISHs (Section 5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISOriginateOnAdjacencyUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_test.go#L122) | unit/verify | unproven | ### [`RFC1195-5.2-2`](#rfc1195-5.2-2) Include IP Interface Address (132) in every IP-capable router's LSPs (Section 5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISOriginateOnAdjacencyUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_test.go#L110) | unit/verify | unproven | | positive | [`TestISISTLVIPv4InterfaceAddr`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_ipv4_test.go#L19) | unit/verify | unproven | ### [`RFC1195-5.2-3`](#rfc1195-5.2-3) Advertise the same IP address(es) at Level 1 and Level 2 when the router is both (Section 5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISEngineOriginateOnAdjacencyUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb_wiring_test.go#L81) | unit/verify | unproven | ### [`RFC1195-5.3.4-1`](#rfc1195-5.3.4-1) Set the I/E bit to 0 in IP Internal Reachability (128) entries (Section 5.3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC1195-5.3.4-1, so no unit is bound to it. ### [`RFC1195-5.3.4-2`](#rfc1195-5.3.4-2) Place codes 128, 130, 131 in pseudonode LSPs (Section 5.3.4, Section 5.3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC1195-5.3.4-2, so no unit is bound to it. ### [`RFC1195-5.2-4`](#rfc1195-5.2-4) Carry a default metric in every reachability entry (Section 5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISTLVIPv4RoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_ipv4_test.go#L66) | unit/verify | unproven | ### [`RFC1195-3.2-1`](#rfc1195-3.2-1) Cap a Level 2 metric sum at 63 (Section 3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC1195-3.2-1, so no unit is bound to it. ### [`RFC1195-3.9-1`](#rfc1195-3.9-1) Discard a packet with invalid authentication information (Section 3.9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthConstantTimeCompare`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L611) | unit/verify | unproven | | positive | [`TestISISAuthSignVerifyHMACMD5`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L134) | unit/verify | unproven | ### [`RFC1195-3.1-1`](#rfc1195-3.1-1) Ignore unrecognised codes and pass them unchanged in forwarded LSPs (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISUnknownTLVPassthrough`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_opaque_test.go#L16) | unit/verify | unproven | ### [`RFC1195-1.4-1`](#rfc1195-1.4-1) Use only dual routers in a dual area, and dual Level 2 routers when routing both IP and OSI between areas (Section 1.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC1195-1.4-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 1195, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 1195, so its obligations are stated where they were written. --- ### Page: RFC 1332 - The PPP Internet Protocol Control Protocol (IPCP) https://ze-software.net/quality/rfc-compliance/rfc1332/ # RFC 1332 - The PPP Internet Protocol Control Protocol (IPCP) Partial. Every requirement this repository extracted from RFC 1332, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 50.0% | 3 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 6 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 11 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 2 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 33.3% | 2 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 16.7% | 1 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 11 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 2 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 6 | | Tagged units | 6 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc1332.md` | | Requirement shard | `rfc/requirements/rfc1332.md` | | RFC text | `rfc/full/rfc1332.txt` | ## Enrolment Enrolled: PPP Internet Protocol Control Protocol (IPCP): six MUST-level requirements. Three are met with positive+negative tags in internal/component/l2tp/ppp: 2-1 (one IPCP packet per 0x8021 frame), 2.1-1 (IPCP reaches Opened before IP is programmed), 3-1 (options follow RFC 1661 TLV format). 2-2 (codes 8-11 on IPCP MUST be Code-Rejected) is {gap}: ze's shared codeToEvent (session_run.go:688-709) maps codes 8-11 to LCP echo/protocol-reject handling, so only codes >= 12 are Code-Rejected; disclosed in the docs/features/rfc-status.md RFC 1332 row. 3.1-1 (no IP-Addresses Type 1) and 4.1-1 (no Van Jacobson Type 2) are {not-applicable}: ze emits only IPCP options 3/129/131 and Configure-Rejects a peer Type-2 option. ## What the public ledger says **Status:** Partial **What the ledger says is covered:** 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. **What the ledger says remains:** One MUST gap gated in [`rfc/short/rfc1332.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc1332.md): codes 8-11 received on IPCP are not Code-Rejected (mapped to LCP echo/protocol-reject handling); codes 12 and above are Code-Rejected. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 3 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (3):** [`RFC1332-2-1`](#rfc1332-2-1), [`RFC1332-2.1-1`](#rfc1332-2.1-1), [`RFC1332-3-1`](#rfc1332-3-1) **Annotated instead of tested (3):** [`RFC1332-2-2`](#rfc1332-2-2), [`RFC1332-3.1-1`](#rfc1332-3.1-1), [`RFC1332-4.1-1`](#rfc1332-4.1-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1332-2-1` | Exactly one IPCP packet encapsulated per PPP frame with Protocol field 0x8021 (Section 2) | MUST | 2 | **positive:** `unit/verify` [`TestIPCPFrameCarriesExactlyOnePacket`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/frame_test.go#L145). **negative:** `unit/verify` [`TestIPCPFrameRejectsNonSinglePacket`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/frame_test.go#L188) | | `RFC1332-2-2` | Only Codes 1-7 accepted; other codes treated as unrecognized and result in Code-Reject (Section 2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's shared codeToEvent (internal/component/l2tp/ppp/session_run.go:688-709) maps PPP control codes 8-11 to LCP echo/protocol-reject events rather than RUC, so ze does not Code-Reject codes 8-11 received on IPCP; only codes 12 and above are Code-Rejected | | `RFC1332-2.1-1` | IPCP reach Opened state before any IP packets may be communicated (Section 2.1) | MUST | 2.1 | **positive:** `unit/verify` [`TestIPResponseConfiguresInterface`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L110). **negative:** `unit/verify` [`TestIPCPNoAddressBeforeOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L160) | | `RFC1332-3-1` | Configuration options follow the format defined in RFC 1661 (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestIPCPParseOptions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ipcp_test.go#L14). **negative:** `unit/verify` [`TestIPCPParseRejects`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ipcp_test.go#L71) | | `RFC1332-3.1-1` | Implementation sending IP-Addresses option must not send it in Configure-Request if peer sent any Configure-Request containing IP-Addresses or IP-Address option (Section 3.1) | MUST NOT | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no IP-Addresses (Type 1) send code path; internal/component/l2tp/ppp/ipcp.go defines only options 3/129/131 and WriteIPCPOptions emits only those | | `RFC1332-4.1-1` | Comp-Slot-Id=1 must not be enabled on links without link-level error-indication mechanism (Section 4.1) | MUST NOT | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze does not negotiate the IP-Compression-Protocol (Type 2) Van Jacobson option; isKnownIPCPOption (internal/component/l2tp/ppp/ipcp.go:53-55) recognizes only options 3/129/131 and Configure-Rejects a peer Type-2 option; Comp-Slot-Id is never set | | `RFC1332-2-3` | Be prepared to wait for Authentication and Link Quality Determination to finish before timing out waiting for Configure-Ack (Section 2) | SHOULD | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC1332-2.1-2` | IP datagrams should use mechanisms (TCP MSS, PMTUD) to avoid fragmentation (Section 2.1) | SHOULD | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC1332-3.3-1` | If negotiation about remote IP-address is required and peer did not provide the option, append IP-Address option to Configure-Nak (Section 3.3) | SHOULD | 3.3 | **positive:** no positive test. **negative:** no negative test | | `RFC1332-3.1-2` | Send IP-Addresses option if previously received Configure-Reject for IP-Address or Configure-Nak with IP-Addresses (Section 3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC1332-3.1-3` | Support for IP-Addresses option may be removed (Section 3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC1332-2-2`](#rfc1332-2-2) Only Codes 1-7 accepted; other codes treated as unrecognized and result in Code-Reject (Section 2) | {gap}, no test | ze's shared codeToEvent (internal/component/l2tp/ppp/session_run.go:688-709) maps PPP control codes 8-11 to LCP echo/protocol-reject events rather than RUC, so ze does not Code-Reject codes 8-11 received on IPCP; only codes 12 and above are Code-Rejected | | [`RFC1332-3.1-1`](#rfc1332-3.1-1) Implementation sending IP-Addresses option must not send it in Configure-Request if peer sent any Configure-Request containing IP-Addresses or IP-Address option (Section 3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no IP-Addresses (Type 1) send code path; internal/component/l2tp/ppp/ipcp.go defines only options 3/129/131 and WriteIPCPOptions emits only those | | [`RFC1332-4.1-1`](#rfc1332-4.1-1) Comp-Slot-Id=1 must not be enabled on links without link-level error-indication mechanism (Section 4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze does not negotiate the IP-Compression-Protocol (Type 2) Van Jacobson option; isKnownIPCPOption (internal/component/l2tp/ppp/ipcp.go:53-55) recognizes only options 3/129/131 and Configure-Rejects a peer Type-2 option; Comp-Slot-Id is never set | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1332-2-1`](#rfc1332-2-1) Exactly one IPCP packet encapsulated per PPP frame with Protocol field 0x8021 (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPCPFrameRejectsNonSinglePacket`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/frame_test.go#L188) | unit/verify | unproven | | positive | [`TestIPCPFrameCarriesExactlyOnePacket`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/frame_test.go#L145) | unit/verify | unproven | ### [`RFC1332-2-2`](#rfc1332-2-2) Only Codes 1-7 accepted; other codes treated as unrecognized and result in Code-Reject (Section 2) Audit verdict: not audited: no reader has judged these tests No test carries RFC1332-2-2, so no unit is bound to it. ### [`RFC1332-2.1-1`](#rfc1332-2.1-1) IPCP reach Opened state before any IP packets may be communicated (Section 2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPCPNoAddressBeforeOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L160) | unit/verify | unproven | | positive | [`TestIPResponseConfiguresInterface`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L110) | unit/verify | unproven | ### [`RFC1332-3-1`](#rfc1332-3-1) Configuration options follow the format defined in RFC 1661 (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPCPParseRejects`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ipcp_test.go#L71) | unit/verify | unproven | | positive | [`TestIPCPParseOptions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ipcp_test.go#L14) | unit/verify | unproven | ### [`RFC1332-3.1-1`](#rfc1332-3.1-1) Implementation sending IP-Addresses option must not send it in Configure-Request if peer sent any Configure-Request containing IP-Addresses or IP-Address option (Section 3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC1332-3.1-1, so no unit is bound to it. ### [`RFC1332-4.1-1`](#rfc1332-4.1-1) Comp-Slot-Id=1 must not be enabled on links without link-level error-indication mechanism (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC1332-4.1-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 1332, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 1332, so its obligations are stated where they were written. --- ### Page: RFC 1334 - PPP Authentication Protocols https://ze-software.net/quality/rfc-compliance/rfc1334/ # RFC 1334 - PPP Authentication Protocols Partial. Every requirement this repository extracted from RFC 1334, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 71.4% | 5 of 7 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 14.3% | 1 of 7 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 7 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 7 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 12 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 7 | of 10 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 7 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 14.3% | 1 of 7 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 7 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 7 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 7 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 10 | | Gated MUST-level | 7 | | Not applicable, so out of scope | 1 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 12 | | Tagged units | 12 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc1334.md` | | Requirement shard | `rfc/requirements/rfc1334.md` | | RFC text | `rfc/full/rfc1334.txt` | ## Enrolment Enrolled: PPP Authentication Protocols (PAP and the LCP Authentication-Protocol option): seven MUST-level requirements. Five are met with positive+negative tags in internal/component/l2tp/ppp: 1-1 (advertise Auth-Protocol in LCP CONFREQ when auth is desired), x-1 (offer CHAP before PAP via the default fallback order), 2.3-1 (Authenticate-Ack Code 2 on accept), 2.3-2 (Authenticate-Nak Code 3 on reject), and 2.3-3 (echo the request Identifier into the reply). 2.3-3 is {single-polarity: positive} (the reply copies the Identifier and never validates or rejects on it). 2.3-4 (the Ack/Nak Message field must not affect operation) is met by a new pppoeclient test proving runClientAuth branches only on Code. 2.2-1 (change Identifier on reissue) is {not-applicable}: ze never reissues a PAP Authenticate-Request. ## What the public ledger says **Status:** Partial **What the ledger says is covered:** PAP authentication option through PPP auth handling. **What the ledger says remains:** Carries the L2TP and PPPoE Partial status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 5 | one part of the gated population | | Annotated instead of tested | 2 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **7** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (5):** [`RFC1334-1-1`](#rfc1334-1-1), [`RFC1334-x-1`](#rfc1334-x-1), [`RFC1334-2.3-1`](#rfc1334-2.3-1), [`RFC1334-2.3-2`](#rfc1334-2.3-2), [`RFC1334-2.3-4`](#rfc1334-2.3-4) **Annotated instead of tested (2):** [`RFC1334-2.2-1`](#rfc1334-2.2-1), [`RFC1334-2.3-3`](#rfc1334-2.3-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1334-1-1` | If authentication is desired, specify Authentication-Protocol Configuration Option during Link Establishment phase (Section 1) | MUST | 1 | **positive:** `unit/verify` [`TestLocalCONFREQAdvertisesAuthMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_dispatch_test.go#L669). **negative:** `unit/verify` [`TestAuthProtoRejectClearsMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_dispatch_test.go#L148) | | `RFC1334-x-1` | Any implementation that includes a stronger authentication method (such as CHAP) must offer to negotiate that method prior to PAP (Security Considerations) | MUST | x | **positive:** `unit/verify` [`TestDefaultAuthFallbackOrder`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_test.go#L269). **negative:** `unit/verify` [`TestSelectAuthFallback`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_test.go#L111) | | `RFC1334-2.3-1` | If id/password pair is recognizable and acceptable, authenticator must transmit Authenticate-Ack (Code=2) (Section 2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestPAPRequestEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L293). **negative:** `unit/verify` [`TestPAPRejectWritesNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L411) | | `RFC1334-2.3-2` | If id/password pair is not recognizable or acceptable, authenticator must transmit Authenticate-Nak (Code=3) (Section 2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestPAPRejectWritesNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L407). **negative:** `unit/verify` [`TestPAPRequestEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L297) | | `RFC1334-2.2-1` | Identifier field must be changed each time an Authenticate-Request is issued (Section 2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never issues a second PAP Authenticate-Request; the authenticator role only responds (internal/component/l2tp/ppp/pap.go:160) and the client issues exactly one request per session with a fixed Identifier and no reissue path (internal/component/l2tp/pppoeclient/session.go:230-241), so the change-Identifier-on-reissue obligation has no code path | | `RFC1334-2.3-3` | Ack/Nak Identifier must be copied from the Authenticate-Request which caused the reply (Section 2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestPAPRejectWritesNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L414). **positive:** `unit/verify` [`TestPAPRequestEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L300). **negative:** no negative test. **{single-polarity}:** writePAPReply copies the request Identifier into the PAP reply (internal/component/l2tp/ppp/pap.go:138) and ze never validates or rejects on the Identifier, so there is no negative case | | `RFC1334-2.3-4` | Message field must not affect operation of the protocol (Section 2.3) | MUST NOT | 2.3 | **positive:** `unit/verify` [`TestPAPReplyMessageDoesNotAffectOutcome`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1334_pap_message_test.go#L49). **negative:** `unit/verify` [`TestPAPReplyMessageDoesNotAffectOutcome`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1334_pap_message_test.go#L53) | | `RFC1334-2.3-5` | Authenticator should take action to terminate the link on Nak (Section 2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC1334-2.3-6` | Message field may be empty (Section 2.3) | MAY | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC1334-2.2-2` | Implementations may retry Authenticate-Request at implementation-defined intervals (Section 2.2) | MAY | 2.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC1334-2.2-1`](#rfc1334-2.2-1) Identifier field must be changed each time an Authenticate-Request is issued (Section 2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze never issues a second PAP Authenticate-Request; the authenticator role only responds (internal/component/l2tp/ppp/pap.go:160) and the client issues exactly one request per session with a fixed Identifier and no reissue path (internal/component/l2tp/pppoeclient/session.go:230-241), so the change-Identifier-on-reissue obligation has no code path | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1334-1-1`](#rfc1334-1-1) If authentication is desired, specify Authentication-Protocol Configuration Option during Link Establishment phase (Section 1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAuthProtoRejectClearsMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_dispatch_test.go#L148) | unit/verify | unproven | | positive | [`TestLocalCONFREQAdvertisesAuthMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_dispatch_test.go#L669) | unit/verify | unproven | ### [`RFC1334-x-1`](#rfc1334-x-1) Any implementation that includes a stronger authentication method (such as CHAP) must offer to negotiate that method prior to PAP (Security Considerations) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSelectAuthFallback`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_test.go#L111) | unit/verify | unproven | | positive | [`TestDefaultAuthFallbackOrder`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_test.go#L269) | unit/verify | unproven | ### [`RFC1334-2.3-1`](#rfc1334-2.3-1) If id/password pair is recognizable and acceptable, authenticator must transmit Authenticate-Ack (Code=2) (Section 2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPAPRejectWritesNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L411) | unit/verify | unproven | | positive | [`TestPAPRequestEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L293) | unit/verify | unproven | ### [`RFC1334-2.3-2`](#rfc1334-2.3-2) If id/password pair is not recognizable or acceptable, authenticator must transmit Authenticate-Nak (Code=3) (Section 2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPAPRequestEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L297) | unit/verify | unproven | | positive | [`TestPAPRejectWritesNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L407) | unit/verify | unproven | ### [`RFC1334-2.2-1`](#rfc1334-2.2-1) Identifier field must be changed each time an Authenticate-Request is issued (Section 2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC1334-2.2-1, so no unit is bound to it. ### [`RFC1334-2.3-3`](#rfc1334-2.3-3) Ack/Nak Identifier must be copied from the Authenticate-Request which caused the reply (Section 2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestPAPRejectWritesNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L414) | unit/verify | unproven | | positive | [`TestPAPRequestEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/pap_test.go#L300) | unit/verify | unproven | ### [`RFC1334-2.3-4`](#rfc1334-2.3-4) Message field must not affect operation of the protocol (Section 2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPAPReplyMessageDoesNotAffectOutcome`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1334_pap_message_test.go#L53) | unit/verify | unproven | | positive | [`TestPAPReplyMessageDoesNotAffectOutcome`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1334_pap_message_test.go#L49) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 1334, so no reviewer has walked its text sentence by sentence. ## Superseded RFC 1334 is obsoleted by RFC 1994. | Requirement | Disposition | Now stated at | Reason | |---|---|---|---| | [`RFC1334-1-1`](#rfc1334-1-1) If authentication is desired, specify Authentication-Protocol Configuration Option during Link Establishment phase (Section 1) | restated | RFC1994-1-1 | RFC 1994 Section 1 repeats the sentence word for word, that an implementation MUST specify the Authentication-Protocol Configuration Option during Link Establishment phase if authentication of the link is desired. The obligation is PPP-wide and binds neither authentication protocol in particular | | [`RFC1334-x-1`](#rfc1334-x-1) Any implementation that includes a stronger authentication method (such as CHAP) must offer to negotiate that method prior to PAP (Security Considerations) | dropped | not stated | RFC 1994 defines CHAP alone and states no obligation to offer a stronger method before PAP. Its Security Considerations warn that authenticating one user name by several methods exposes the least secure of them, and recommend one method per user name, with no keyword. The rule is still owed for as long as PAP is offered | | [`RFC1334-2.3-1`](#rfc1334-2.3-1) If id/password pair is recognizable and acceptable, authenticator must transmit Authenticate-Ack (Code=2) (Section 2.3) | dropped | not stated | RFC 1994 defines no PAP packet. Its Section 4.2 obliges a CHAP packet with Code 3 (Success) when the received Response Value equals the expected value, which is the CHAP handshake rather than the PAP Authenticate-Ack. PAP stays defined by RFC 1334, as this summary's forward Meta row records | | [`RFC1334-2.3-2`](#rfc1334-2.3-2) If id/password pair is not recognizable or acceptable, authenticator must transmit Authenticate-Nak (Code=3) (Section 2.3) | dropped | not stated | RFC 1994 Section 4.2 obliges a CHAP packet with Code 4 (Failure) when the Response Value does not match, and defines no PAP Authenticate-Nak. The PAP rule is still owed for as long as PAP peers are authenticated | | [`RFC1334-2.2-1`](#rfc1334-2.2-1) Identifier field must be changed each time an Authenticate-Request is issued (Section 2.2) | dropped | not stated | RFC 1994 states no PAP obligation. Its Section 4.1 requires the Identifier to change each time a Challenge is sent, which binds the CHAP Challenge and not a reissued PAP Authenticate-Request | | [`RFC1334-2.3-3`](#rfc1334-2.3-3) Ack/Nak Identifier must be copied from the Authenticate-Request which caused the reply (Section 2.3) | dropped | not stated | RFC 1994 Section 4.2 requires the Success or Failure Identifier to be copied from the Response which caused the reply, which binds CHAP. It states nothing about a PAP Authenticate-Ack or Authenticate-Nak | | [`RFC1334-2.3-4`](#rfc1334-2.3-4) Message field must not affect operation of the protocol (Section 2.3) | dropped | not stated | RFC 1994 Section 4.2 carries the same MUST NOT for the Message field of a CHAP Success or Failure packet. It states nothing about the PAP Ack and Nak Message field, which RFC 1334 still defines | | [`RFC1334-2.3-5`](#rfc1334-2.3-5) Authenticator should take action to terminate the link on Nak (Section 2.3) | dropped | not stated | RFC 1994 Section 4.2 says a CHAP implementation SHOULD take action to terminate the link when it transmits a Failure. That binds the CHAP exchange, and RFC 1994 states nothing about a PAP Authenticate-Nak | | [`RFC1334-2.3-6`](#rfc1334-2.3-6) Message field may be empty (Section 2.3) | dropped | not stated | RFC 1994 Section 4.2 says the Message field of a CHAP Success or Failure is zero or more octets. It states nothing about the PAP Ack and Nak Message field | | [`RFC1334-2.2-2`](#rfc1334-2.2-2) Implementations may retry Authenticate-Request at implementation-defined intervals (Section 2.2) | dropped | not stated | RFC 1994 Section 4.1 obliges the authenticator to send further Challenges until a valid Response arrives or a retry counter expires, which is a CHAP obligation on the authenticator. RFC 1994 states nothing about a peer retrying a PAP Authenticate-Request | --- ### Page: RFC 1350 - The TFTP Protocol (Revision 2) https://ze-software.net/quality/rfc-compliance/rfc1350/ # RFC 1350 - The TFTP Protocol (Revision 2) Supported. Every requirement this repository extracted from RFC 1350, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 9.1% | 1 of 11 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 63.6% | 7 of 11 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 11 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 13 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 11 | of 15 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 2 | of 11 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 18.2% | 2 of 11 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 11 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 11 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 9.1% | 1 of 11 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 11 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 15 | | Gated MUST-level | 11 | | Not applicable, so out of scope | 2 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 13 | | Tagged units | 13 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc1350.md` | | Requirement shard | `rfc/requirements/rfc1350.md` | | RFC text | `rfc/full/rfc1350.txt` | ## Enrolment Enrolled: The TFTP Protocol (revision 2), base TFTP read-only server: eleven MUST-level requirements. Eight are met -- 2-1 (512-octet blocks with a short block ending the transfer), 6-1 (retransmit on timeout), 5-2 (octet mode transfers bytes unchanged), 4-1 (block numbers start at 1 and increment), 4-3 (a fresh transfer TID distinct from port 69), 7-1 (timeout then abort), and 5-3 (an exact-multiple file ends with a zero-length DATA block) are {single-polarity: positive} since ze is the DATA sender with no reject path; 2-2 (lockstep, advance only after ACK) carries positive+negative tags. 2-3 (silently ignore a duplicate or stale ACK per the Sorcerer's Apprentice fix) is {gap}: ze's sendAndWaitACK retransmits on any non-matching ACK. 5-1 (netascii translation on received data) and 4-2 (WRQ acknowledged with an ACK for block 0) are {not-applicable}: ze is a read-only server that rejects WRQ and accepts only octet mode. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** Read-only TFTP server for PXE bootloader delivery. **What the ledger says remains:** [`RFC1350-2-3`](#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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 1 | one part of the gated population | | Annotated instead of tested | 10 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **11** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (1):** [`RFC1350-2-2`](#rfc1350-2-2) **Annotated instead of tested (10):** [`RFC1350-2-1`](#rfc1350-2-1), [`RFC1350-6-1`](#rfc1350-6-1), [`RFC1350-5-1`](#rfc1350-5-1), [`RFC1350-5-2`](#rfc1350-5-2), [`RFC1350-4-1`](#rfc1350-4-1), [`RFC1350-4-2`](#rfc1350-4-2), [`RFC1350-4-3`](#rfc1350-4-3), [`RFC1350-7-1`](#rfc1350-7-1), [`RFC1350-2-3`](#rfc1350-2-3), [`RFC1350-5-3`](#rfc1350-5-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1350-2-1` | File is sent in fixed length blocks of 512 bytes (Section 2) | MUST | 2 - Overview of the Protocol | **positive:** `unit/verify` [`TestTFTPReadExact512`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L471). **positive:** `unit/verify` [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L390). **negative:** no negative test. **{single-polarity}:** ze is the DATA sender and always frames output at the block size, ending on a short or zero block (handler.go:360, 373, 379), so no client input can make it emit a non-conforming block and there is no reject path for a negative | | `RFC1350-2-2` | Each data packet must be acknowledged before the next packet can be sent (Section 2) | MUST | 2 - Overview of the Protocol | **positive:** `unit/verify` [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L393). **negative:** `unit/verify` [`TestTFTPConcurrentLimit`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L726) | | `RFC1350-6-1` | Host sending the last DATA must retransmit it until acknowledged or timed out (Section 6) | MUST | 6 - Normal Termination | **positive:** `unit/verify` [`TestTFTPRetransmitOnTimeout`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L757). **negative:** no negative test. **{single-polarity}:** retransmission fires only on ackTimeout expiry in sendAndWaitACK (handler.go:392-400). Its only counterpart, ceasing to resend once a valid ACK arrives, is the lockstep advance already pinned by RFC1350-2-2, so no distinct negative remains | | `RFC1350-5-1` | Host receiving netascii mode data must translate the data to its own format (Section 5) | MUST | 5 - TFTP Packets | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is a read-only TFTP server that rejects WRQ (internal/plugins/tftpserver/handler.go:227) and accepts only octet mode (handler.go:248), so it never receives file data to translate between netascii and its local format | | `RFC1350-5-2` | If a host receives an octet file and then returns it, the returned file must be identical to the original (Section 5) | MUST | 5 - TFTP Packets | **positive:** `unit/verify` [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L398). **positive:** `unit/verify` [`TestTFTPReadRequest`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L335). **negative:** no negative test. **{single-polarity}:** octet is the only accepted mode (handler.go:248) and serveFile copies file bytes verbatim into DATA (handler.go:360, 373), so no octet code path transforms bytes and there is no altered-bytes negative to test | | `RFC1350-4-1` | Block numbers are consecutive and begin with one (Section 4) | MUST | 4 - Initial Connection Protocol | **positive:** `unit/verify` [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L396). **positive:** `unit/verify` [`TestTFTPReadRequest`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L332). **negative:** no negative test. **{single-polarity}:** the server assigns block numbers itself, starting at 1 and incrementing (handler.go:362, 382), so no input can make it emit a non-1-based or non-consecutive number and there is no reject path for a negative | | `RFC1350-4-2` | Positive response to a write request is an ACK with block number zero (Section 4) | MUST | 4 - Initial Connection Protocol | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves reads only and rejects WRQ with an ERROR (internal/plugins/tftpserver/handler.go:227-230), so there is no WRQ-to-ACK-block-0 write-initiation path | | `RFC1350-4-3` | Each end of the connection chooses a TID for itself, used for the duration of that connection (Section 4) | MUST | 4 - Initial Connection Protocol | **positive:** `unit/verify` [`TestListenTFTPLoopbackRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/socket_integration_linux_test.go#L144). **negative:** no negative test. **{single-polarity}:** the server always allocates a fresh transfer socket via net.DialUDP per RRQ (handler.go:287), so no configuration or input reuses port 69 and there is no stale-TID negative case | | `RFC1350-7-1` | Timeouts must be used to detect errors (Section 7) | MUST | 7 - Premature Termination | **positive:** `unit/verify` [`TestTFTPRetransmitOnTimeout`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L760). **negative:** no negative test. **{single-polarity}:** the failure branch (abort after maxRetransmit ackTimeout expiries, handler.go:387 and 411, roughly 4x5s) needs about 20 seconds of real timeouts to reach, so a unit test exercises only the positive timeout-detection | | `RFC1350-2-3` | Duplicate ACKs must be silently ignored; must not resend next DATA block (Sorcerer's Apprentice fix, Section 2 / RFC 1123 Section 4.2) | MUST | 2 - Overview of the Protocol | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's sendAndWaitACK (internal/plugins/tftpserver/handler.go:386-413) retransmits the DATA block on any non-matching ACK (ackBlock != block, handler.go:407) rather than silently ignoring a duplicate or stale ACK, so the RFC 1350 Sorcerer's Apprentice Syndrome fix is not implemented | | `RFC1350-5-3` | When file size is exact multiple of 512, a final DATA packet with zero bytes of data must be sent (Section 5, Section 6) | MUST | 5 - TFTP Packets | **positive:** `unit/verify` [`TestTFTPReadEmptyFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L537). **positive:** `unit/verify` [`TestTFTPReadExact512`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L474). **negative:** no negative test. **{single-polarity}:** ze is the sender and always appends the zero-length terminator when the file length is an exact multiple of the block size (handler.go:364-383, 379), so no input can suppress or misplace it and there is no reject-path negative for this framing requirement | | `RFC1350-4-4` | If source TID does not match, packet should be discarded; an error packet should be sent to incorrect source while not disturbing the transfer (Section 4) | SHOULD | 4 - Initial Connection Protocol | **positive:** no positive test. **negative:** no negative test | | `RFC1350-4-5` | TIDs chosen for a connection should be randomly chosen (Section 4) | SHOULD | 4 - Initial Connection Protocol | **positive:** no positive test. **negative:** no negative test | | `RFC1350-6-2` | Host sending final ACK should dally (wait before terminating) to retransmit final ACK if lost (Section 6) | SHOULD | 6 - Normal Termination | **positive:** no positive test. **negative:** no negative test | | `RFC1350-5-4` | Error message in ERROR packet should be in netascii (Section 5) | SHOULD | 5 - TFTP Packets | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC1350-5-1`](#rfc1350-5-1) Host receiving netascii mode data must translate the data to its own format (Section 5) | no test | no test carries this requirement id; annotated {not-applicable}: ze is a read-only TFTP server that rejects WRQ (internal/plugins/tftpserver/handler.go:227) and accepts only octet mode (handler.go:248), so it never receives file data to translate between netascii and its local format | | [`RFC1350-4-2`](#rfc1350-4-2) Positive response to a write request is an ACK with block number zero (Section 4) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves reads only and rejects WRQ with an ERROR (internal/plugins/tftpserver/handler.go:227-230), so there is no WRQ-to-ACK-block-0 write-initiation path | | [`RFC1350-2-3`](#rfc1350-2-3) Duplicate ACKs must be silently ignored; must not resend next DATA block (Sorcerer's Apprentice fix, Section 2 / RFC 1123 Section 4.2) | {gap}, no test | ze's sendAndWaitACK (internal/plugins/tftpserver/handler.go:386-413) retransmits the DATA block on any non-matching ACK (ackBlock != block, handler.go:407) rather than silently ignoring a duplicate or stale ACK, so the RFC 1350 Sorcerer's Apprentice Syndrome fix is not implemented | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1350-2-1`](#rfc1350-2-1) File is sent in fixed length blocks of 512 bytes (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestTFTPReadExact512`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L471) | unit/verify | unproven | | positive | [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L390) | unit/verify | unproven | ### [`RFC1350-2-2`](#rfc1350-2-2) Each data packet must be acknowledged before the next packet can be sent (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTFTPConcurrentLimit`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L726) | unit/verify | unproven | | positive | [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L393) | unit/verify | unproven | ### [`RFC1350-6-1`](#rfc1350-6-1) Host sending the last DATA must retransmit it until acknowledged or timed out (Section 6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestTFTPRetransmitOnTimeout`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L757) | unit/verify | unproven | ### [`RFC1350-5-1`](#rfc1350-5-1) Host receiving netascii mode data must translate the data to its own format (Section 5) Audit verdict: not audited: no reader has judged these tests No test carries RFC1350-5-1, so no unit is bound to it. ### [`RFC1350-5-2`](#rfc1350-5-2) If a host receives an octet file and then returns it, the returned file must be identical to the original (Section 5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L398) | unit/verify | unproven | | positive | [`TestTFTPReadRequest`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L335) | unit/verify | unproven | ### [`RFC1350-4-1`](#rfc1350-4-1) Block numbers are consecutive and begin with one (Section 4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L396) | unit/verify | unproven | | positive | [`TestTFTPReadRequest`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L332) | unit/verify | unproven | ### [`RFC1350-4-2`](#rfc1350-4-2) Positive response to a write request is an ACK with block number zero (Section 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC1350-4-2, so no unit is bound to it. ### [`RFC1350-4-3`](#rfc1350-4-3) Each end of the connection chooses a TID for itself, used for the duration of that connection (Section 4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestListenTFTPLoopbackRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/socket_integration_linux_test.go#L144) | unit/verify | unproven | ### [`RFC1350-7-1`](#rfc1350-7-1) Timeouts must be used to detect errors (Section 7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestTFTPRetransmitOnTimeout`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L760) | unit/verify | unproven | ### [`RFC1350-2-3`](#rfc1350-2-3) Duplicate ACKs must be silently ignored; must not resend next DATA block (Sorcerer's Apprentice fix, Section 2 / RFC 1123 Section 4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC1350-2-3, so no unit is bound to it. ### [`RFC1350-5-3`](#rfc1350-5-3) When file size is exact multiple of 512, a final DATA packet with zero bytes of data must be sent (Section 5, Section 6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestTFTPReadEmptyFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L537) | unit/verify | unproven | | positive | [`TestTFTPReadExact512`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L474) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6, rfc1350 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc1350.txt | | Source fingerprint | 37aca32d5dfaf1a8 | | Record | rfc/extraction/rfc1350.json | | Mapped sentences | 5 | | Declined as scope | 5 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, Summary and Acknowlegements. The Summary restates the protocol in one paragraph and the Acknowlegements name the authors of the 1992 revision and the Sorcerer's Apprentice fix. Neither directs a TFTP speaker. | | `1` | Purpose | 0 | walked | Purpose. Says what TFTP is, that it runs over UDP, that it lacks directory listing and user authentication, and that it passes 8-bit bytes. It names the three transfer modes: netascii, octet and mail. The one directive-shaped sentence is 'The mail mode is obsolete and should not be implemented or used', which is advisory and states no gated obligation; the mail-mode obligation itself is site 5:3, excluded below. Ze accepts only octet mode (parseRRQ callers reject any other mode in handleRRQ, internal/plugins/tftpserver/handler.go). No gated requirement of rfc/short/rfc1350.md is read from this section. | | `2` | Overview of the Protocol | 1 | walked | Overview of the Protocol. Its one modal sentence is site 2:1, the lockstep obligation, mapped below. The rest is indicative and states two obligations rfc/short/rfc1350.md gates with no modal behind them. 'the connection is opened and the file is sent in fixed length blocks of 512 bytes' is where RFC1350-2-1 is read from, declared unsourced below. 'A data packet of less than 512 bytes signals termination of a transfer' is the framing rule RFC1350-5-3 turns into the exact-multiple terminator; the id names section 5, so it is declared there. The paragraph on error handling ('Most errors cause termination of the connection', 'TFTP recognizes only one error condition that does not cause termination, the source port of a received packet being incorrect') is indicative and its two obligations are stated normatively in sections 4 and 7. The duplicate-ACK exception this section's retransmission paragraph implies is stated in section 5 and RFC1350-2-3 is declared there. | | `3` | Relation to other Protocols | 1 | walked | Relation to other Protocols. Describes the header stack (local medium, Internet, Datagram, TFTP) and says TFTP specifies no value in the Internet header while the Datagram source and destination ports carry the TIDs. Its one modal sentence is site 3:1, excluded below as a description of the datagram layer's port range. No gated requirement of rfc/short/rfc1350.md is read from this section. | | `4` | Initial Connection Protocol | 0 | walked | Initial Connection Protocol. The section that carries the most gated obligations of the document and states every one of them in the indicative, so the modal scan sees none and derives zero sites here. 'Each data packet has associated with it a block number; block numbers are consecutive and begin with one' is RFC1350-4-1. 'Since the positive response to a write request is an acknowledgment packet, in this special case the block number will be zero' is RFC1350-4-2. 'In order to create a connection, each end of the connection chooses a TID for itself, to be used for the duration of that connection' is RFC1350-4-3. All three are declared unsourced below. The remaining normative material is advisory and ungated: the TIDs 'should be randomly chosen' (RFC1350-4-5) and a packet whose source TID does not match 'should be discarded' with an error packet sent to the wrong source 'while not disturbing the transfer' (RFC1350-4-4). The write-establishment example and the duplicated-request narrative that closes the section are worked examples and direct nobody. | | `5` | TFTP Packets | 5 | walked | TFTP Packets. The wire-format section and the largest site cluster: five of the document's ten modal sentences sit here. Two are mapped (5:1 netascii translation, 5:2 octet round-trip identity) and three are excluded (5:3 mail mode, 5:4 the DEC-20 special-mode example, 5:5 the caution on defining new modes). Two further gated rows are read from indicative sentences of this section and declared unsourced below. RFC1350-5-3, the zero-length final DATA block, is read from 'The data field is from zero to 512 bytes long. If it is 512 bytes long, the block is not the last block of data; if it is from zero to 511 bytes long, it signals the end of the transfer.' RFC1350-2-3, the Sorcerer's Apprentice fix, is read from 'All packets other than duplicate ACK's and those used for termination are acknowledged unless a timeout occurs [4].' That sentence is the only place RFC 1350 states the duplicate-ACK exception the 1992 revision was written to add, and it carries no modal; the id names section 2 because rfc/short/rfc1350.md cites the overview and RFC 1123 Section 4.2, so the row is homed here on the section the sentence is in rather than on the section its id spells. The opcode table, the four packet figures, the case-insensitive mode string, the block-number and data-length rules of the DATA figure, and the ERROR packet's human-readable message (advisory, RFC1350-5-4) complete the section. | | `6` | Normal Termination | 1 | walked | Normal Termination. Its one modal sentence is site 6:1, the last-DATA retransmission obligation, mapped below. The rest is the dallying paragraph, advisory and carried by RFC1350-6-2, plus the indicative statement that the end of a transfer is marked by a DATA packet of 0 to 511 bytes, which corroborates RFC1350-5-3 declared on section 5. | | `7` | Premature Termination | 1 | walked | Premature Termination. Two sentences. The ERROR packet is 'only a courtesy since it will not be retransmitted or acknowledged', which is indicative, and 'Timeouts must also be used to detect errors', which is site 7:1, mapped below. | | `I` | not stated | 1 | walked | Appendix, and everything the derivation folds under it: the header order figure, the four packet format figures, the read-establishment example, the error code table (values 0 to 7), the UDP header reproduced for convenience, the References, Security Considerations and the Author's Address. The figures and tables restate section 5's formats and section 4's establishment steps and direct nobody; the UDP header is reproduced with the RFC's own note that 'TFTP need not be implemented on top of the Internet User Datagram Protocol'. The one modal sentence is site I:1, in Security Considerations, excluded below as binding the administrator who grants rights to the server process. No gated requirement of rfc/short/rfc1350.md is read from this section. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `3:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A description of the datagram layer, not a directive to a TFTP speaker. The sentence derives a range from a property of the protocol underneath it: TIDs are handed to UDP to be used as ports, 'therefore they must be between 0 and 65,535'. The bound is the width of the UDP port field, so no TFTP implementation can violate it and none can be tested against it. Ze obtains its transfer TID from net.DialUDP in handleRRQ (internal/plugins/tftpserver/handler.go) and never chooses a port number itself. rfc/short/rfc1350.md declares no requirement for this sentence, and the TID obligation it does declare, RFC1350-4-3, is read from section 4 and declared unsourced there. | The transfer identifiers (TID's) used by TFTP are passed to the Datagram layer to be used as ports; therefore they must be between 0 and 65,535. | | `5:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the mail-mode role, which RFC 1350 retires in its own section 1: 'mail, netascii characters sent to a user rather than a file. (The mail mode is obsolete and should not be implemented or used.)' The sentence tells a host that offers mail mode that such a transfer begins with a WRQ and names a recipient in place of a file. Ze plays no mail-mode role at all: handleRRQ accepts only 'octet' and the server rejects WRQ outright in serve (internal/plugins/tftpserver/handler.go). rfc/short/rfc1350.md declares no requirement for mail mode. | Mail mode uses the name of a mail recipient in place of a file and must begin with a WRQ. | | `5:4` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A worked example, not a directive. The sentence sits inside the DEC-20 narrative the section opens with 'One might create a special mode for such a machine which read all the bits in a word, but in which the receiver stored the information in 8-bit format', and the RFC closes the narrative by saying 'No such machine or application specific modes have been specified in TFTP'. The 'must' describes what such a hypothetical mode would need in order to be useful, and binds no speaker of the protocol as specified. rfc/short/rfc1350.md declares no requirement for it. | When such a file is retrieved from the storage site, it must be restored to its original form to be useful, so the reverse mode must also be implemented. | | `5:5` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A caution attached to a permission, naming no observable behavior. The enclosing construction is 'It is also possible to define other modes for cooperating pairs of hosts, although this must be done with care', and the two sentences after it say 'There is no requirement that any other hosts implement these. There is no central authority that will define these modes or assign them names.' Care is not a wire behavior a test can assert or a decoder can violate, and the RFC itself says the sentence creates no requirement. rfc/short/rfc1350.md declares none for it. | It is also possible to define other modes for cooperating pairs of hosts, although this must be done with care. | | `I:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the system administrator who grants file system rights to the TFTP server process, a role RFC 1350 names in the sentence itself: 'care must be taken in the rights granted to a TFTP server process'. The obligation is a deployment decision made outside the protocol and outside ze, and the sentence after it describes the common deployment rather than requiring one: 'TFTP is often installed with controls such that only files that have public read access are available via TFTP and writing files via TFTP is disallowed.' Ze confines every transfer to the configured root through resolvePath (internal/plugins/tftpserver/handler.go) and serves reads only, which is the posture the sentence recommends to that administrator, but the rights on the files themselves are not ze's to grant. rfc/short/rfc1350.md declares no requirement for it. | Since TFTP includes no login or access control mechanisms, care must be taken in the rights granted to a TFTP server process so as not to violate the security of the server hosts file system. | ## Superseded No document obsoletes RFC 1350, so its obligations are stated where they were written. --- ### Page: RFC 1661 - The Point-to-Point Protocol (PPP) https://ze-software.net/quality/rfc-compliance/rfc1661/ # RFC 1661 - The Point-to-Point Protocol (PPP) Partial. Every requirement this repository extracted from RFC 1661, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 39.4% | 26 of 66 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 12.1% | 8 of 66 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 66 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 91 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 66 | of 91 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 8 | of 66 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 12.1% | 8 of 66 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 66 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 66 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 36.4% | 24 of 66 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 66 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 91 | | Gated MUST-level | 66 | | Not applicable, so out of scope | 8 | | Declared gaps | 24 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 91 | | Tagged units | 91 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc1661.md` | | Requirement shard | `rfc/requirements/rfc1661.md` | | RFC text | `rfc/full/rfc1661.txt` | ## Enrolment Enrolled: PPP LCP (RFC 1661): 27 MET (Protocol-field parity, LCP-first bring-up, per-family NCP configuration, auth requested in Link Establishment, no network phase before auth completes, Configure-Request replies, verbatim Configure-Ack echo, Nak value substitution and ordering, Configure-Reject contents and ordering, differing Nak option length, Terminate-Ack, Code-Reject, Echo-Reply only in Opened, silent Discard-Request, Magic-Number accept/zero-refuse/echo, two-octet Protocol field) + 6 single-polarity positive (LCP sent first, network-layer packets dropped before the NCP opens, no disconnect after Terminate-Ack, new Configure-Request accepted after RTR, one Auth-Protocol option, never compresses the Protocol field) + 24 gap (no send-side negotiated-MRU clamp, no LCP-phase gate on the frame dispatcher, no Protocol-Reject sender or RXJ+ suppression, constant Configure-Request Identifier and no last-sent-request record so Ack/Nak/Reject Identifier and option matching are unchecked, Code-Reject Identifier echoed, no single-octet Protocol parsing, no configurable Restart timer / Max-Terminate / Max-Configure / Max-Failure, zrc no-op) + 8 not-applicable (no link-quality protocol, no multi-instance LCP option, no Protocol-Reject or Discard-Request sender, no HDLC Address/Control framing, no Restart-timer backoff) ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc1661.md). **What the ledger says remains** Carries the L2TP and PPPoE Partial status. Twenty-four MUST gaps gated in [`rfc/short/rfc1661.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc1661.md): (1) no send-side clamp to a negotiated peer MRU, so frame and LCP Length bounds come from the fixed 1500-octet buffer ([`RFC1661-2-2`](#rfc1661-2-2), 5-1, 5.6-3); (2) the frame dispatcher has no LCP-phase gate and buffers early NCP frames instead of discarding out-of-phase packets ([`RFC1661-3.4-1`](#rfc1661-3.4-1), 3.5-4, 3.7-2); (3) no Protocol-Reject sender and no suppression of a packet type on RXJ+ ([`RFC1661-3.6-3`](#rfc1661-3.6-3), 4.3-3, 5.7-1, 5.7-2); (4) Configure-Request carries a constant Identifier and ze keeps no record of its last transmitted request, so Configure-Ack/Nak/Reject Identifier and option matching are unchecked and a rejected MRU or Magic-Number option reappears ([`RFC1661-5.1-3`](#rfc1661-5.1-3), 5.2-3, 5.2-4, 5.3-7, 5.4-3, 5.4-4, 5.4-5); (5) Code-Reject echoes the offending Identifier ([`RFC1661-5.6-2`](#rfc1661-5.6-2)); (6) a single-octet Protocol field is refused even with PFC negotiated ([`RFC1661-6.5-3`](#rfc1661-6.5-3)); (7) the Restart timer, Max-Terminate, Max-Configure and Max-Failure are not configurable and zrc is a no-op ([`RFC1661-4.6-1`](#rfc1661-4.6-1), 4.6-2, 4.6-3, 4.6-4, 4.4-2). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 26 | one part of the gated population | | Annotated instead of tested | 40 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **66** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (26):** [`RFC1661-2-1`](#rfc1661-2-1), [`RFC1661-6-2`](#rfc1661-6-2), [`RFC1661-3.1-2`](#rfc1661-3.1-2), [`RFC1661-3.5-1`](#rfc1661-3.5-1), [`RFC1661-3.5-3`](#rfc1661-3.5-3), [`RFC1661-3.6-1`](#rfc1661-3.6-1), [`RFC1661-4.3-1`](#rfc1661-4.3-1), [`RFC1661-5.1-1`](#rfc1661-5.1-1), [`RFC1661-5.1-2`](#rfc1661-5.1-2), [`RFC1661-5.2-1`](#rfc1661-5.2-1), [`RFC1661-5.2-2`](#rfc1661-5.2-2), [`RFC1661-5.3-1`](#rfc1661-5.3-1), [`RFC1661-5.3-2`](#rfc1661-5.3-2), [`RFC1661-5.3-3`](#rfc1661-5.3-3), [`RFC1661-5.3-5`](#rfc1661-5.3-5), [`RFC1661-5.3-6`](#rfc1661-5.3-6), [`RFC1661-5.3-8`](#rfc1661-5.3-8), [`RFC1661-5.4-1`](#rfc1661-5.4-1), [`RFC1661-5.4-2`](#rfc1661-5.4-2), [`RFC1661-5.5-1`](#rfc1661-5.5-1), [`RFC1661-5.6-1`](#rfc1661-5.6-1), [`RFC1661-5.8-1`](#rfc1661-5.8-1), [`RFC1661-5.8-2`](#rfc1661-5.8-2), [`RFC1661-6.4-1`](#rfc1661-6.4-1), [`RFC1661-6.4-2`](#rfc1661-6.4-2), [`RFC1661-6.4-3`](#rfc1661-6.4-3) **Annotated instead of tested (40):** [`RFC1661-2-2`](#rfc1661-2-2), [`RFC1661-5-1`](#rfc1661-5-1), [`RFC1661-3.1-1`](#rfc1661-3.1-1), [`RFC1661-3.4-1`](#rfc1661-3.4-1), [`RFC1661-3.5-2`](#rfc1661-3.5-2), [`RFC1661-3.5-4`](#rfc1661-3.5-4), [`RFC1661-3.6-2`](#rfc1661-3.6-2), [`RFC1661-3.6-3`](#rfc1661-3.6-3), [`RFC1661-3.7-1`](#rfc1661-3.7-1), [`RFC1661-3.7-2`](#rfc1661-3.7-2), [`RFC1661-4.3-2`](#rfc1661-4.3-2), [`RFC1661-4.3-3`](#rfc1661-4.3-3), [`RFC1661-5.1-3`](#rfc1661-5.1-3), [`RFC1661-5.2-3`](#rfc1661-5.2-3), [`RFC1661-5.2-4`](#rfc1661-5.2-4), [`RFC1661-5.3-4`](#rfc1661-5.3-4), [`RFC1661-5.3-7`](#rfc1661-5.3-7), [`RFC1661-5.4-3`](#rfc1661-5.4-3), [`RFC1661-5.4-4`](#rfc1661-5.4-4), [`RFC1661-5.4-5`](#rfc1661-5.4-5), [`RFC1661-5.6-2`](#rfc1661-5.6-2), [`RFC1661-5.6-3`](#rfc1661-5.6-3), [`RFC1661-5.7-1`](#rfc1661-5.7-1), [`RFC1661-5.7-2`](#rfc1661-5.7-2), [`RFC1661-5.7-3`](#rfc1661-5.7-3), [`RFC1661-5.7-4`](#rfc1661-5.7-4), [`RFC1661-5.9-1`](#rfc1661-5.9-1), [`RFC1661-5.9-2`](#rfc1661-5.9-2), [`RFC1661-6.2-1`](#rfc1661-6.2-1), [`RFC1661-6.5-1`](#rfc1661-6.5-1), [`RFC1661-6.5-2`](#rfc1661-6.5-2), [`RFC1661-6.5-3`](#rfc1661-6.5-3), [`RFC1661-6.6-1`](#rfc1661-6.6-1), [`RFC1661-6.6-2`](#rfc1661-6.6-2), [`RFC1661-4.6-1`](#rfc1661-4.6-1), [`RFC1661-4.6-2`](#rfc1661-4.6-2), [`RFC1661-4.6-3`](#rfc1661-4.6-3), [`RFC1661-4.6-4`](#rfc1661-4.6-4), [`RFC1661-4.4-1`](#rfc1661-4.4-1), [`RFC1661-4.4-2`](#rfc1661-4.4-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1661-2-1` | Protocol field: LSB of least-significant octet must equal 1; LSB of most-significant octet must equal 0; frames violating these rules must be treated as unrecognized Protocol (Section 2) | MUST | 2 | **positive:** `unit/verify` [`TestRFC1661CompliantProtocolRecognized`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L212). **negative:** `unit/verify` [`TestRFC1661NonCompliantProtocolTreatedUnrecognized`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L187) | | `RFC1661-2-2` | Information field plus Padding must fit within peer's MRU (default 1500) (Section 2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze sizes every frame it emits from the fixed 1500-octet frameBufPool buffer and never consults the peer's negotiated MRU -- getFrameBuf (internal/component/l2tp/ppp/session_run.go:59-65) hands out MaxFrameLen bytes and sendCodeReject (internal/component/l2tp/ppp/session_run.go) truncates only against that buffer -- so a Code-Reject echoing a large packet exceeds a peer MRU negotiated below 1500. Disclosed in docs/features/rfc-status.md | | `RFC1661-5-1` | LCP Length must not exceed the MRU of the link (Section 5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the LCP Length ze writes is backfilled from what fits the fixed 1500-octet buffer (WriteLCPPacket, internal/component/l2tp/ppp/lcp.go:91-99, fed by getFrameBuf at internal/component/l2tp/ppp/session_run.go:59-65); no send path reads s.negotiatedMRU, so the Length is bounded by the default MRU rather than by a smaller MRU the peer negotiated. Disclosed in docs/features/rfc-status.md | | `RFC1661-6-1` | A negotiable Configuration Option received in a Configure-Request with an invalid or unrecognized Length should draw a Configure-Nak carrying the desired Configuration Option with an appropriate Length and Data (Section 6) | SHOULD | 6 | **positive:** `unit/verify` [`TestRFC1661ClientInvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L148). **positive:** `unit/verify` [`TestRFC1661InvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L154). **positive:** `unit/verify` [`TestRFC1661LCPInvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L318). **positive:** `unit/verify` [`TestRFC1661LCPWrongLengthMagicIsNakedNotRejected`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L404). **positive:** `unit/verify` [`TestRFC1661ReplyListsEachOptionTypeOnce`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L724). **negative:** `unit/verify` [`TestRFC1661ClientInvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L154). **negative:** `unit/verify` [`TestRFC1661InvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L159). **negative:** `unit/verify` [`TestRFC1661LCPInvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L325). **negative:** `unit/verify` [`TestRFC1661LCPUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L465). **negative:** `unit/verify` [`TestRFC1661LCPWrongLengthMagicIsNakedNotRejected`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L408). **negative:** `unit/verify` [`TestRFC1661ReplyListsEachOptionTypeOnce`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L728) | | `RFC1661-6-2` | A Configuration Option whose Data is indicated by its Length to extend beyond the end of the Information field must cause the entire packet to be silently discarded without affecting the automaton (Section 6) | MUST | 6 | **positive:** `unit/verify` [`TestRFC1661ClientRequestPastEndSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L104). **positive:** `unit/verify` [`TestRFC1661LCPReplyWithOptionsPastEndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L584). **positive:** `unit/verify` [`TestRFC1661LCPTruncatedOptionSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L236). **positive:** `unit/verify` [`TestRFC1661NCPReplyWithOptionsPastEndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L655). **positive:** `unit/verify` [`TestRFC1661TruncatedOptionSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L75). **negative:** `unit/verify` [`TestRFC1661ClientRequestPastEndSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L111). **negative:** `unit/verify` [`TestRFC1661LCPReplyWithOptionsPastEndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L590). **negative:** `unit/verify` [`TestRFC1661LCPTruncatedOptionSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L243). **negative:** `unit/verify` [`TestRFC1661NCPReplyWithOptionsPastEndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L660). **negative:** `unit/verify` [`TestRFC1661TruncatedOptionSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L81) | | `RFC1661-3.1-1` | Each end must first send LCP packets to configure and test the data link (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRFC1661LCPPacketsSentFirst`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L280). **negative:** no negative test. **{single-polarity}:** run (internal/component/l2tp/ppp/session_run.go:182-197) drives the synthetic Initial->Closed->ReqSent sequence whose scr action puts an LCP Configure-Request on the wire before any other traffic, and the sole branch that skips it is the RFC 2661 Section 18 proxy-LCP path where the LAC has already run LCP, so there is no case in which ze opens a link with LCP packets unsent | | `RFC1661-3.1-2` | PPP must send NCP packets to choose and configure network-layer protocols (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRFC1661NCPConfiguresEachFamilySeparately`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L308). **negative:** `unit/verify` [`TestRFC1661NoNCPPacketsWhenNoNetworkProtocol`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L347) | | `RFC1661-3.4-1` | Non-LCP packets received during Link Establishment phase must be silently discarded (Section 3.4) | MUST | 3.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** handleFrame (internal/component/l2tp/ppp/session_run.go:628-684) dispatches purely on the PPP Protocol field with no LCP-phase gate; an IPCP frame arriving during Link Establishment is copied into earlyNCPFrames (internal/component/l2tp/ppp/session_run.go:645-651) and replayed by runNCPPhase (internal/component/l2tp/ppp/ncp.go) instead of being silently discarded. Disclosed in docs/features/rfc-status.md | | `RFC1661-3.5-1` | If peer authentication is desired, must request Authentication-Protocol during Link Establishment (Section 3.5) | MUST | 3.5 | **positive:** `unit/verify` [`TestRFC1661AuthProtocolRequestedDuringEstablishment`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L409). **negative:** `unit/verify` [`TestRFC1661NoAuthProtocolWhenNotDesired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L440) | | `RFC1661-3.5-2` | Exchange of link quality determination packets must not delay authentication indefinitely (Section 3.5) | MUST NOT | 3.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze implements no link-quality determination protocol. The LCP Quality-Protocol option (type 4) has no constant in internal/component/l2tp/ppp/lcp_options.go:14-21 and negotiatePeerOption (internal/component/l2tp/ppp/lcp_options.go) Configure-Rejects it as an unknown type; a grep for LQR, 0xC025 and Quality-Protocol across internal/ matches only that lcp_options.go comment naming type 4 as unimplemented | | `RFC1661-3.5-3` | Advancement from Authentication to Network-Layer Protocol phase must not occur until authentication completes (Section 3.5) | MUST NOT | 3.5 | **positive:** `unit/verify` [`TestRFC1661NoNetworkPhaseUntilAuthCompletes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L467). **negative:** `unit/verify` [`TestRFC1661NetworkPhaseRunsAfterAuthCompletes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L500) | | `RFC1661-3.5-4` | All other packets during Authentication phase must be silently discarded (Section 3.5) | MUST | 3.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** during the authentication wait waitCHAPLike (internal/component/l2tp/ppp/auth.go:288-306) hands every non-auth frame to handleFrame, which has no phase gate (internal/component/l2tp/ppp/session_run.go:628-684), so an NCP frame received in the Authentication phase is buffered into earlyNCPFrames and replayed rather than silently discarded. Disclosed in docs/features/rfc-status.md | | `RFC1661-3.6-1` | Each network-layer protocol must be separately configured by appropriate NCP (Section 3.6) | MUST | 3.6 | **positive:** `unit/verify` [`TestRFC1661NCPConfiguresEachFamilySeparately`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L312). **negative:** `unit/verify` [`TestRFC1661NCPStatesAreIndependent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L364) | | `RFC1661-3.6-2` | Network-layer packets received when corresponding NCP is not Opened must be silently discarded (Section 3.6) | MUST | 3.6 | **positive:** `unit/verify` [`TestRFC1661NetworkLayerPacketDiscardedBeforeNCPOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L382). **negative:** no negative test. **{single-polarity}:** handleFrame (internal/component/l2tp/ppp/session_run.go:637-683) dispatches only the three control protocols and drops IPv4 (0x0021) and IPv6 (0x0057) frames in every state, so no NCP state exists in which a network-layer packet is processed in userspace and there is no accepting counterpart to assert | | `RFC1661-3.6-3` | Unsupported Protocol in Opened state must be returned in Protocol-Reject (Section 3.6) | MUST | 3.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze has no Protocol-Reject sender. handleFrame (internal/component/l2tp/ppp/session_run.go:681-683) logs an unsupported PPP Protocol at debug level and drops the frame; LCPProtocolReject (internal/component/l2tp/ppp/lcp.go:25) appears only in LCPCodeName and in the receive-side codeToEvent mapping. Disclosed in docs/features/rfc-status.md | | `RFC1661-3.7-1` | Receiver of Terminate-Request must not disconnect until at least one Restart time after sending Terminate-Ack (Section 3.7) | MUST | 3.7 | **positive:** `unit/verify` [`TestRFC1661TerminateAckSentAndLinkHeld`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L532). **negative:** no negative test. **{single-polarity}:** the Opened+RTR edge (internal/component/l2tp/ppp/ppp_fsm.go:393-394) lands in Stopping and handleLCPPacket emits EventSessionDown only for Closed or Stopped (internal/component/l2tp/ppp/session_run.go), so after sending a Terminate-Ack ze holds the link; there is no early-disconnect branch to assert | | `RFC1661-3.7-2` | Non-LCP packets during Link Termination phase must be silently discarded (Section 3.7) | MUST | 3.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** handleFrame (internal/component/l2tp/ppp/session_run.go:637-680) carries no LCP-state guard, so an IPCP or IPv6CP packet arriving while LCP sits in Closing or Stopping is still passed to handleNCPPacket (internal/component/l2tp/ppp/ncp.go) rather than silently discarded. Disclosed in docs/features/rfc-status.md | | `RFC1661-4.3-1` | Implementation must be prepared to immediately renegotiate Configuration Options on RCR+/RCR- in Opened (Section 4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestRFC1661RenegotiateOnConfigureRequestInOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L587). **negative:** `unit/verify` [`TestRFC1661EchoDoesNotRenegotiate`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L620) | | `RFC1661-4.3-2` | Implementation must be prepared to receive new Configure-Request without admin intervention after RTR (Section 4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestRFC1661NewConfigureRequestAcceptedAfterTerminateRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L640). **negative:** no negative test. **{single-polarity}:** every post-RTR state in LCPDoTransition accepts a fresh RCR+ (internal/component/l2tp/ppp/ppp_fsm.go:295-296, :327-328, :359-360) and no code path consults an administrative flag before doing so, so there is no refusing counterpart to assert | | `RFC1661-4.3-3` | Implementation must stop sending the offending packet type on RXJ+ (Section 4.3) | MUST | 4.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** codeToEvent (internal/component/l2tp/ppp/session_run.go:703-704) maps a received Code-Reject or Protocol-Reject to RXJ+, and every RXJ+ edge in LCPDoTransition (internal/component/l2tp/ppp/ppp_fsm.go:227, :305, :337, :369, :399) carries no action; ze holds no per-packet-type suppression state, so it keeps sending the offending packet type. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.1-1` | An implementation wishing to open a connection must transmit a Configure-Request (Section 5.1) | MUST | 5.1 | **positive:** `unit/verify` [`TestRFC1661OpenTransmitsConfigureRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L666). **negative:** `unit/verify` [`TestRFC1661UpWithoutOpenSendsNoConfigureRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L697) | | `RFC1661-5.1-2` | Upon reception of Configure-Request, an appropriate reply must be transmitted (Section 5.1) | MUST | 5.1 | **positive:** `unit/verify` [`TestRFC1661ConfigureAckEchoesOptionsVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L715). **negative:** `unit/verify` [`TestRFC1661ConfigureRejectForUnrecognizedOption`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L779) | | `RFC1661-5.1-3` | Identifier field must be changed whenever Options field changes or a valid reply is received (Section 5.1) | MUST | 5.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** sendConfigureRequest (internal/component/l2tp/ppp/session_run.go) writes a constant Identifier of 1 into every LCP Configure-Request, so the Identifier changes neither when the option list changes nor after a valid reply arrives. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.2-1` | If all options recognizable and acceptable, must transmit Configure-Ack (Section 5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestRFC1661ConfigureAckEchoesOptionsVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L719). **negative:** `unit/verify` [`TestRFC1661ConfigureRejectForUnrecognizedOption`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L783) | | `RFC1661-5.2-2` | Acknowledged Configuration Options must not be reordered or modified (Section 5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestRFC1661ConfigureAckEchoesOptionsVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L722). **negative:** `unit/verify` [`TestRFC1661ConfigureAckDoesNotReorderOptions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L748) | | `RFC1661-5.2-3` | On reception of Configure-Ack, Identifier must match last transmitted Configure-Request (Section 5.2) | MUST | 5.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** handleLCPPacket (internal/component/l2tp/ppp/session_run.go) feeds a Configure-Ack straight through codeToEvent (internal/component/l2tp/ppp/session_run.go:695-696) as RCA without comparing pkt.Identifier against the last transmitted Configure-Request; ze stores no last-sent Identifier. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.2-4` | Configure-Ack options must exactly match last transmitted Configure-Request (Section 5.2) | MUST | 5.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze keeps no copy of the options it last sent -- sendConfigureRequest rebuilds them from s.maxMRU, s.magic and s.configuredAuthMethod on each call (internal/component/l2tp/ppp/session_run.go) -- so handleLCPPacket accepts a Configure-Ack without comparing its option list to the last transmitted Configure-Request. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.3-1` | If all options recognized but some values unacceptable, must transmit Configure-Nak (Section 5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestRFC1661ConfigureNakSuggestsAcceptableValue`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L869). **negative:** `unit/verify` [`TestRFC1661NoNakForAcceptableValue`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L904) | | `RFC1661-5.3-2` | Boolean options (no value) must use Configure-Reject instead of Configure-Nak (Section 5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestRFC1661BooleanOptionsUseRejectNotNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L934). **negative:** `unit/verify` [`TestRFC1661ValuedOptionUsesNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L956) | | `RFC1661-5.3-3` | Single-instance option in Nak must be modified to acceptable value (Section 5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestRFC1661ConfigureNakSuggestsAcceptableValue`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L873). **negative:** `unit/verify` [`TestRFC1661NoNakForAcceptableValue`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L907) | | `RFC1661-5.3-4` | Multi-instance option in Nak must list all acceptable values (Section 5.3) | MUST | 5.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the requirement is conditional on an option type that "can be listed more than once with different values", and RFC 1661 Section 6 says of its own set: "(None of the Configuration Options in this specification can be listed more than once.)" Ze implements types 1, 2, 3, 5, 7 and 8 (internal/component/l2tp/ppp/lcp_options.go:14-21), none of them multi-instance, so no input gives ze a list of acceptable values to send. The earlier reason here read that vacuity off NegotiatePeerOptions (internal/component/l2tp/ppp/lcp_options.go), which emits one Nak entry per received OPTION rather than per Type; keeping the reply to one entry per Type is appendUnlessListed (internal/component/l2tp/ppp/session_run.go), not this row | | `RFC1661-5.3-5` | Nak option value fields must indicate values acceptable to the sender (Section 5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestRFC1661NakValueIsAcceptable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L973). **negative:** `unit/verify` [`TestRFC1661RejectedValueStaysUnacceptable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L995) | | `RFC1661-5.3-6` | Options from Configure-Request must not be reordered in Configure-Nak (Section 5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestRFC1661NakPreservesRequestOrder`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1012). **negative:** `unit/verify` [`TestRFC1661NakOrderFollowsRequestNotAFixedOrder`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1031) | | `RFC1661-5.3-7` | On reception of Configure-Nak, Identifier must match last transmitted Configure-Request (Section 5.3) | MUST | 5.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** a Configure-Nak reaches codeToEvent as RCN (internal/component/l2tp/ppp/session_run.go:697-698) with no Identifier comparison, and adjustAuthOnNakOrReject (internal/component/l2tp/ppp/auth.go:38-46) parses the option list without checking pkt.Identifier against the last transmitted Configure-Request. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.3-8` | Implementation must handle option length different from original Configure-Request (Section 5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestRFC1661NakHandlesDifferentOptionLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1049). **negative:** `unit/verify` [`TestRFC1661NakTooShortOptionNotDecoded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1071) | | `RFC1661-5.4-1` | If options not recognized or not acceptable for negotiation, must transmit Configure-Reject (Section 5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestRFC1661ClientUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L197). **positive:** `unit/verify` [`TestRFC1661ConfigureRejectForUnrecognizedOption`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L786). **positive:** `unit/verify` [`TestRFC1661LCPUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L459). **positive:** `unit/verify` [`TestRFC1661NCPUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L526). **negative:** `unit/verify` [`TestRFC1661ClientAcceptsServerAuthProtocol`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L354). **negative:** `unit/verify` [`TestRFC1661ClientUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L202). **negative:** `unit/verify` [`TestRFC1661NCPUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L531). **negative:** `unit/verify` [`TestRFC1661NoConfigureRejectWhenAllOptionsRecognized`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L817) | | `RFC1661-5.4-2` | Configure-Reject options must not be reordered or modified (Section 5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestRFC1661ClientRejectEchoesTheRefusedOptionUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L233). **positive:** `unit/verify` [`TestRFC1661ConfigureRejectForUnrecognizedOption`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L789). **positive:** `unit/verify` [`TestRFC1661LCPRejectEchoesTheRefusedOptionUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L808). **negative:** `unit/verify` [`TestRFC1661ClientRejectEchoesTheRefusedOptionUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L245). **negative:** `unit/verify` [`TestRFC1661ConfigureRejectDoesNotReorderOptions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L835). **negative:** `unit/verify` [`TestRFC1661LCPRejectEchoesTheRefusedOptionUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L819) | | `RFC1661-5.4-3` | On reception of Configure-Reject, Identifier must match last transmitted Configure-Request (Section 5.4) | MUST | 5.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** a Configure-Reject reaches codeToEvent as RCN (internal/component/l2tp/ppp/session_run.go:697-698) with no Identifier comparison, and adjustAuthOnNakOrReject (internal/component/l2tp/ppp/auth.go:38-56) acts on it without checking pkt.Identifier against the last transmitted Configure-Request. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.4-4` | Configure-Reject options must be a subset of last transmitted Configure-Request (Section 5.4, Errata 543) | MUST | 5.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze retains no record of the options in its last transmitted Configure-Request (sendConfigureRequest rebuilds them per call, internal/component/l2tp/ppp/session_run.go), so a Configure-Reject is acted on without verifying its options are a subset of that request. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.4-5` | Next Configure-Request must not include any rejected options (Section 5.4) | MUST | 5.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** only the Authentication-Protocol option is absorbed on a peer Configure-Reject -- adjustAuthOnNakOrReject clears configuredAuthMethod (internal/component/l2tp/ppp/auth.go:52-56) -- while sendConfigureRequest (internal/component/l2tp/ppp/session_run.go) rebuilds the MRU and Magic-Number options from s.maxMRU and s.magic on every call, so a rejected MRU or Magic-Number option reappears in the next Configure-Request. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.5-1` | Upon reception of Terminate-Request, a Terminate-Ack must be transmitted (Section 5.5) | MUST | 5.5 | **positive:** `unit/verify` [`TestRFC1661TerminateAckSentAndLinkHeld`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L537). **negative:** `unit/verify` [`TestRFC1661NoTerminateAckForTerminateAck`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L566) | | `RFC1661-5.6-1` | Unknown code must be reported via Code-Reject (Section 5.6) | MUST | 5.6 | **positive:** `unit/verify` [`TestRFC1661CodeRejectForUnknownCode`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1093). **negative:** `unit/verify` [`TestRFC1661NoCodeRejectForKnownCode`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1125) | | `RFC1661-5.6-2` | Identifier must be changed for each Code-Reject sent (Section 5.6) | MUST | 5.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** sendCodeReject (internal/component/l2tp/ppp/session_run.go) reuses the offending packet's Identifier for the Code-Reject instead of allocating a fresh one, so the Identifier does not change per Code-Reject sent. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.6-3` | Rejected-Packet in Code-Reject must be truncated to peer's MRU (Section 5.6) | MUST | 5.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** sendCodeReject (internal/component/l2tp/ppp/session_run.go) truncates the Rejected-Packet only against the fixed 1500-octet buffer from getFrameBuf (internal/component/l2tp/ppp/session_run.go:59-65) and never reads s.negotiatedMRU, so the copy is not bounded by a peer MRU negotiated below 1500. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.7-1` | In Opened state, unsupported Protocol must be reported via Protocol-Reject (Section 5.7) | MUST | 5.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze has no Protocol-Reject sender. In the Opened state an unsupported PPP Protocol reaches the drop path in handleFrame (internal/component/l2tp/ppp/session_run.go:681-683); no code writes an LCPProtocolReject packet (internal/component/l2tp/ppp/lcp.go:25 is referenced only by LCPCodeName and codeToEvent). Disclosed in docs/features/rfc-status.md | | `RFC1661-5.7-2` | On reception of Protocol-Reject, must stop sending packets of indicated protocol (Section 5.7) | MUST | 5.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** codeToEvent (internal/component/l2tp/ppp/session_run.go:703-704) turns a received Protocol-Reject into RXJ+, whose FSM edge in Opened carries no action (internal/component/l2tp/ppp/ppp_fsm.go:399-400); ze records no rejected-protocol set, so it keeps sending packets of the indicated protocol. Disclosed in docs/features/rfc-status.md | | `RFC1661-5.7-3` | Identifier must be changed for each Protocol-Reject sent (Section 5.7) | MUST | 5.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never transmits a Protocol-Reject. A grep for LCPProtocolReject across internal/ matches only the constant (internal/component/l2tp/ppp/lcp.go:25), its name in LCPCodeName (internal/component/l2tp/ppp/lcp.go:119) and the receive-side codeToEvent mapping (internal/component/l2tp/ppp/session_run.go:703); no send path exists, so no Identifier is allocated for one | | `RFC1661-5.7-4` | Rejected-Information in Protocol-Reject must be truncated to peer's MRU (Section 5.7) | MUST | 5.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never transmits a Protocol-Reject, so no Rejected-Information field is built. A grep for LCPProtocolReject across internal/ matches only internal/component/l2tp/ppp/lcp.go:25, lcp.go:119 and the receive-side session_run.go:703 | | `RFC1661-5.8-1` | On Echo-Request in Opened state, Echo-Reply must be transmitted (Section 5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestRFC1661EchoReplyInOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1146). **negative:** `unit/verify` [`TestRFC1661NoEchoOutsideOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1187) | | `RFC1661-5.8-2` | Echo-Request and Echo-Reply must only be sent in Opened state (Section 5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestRFC1661NoEchoOutsideOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1191). **negative:** `unit/verify` [`TestRFC1661EchoReplyInOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1149) | | `RFC1661-5.9-1` | Discard-Request must only be sent in Opened state (Section 5.9) | MUST | 5.9 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never transmits a Discard-Request. A grep for LCPDiscardRequest across internal/ matches only the constant (internal/component/l2tp/ppp/lcp.go:28), LCPCodeName (internal/component/l2tp/ppp/lcp.go:125) and the receive-side codeToEvent mapping (internal/component/l2tp/ppp/session_run.go:705) | | `RFC1661-5.9-2` | Receiver must silently discard any Discard-Request (Section 5.9) | MUST | 5.9 | **positive:** `unit/verify` [`TestRFC1661DiscardRequestSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1213). **negative:** no negative test. **{single-polarity}:** the obligation covers ANY Discard-Request, so no conforming Discard-Request exists that must instead draw a reply, and no input can make the required silence wrong. The discrimination against a receiver that answers nothing at all is carried by TestRFC1661EchoReplyInOpened, which is tagged for the Echo requirements it actually drives (RFC1661-5.8-1, RFC1661-5.8-2), not for this one | | `RFC1661-6.2-1` | Multiple Authentication-Protocol options must not be included in a single Configure-Request (Section 6.2) | MUST NOT | 6.2 | **positive:** `unit/verify` [`TestRFC1661SingleAuthProtocolOptionInRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1245). **negative:** no negative test. **{single-polarity}:** LCPOptions.AuthProto is a single uint16 (internal/component/l2tp/ppp/lcp_options.go) and BuildLocalConfigRequest appends the option once (internal/component/l2tp/ppp/lcp_options.go), so no input produces two Authentication-Protocol options and there is no violating case to assert | | `RFC1661-6.4-1` | If implementation transmits Configure-Request with Magic-Number, must not Configure-Reject peer's Magic-Number option (Section 6.4) | MUST NOT | 6.4 | **positive:** `unit/verify` [`TestRFC1661ZeroMagicNumberRefused`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1323). **negative:** `unit/verify` [`TestRFC1661UnknownOptionRejectedWhileMagicIsNot`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1295) | | `RFC1661-6.4-2` | If Magic-Number negotiated, Echo/Discard packets must carry negotiated Magic-Number (Section 6.4) | MUST | 6.4 | **positive:** `unit/verify` [`TestRFC1661EchoReplyInOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1159). **negative:** `unit/verify` [`TestRFC1661EchoReplyDoesNotMirrorPeerMagic`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1362) | | `RFC1661-6.4-3` | Magic-Number of zero must always be Nak'd if not Rejected (Section 6.4) | MUST | 6.4 | **positive:** `unit/verify` [`TestRFC1661ClientNaksZeroMagicWithAValueOfItsOwn`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L402). **positive:** `unit/verify` [`TestRFC1661ZeroMagicNumberRefused`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1315). **negative:** `unit/verify` [`TestRFC1661ClientNaksZeroMagicWithAValueOfItsOwn`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L408). **negative:** `unit/verify` [`TestRFC1661PeerMagicNumberAcked`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1276) | | `RFC1661-6.5-1` | All implementations must transmit packets with two-octet PPP Protocol fields by default (Section 6.5) | MUST | 6.5 | **positive:** `unit/verify` [`TestRFC1661TwoOctetProtocolField`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L232). **negative:** no negative test. **{single-polarity}:** this is a transmit obligation and WriteFrame has no compressed branch at all -- WriteFrame always writes the Protocol with binary.BigEndian.PutUint16 (internal/component/l2tp/ppp/frame.go:81-85), so no configuration or negotiated option produces a single-octet transmit to assert against. The positive test drives both PFC settings to show the option cannot change the encoder; the receive-side refusal of a one-octet Protocol is the separate RFC1661-6.5-3 | | `RFC1661-6.5-2` | Compressed Protocol fields must not be transmitted unless PFC option negotiated (Section 6.5) | MUST NOT | 6.5 | **positive:** `unit/verify` [`TestRFC1661TwoOctetProtocolField`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L236). **negative:** no negative test. **{single-polarity}:** WriteFrame (internal/component/l2tp/ppp/frame.go:81-85) has no compressed-Protocol branch, so ze transmits an uncompressed Protocol field whether or not the option is negotiated and there is no compressed-transmit case to contrast | | `RFC1661-6.5-3` | When PFC negotiated, must accept both single-octet and double-octet Protocol fields (Section 6.5) | MUST | 6.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ParseFrame (internal/component/l2tp/ppp/frame.go:59-67) accepts only the two-octet Protocol form, a choice documented at internal/component/l2tp/ppp/frame.go:44-58, so a single-octet Protocol field is rejected as a malformed frame even once Protocol-Field-Compression is negotiated. Disclosed in docs/features/rfc-status.md | | `RFC1661-6.6-1` | All implementations must transmit frames with Address and Control fields appropriate to link framing (Section 6.6) | MUST | 6.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze performs no HDLC-like framing. It writes protocol-plus-payload frames to a /dev/ppp channel fd (WriteFrame, internal/component/l2tp/ppp/frame.go:81-85) and the kernel PPP driver supplies the Address and Control octets; a grep for 0xFF03 and HDLC across internal/ matches only two comments in internal/component/l2tp/ppp/lcp_options.go, one on desiredLCPOption and one on negotiatePeerOption, each recording that the kernel does the framing | | `RFC1661-6.6-2` | Address and Control fields must not be compressed when sending LCP packets (Section 6.6) | MUST NOT | 6.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze emits no Address or Control field on any packet, LCP included: WriteFrame (internal/component/l2tp/ppp/frame.go:81-85) writes only the Protocol field and payload, and a grep for 0xFF03 and HDLC across internal/ matches only two comments in internal/component/l2tp/ppp/lcp_options.go, one on desiredLCPOption and one on negotiatePeerOption, each recording that the kernel supplies the framing | | `RFC1661-4.6-1` | Restart timer must be configurable (Section 4.6) | MUST | 4.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the Configure-Request retransmission timer is a fixed 3-second ticker created inline in run (internal/component/l2tp/ppp/session_run.go:217); StartSession (internal/component/l2tp/ppp/start_session.go) carries no restart-timer field and no YANG leaf sets one. Disclosed in docs/features/rfc-status.md | | `RFC1661-4.6-2` | Max-Terminate must be configurable (Section 4.6) | MUST | 4.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze holds no Restart counter -- performAction treats LCPActIRC and LCPActZRC as no-ops (internal/component/l2tp/ppp/session_run.go) -- so there is no Max-Terminate value to configure; sendTerminateRequest (internal/component/l2tp/ppp/session_run.go) fires only from FSM edges. Disclosed in docs/features/rfc-status.md | | `RFC1661-4.6-3` | Max-Configure must be configurable (Section 4.6) | MUST | 4.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze bounds LCP negotiation with the fixed 30-second defaultNegoTimeout (internal/component/l2tp/ppp/session_run.go, the constant and its use in run) rather than a Configure-Request transmission count, and the Restart counter LCPActIRC would initialize is a no-op (internal/component/l2tp/ppp/session_run.go, performAction), so no Max-Configure value exists to configure. Disclosed in docs/features/rfc-status.md | | `RFC1661-4.6-4` | Max-Failure must be configurable (Section 4.6) | MUST | 4.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze counts no Configure-Naks sent; sendConfigureNakOrReject (internal/component/l2tp/ppp/session_run.go) picks Nak or Reject from the LCPNakOrReject verdict over NegotiatePeerOptions output on each request, so there is no Max-Failure value to configure and no threshold that converts a Nak into a Reject. Disclosed in docs/features/rfc-status.md | | `RFC1661-4.4-1` | On irc action, timeout period must be reset to initial value when backoff is used (Section 4.4) | MUST | 4.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze applies no Restart timer backoff. The retransmission timer is a fixed-interval ticker, time.NewTicker(3 * time.Second) at internal/component/l2tp/ppp/session_run.go:217, with no growing timeout value, so the condition "when Restart timer backoff is used" never holds | | `RFC1661-4.4-2` | On zrc action, timeout period must be set to appropriate value (Section 4.4) | MUST | 4.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** performAction treats LCPActZRC as a no-op (internal/component/l2tp/ppp/session_run.go), so the Opened+RTR edge that prescribes zrc (internal/component/l2tp/ppp/ppp_fsm.go:393-394) neither zeroes a Restart counter nor sets a timeout period. Disclosed in docs/features/rfc-status.md | | `RFC1661-4.6-5` | Restart timer should default to 3 seconds (Section 4.6) | SHOULD | 4.6 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-4.6-6` | Max-Terminate should default to 2 transmissions (Section 4.6) | SHOULD | 4.6 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-4.6-7` | Max-Configure should default to 10 transmissions (Section 4.6) | SHOULD | 4.6 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-4.6-8` | Max-Failure should default to 5 transmissions (Section 4.6) | SHOULD | 4.6 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-1.2-1` | Provide capability of logging silently discarded packets and record in statistics counter (Section 1.2) | SHOULD | 1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-3.5-5` | Authentication should take place as soon as possible after link establishment (Section 3.5) | SHOULD | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-3.5-6` | If authentication fails, proceed to Link Termination phase (Section 3.5) | SHOULD | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-3.5-7` | Should not fail authentication simply due to timeout or lack of response (Section 3.5) | SHOULD NOT | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-3.6-4` | Avoid fixed timeouts when waiting for peers to configure NCP (Section 3.6) | SHOULD | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-3.7-3` | Signal physical-layer to disconnect on termination, especially on auth failure (Section 3.7) | SHOULD | 3.7 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-3.7-4` | Sender of Terminate-Request should disconnect after Terminate-Ack or Restart counter expires (Section 3.7) | SHOULD | 3.7 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-3.7-5` | Receiver of Terminate-Request should wait for peer to disconnect (Section 3.7) | SHOULD | 3.7 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-5.1-4` | Configuration Options should not be included with default values in Configure-Request (Section 5.1) | SHOULD | 5.1 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-5.6-4` | Upon Code-Reject of fundamental code, report problem and drop connection (Section 5.6) | SHOULD | 5.6 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-5.7-5` | Protocol-Reject received outside Opened state should be silently discarded (Section 5.7) | SHOULD | 5.7 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-5.8-3` | Echo-Request/Reply received outside Opened state should be silently discarded (Section 5.8) | SHOULD | 5.8 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-6.2-2` | Attempt most desirable authentication protocol first; if Nak'd, try next (Section 6.2) | SHOULD | 6.2 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-6.4-4` | Magic-Number should be chosen in most random manner possible (Section 6.4) | SHOULD | 6.4 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-4.2-1` | Passive option should not be used on switched circuits (Section 4.2) | SHOULD NOT | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-3.5-8` | Link quality determination may occur concurrently with authentication (Section 3.5) | MAY | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-4.6-9` | Restart timer may use exponential backoff; each value should be at least 2x previous (Section 4.6) | MAY | 4.6 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-5.1-5` | Identifier may remain unchanged for retransmissions (Section 5.1) | MAY | 5.1 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-5.3-9` | On Configure-Nak, options may be modified as specified (Section 5.3) | MAY | 5.3 | **positive:** no positive test. **negative:** no negative test | | `RFC1661-5.3-10` | Responder may append desired options to Configure-Nak to prompt peer (Section 5.3) | MAY | 5.3 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC1661-2-2`](#rfc1661-2-2) Information field plus Padding must fit within peer's MRU (default 1500) (Section 2) | {gap}, no test | ze sizes every frame it emits from the fixed 1500-octet frameBufPool buffer and never consults the peer's negotiated MRU -- getFrameBuf (internal/component/l2tp/ppp/session_run.go:59-65) hands out MaxFrameLen bytes and sendCodeReject (internal/component/l2tp/ppp/session_run.go) truncates only against that buffer -- so a Code-Reject echoing a large packet exceeds a peer MRU negotiated below 1500. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5-1`](#rfc1661-5-1) LCP Length must not exceed the MRU of the link (Section 5) | {gap}, no test | the LCP Length ze writes is backfilled from what fits the fixed 1500-octet buffer (WriteLCPPacket, internal/component/l2tp/ppp/lcp.go:91-99, fed by getFrameBuf at internal/component/l2tp/ppp/session_run.go:59-65); no send path reads s.negotiatedMRU, so the Length is bounded by the default MRU rather than by a smaller MRU the peer negotiated. Disclosed in docs/features/rfc-status.md | | [`RFC1661-3.4-1`](#rfc1661-3.4-1) Non-LCP packets received during Link Establishment phase must be silently discarded (Section 3.4) | {gap}, no test | handleFrame (internal/component/l2tp/ppp/session_run.go:628-684) dispatches purely on the PPP Protocol field with no LCP-phase gate; an IPCP frame arriving during Link Establishment is copied into earlyNCPFrames (internal/component/l2tp/ppp/session_run.go:645-651) and replayed by runNCPPhase (internal/component/l2tp/ppp/ncp.go) instead of being silently discarded. Disclosed in docs/features/rfc-status.md | | [`RFC1661-3.5-2`](#rfc1661-3.5-2) Exchange of link quality determination packets must not delay authentication indefinitely (Section 3.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze implements no link-quality determination protocol. The LCP Quality-Protocol option (type 4) has no constant in internal/component/l2tp/ppp/lcp_options.go:14-21 and negotiatePeerOption (internal/component/l2tp/ppp/lcp_options.go) Configure-Rejects it as an unknown type; a grep for LQR, 0xC025 and Quality-Protocol across internal/ matches only that lcp_options.go comment naming type 4 as unimplemented | | [`RFC1661-3.5-4`](#rfc1661-3.5-4) All other packets during Authentication phase must be silently discarded (Section 3.5) | {gap}, no test | during the authentication wait waitCHAPLike (internal/component/l2tp/ppp/auth.go:288-306) hands every non-auth frame to handleFrame, which has no phase gate (internal/component/l2tp/ppp/session_run.go:628-684), so an NCP frame received in the Authentication phase is buffered into earlyNCPFrames and replayed rather than silently discarded. Disclosed in docs/features/rfc-status.md | | [`RFC1661-3.6-3`](#rfc1661-3.6-3) Unsupported Protocol in Opened state must be returned in Protocol-Reject (Section 3.6) | {gap}, no test | ze has no Protocol-Reject sender. handleFrame (internal/component/l2tp/ppp/session_run.go:681-683) logs an unsupported PPP Protocol at debug level and drops the frame; LCPProtocolReject (internal/component/l2tp/ppp/lcp.go:25) appears only in LCPCodeName and in the receive-side codeToEvent mapping. Disclosed in docs/features/rfc-status.md | | [`RFC1661-3.7-2`](#rfc1661-3.7-2) Non-LCP packets during Link Termination phase must be silently discarded (Section 3.7) | {gap}, no test | handleFrame (internal/component/l2tp/ppp/session_run.go:637-680) carries no LCP-state guard, so an IPCP or IPv6CP packet arriving while LCP sits in Closing or Stopping is still passed to handleNCPPacket (internal/component/l2tp/ppp/ncp.go) rather than silently discarded. Disclosed in docs/features/rfc-status.md | | [`RFC1661-4.3-3`](#rfc1661-4.3-3) Implementation must stop sending the offending packet type on RXJ+ (Section 4.3) | {gap}, no test | codeToEvent (internal/component/l2tp/ppp/session_run.go:703-704) maps a received Code-Reject or Protocol-Reject to RXJ+, and every RXJ+ edge in LCPDoTransition (internal/component/l2tp/ppp/ppp_fsm.go:227, :305, :337, :369, :399) carries no action; ze holds no per-packet-type suppression state, so it keeps sending the offending packet type. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.1-3`](#rfc1661-5.1-3) Identifier field must be changed whenever Options field changes or a valid reply is received (Section 5.1) | {gap}, no test | sendConfigureRequest (internal/component/l2tp/ppp/session_run.go) writes a constant Identifier of 1 into every LCP Configure-Request, so the Identifier changes neither when the option list changes nor after a valid reply arrives. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.2-3`](#rfc1661-5.2-3) On reception of Configure-Ack, Identifier must match last transmitted Configure-Request (Section 5.2) | {gap}, no test | handleLCPPacket (internal/component/l2tp/ppp/session_run.go) feeds a Configure-Ack straight through codeToEvent (internal/component/l2tp/ppp/session_run.go:695-696) as RCA without comparing pkt.Identifier against the last transmitted Configure-Request; ze stores no last-sent Identifier. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.2-4`](#rfc1661-5.2-4) Configure-Ack options must exactly match last transmitted Configure-Request (Section 5.2) | {gap}, no test | ze keeps no copy of the options it last sent -- sendConfigureRequest rebuilds them from s.maxMRU, s.magic and s.configuredAuthMethod on each call (internal/component/l2tp/ppp/session_run.go) -- so handleLCPPacket accepts a Configure-Ack without comparing its option list to the last transmitted Configure-Request. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.3-4`](#rfc1661-5.3-4) Multi-instance option in Nak must list all acceptable values (Section 5.3) | no test | no test carries this requirement id; annotated {not-applicable}: the requirement is conditional on an option type that "can be listed more than once with different values", and RFC 1661 Section 6 says of its own set: "(None of the Configuration Options in this specification can be listed more than once.)" Ze implements types 1, 2, 3, 5, 7 and 8 (internal/component/l2tp/ppp/lcp_options.go:14-21), none of them multi-instance, so no input gives ze a list of acceptable values to send. The earlier reason here read that vacuity off NegotiatePeerOptions (internal/component/l2tp/ppp/lcp_options.go), which emits one Nak entry per received OPTION rather than per Type; keeping the reply to one entry per Type is appendUnlessListed (internal/component/l2tp/ppp/session_run.go), not this row | | [`RFC1661-5.3-7`](#rfc1661-5.3-7) On reception of Configure-Nak, Identifier must match last transmitted Configure-Request (Section 5.3) | {gap}, no test | a Configure-Nak reaches codeToEvent as RCN (internal/component/l2tp/ppp/session_run.go:697-698) with no Identifier comparison, and adjustAuthOnNakOrReject (internal/component/l2tp/ppp/auth.go:38-46) parses the option list without checking pkt.Identifier against the last transmitted Configure-Request. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.4-3`](#rfc1661-5.4-3) On reception of Configure-Reject, Identifier must match last transmitted Configure-Request (Section 5.4) | {gap}, no test | a Configure-Reject reaches codeToEvent as RCN (internal/component/l2tp/ppp/session_run.go:697-698) with no Identifier comparison, and adjustAuthOnNakOrReject (internal/component/l2tp/ppp/auth.go:38-56) acts on it without checking pkt.Identifier against the last transmitted Configure-Request. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.4-4`](#rfc1661-5.4-4) Configure-Reject options must be a subset of last transmitted Configure-Request (Section 5.4, Errata 543) | {gap}, no test | ze retains no record of the options in its last transmitted Configure-Request (sendConfigureRequest rebuilds them per call, internal/component/l2tp/ppp/session_run.go), so a Configure-Reject is acted on without verifying its options are a subset of that request. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.4-5`](#rfc1661-5.4-5) Next Configure-Request must not include any rejected options (Section 5.4) | {gap}, no test | only the Authentication-Protocol option is absorbed on a peer Configure-Reject -- adjustAuthOnNakOrReject clears configuredAuthMethod (internal/component/l2tp/ppp/auth.go:52-56) -- while sendConfigureRequest (internal/component/l2tp/ppp/session_run.go) rebuilds the MRU and Magic-Number options from s.maxMRU and s.magic on every call, so a rejected MRU or Magic-Number option reappears in the next Configure-Request. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.6-2`](#rfc1661-5.6-2) Identifier must be changed for each Code-Reject sent (Section 5.6) | {gap}, no test | sendCodeReject (internal/component/l2tp/ppp/session_run.go) reuses the offending packet's Identifier for the Code-Reject instead of allocating a fresh one, so the Identifier does not change per Code-Reject sent. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.6-3`](#rfc1661-5.6-3) Rejected-Packet in Code-Reject must be truncated to peer's MRU (Section 5.6) | {gap}, no test | sendCodeReject (internal/component/l2tp/ppp/session_run.go) truncates the Rejected-Packet only against the fixed 1500-octet buffer from getFrameBuf (internal/component/l2tp/ppp/session_run.go:59-65) and never reads s.negotiatedMRU, so the copy is not bounded by a peer MRU negotiated below 1500. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.7-1`](#rfc1661-5.7-1) In Opened state, unsupported Protocol must be reported via Protocol-Reject (Section 5.7) | {gap}, no test | ze has no Protocol-Reject sender. In the Opened state an unsupported PPP Protocol reaches the drop path in handleFrame (internal/component/l2tp/ppp/session_run.go:681-683); no code writes an LCPProtocolReject packet (internal/component/l2tp/ppp/lcp.go:25 is referenced only by LCPCodeName and codeToEvent). Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.7-2`](#rfc1661-5.7-2) On reception of Protocol-Reject, must stop sending packets of indicated protocol (Section 5.7) | {gap}, no test | codeToEvent (internal/component/l2tp/ppp/session_run.go:703-704) turns a received Protocol-Reject into RXJ+, whose FSM edge in Opened carries no action (internal/component/l2tp/ppp/ppp_fsm.go:399-400); ze records no rejected-protocol set, so it keeps sending packets of the indicated protocol. Disclosed in docs/features/rfc-status.md | | [`RFC1661-5.7-3`](#rfc1661-5.7-3) Identifier must be changed for each Protocol-Reject sent (Section 5.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze never transmits a Protocol-Reject. A grep for LCPProtocolReject across internal/ matches only the constant (internal/component/l2tp/ppp/lcp.go:25), its name in LCPCodeName (internal/component/l2tp/ppp/lcp.go:119) and the receive-side codeToEvent mapping (internal/component/l2tp/ppp/session_run.go:703); no send path exists, so no Identifier is allocated for one | | [`RFC1661-5.7-4`](#rfc1661-5.7-4) Rejected-Information in Protocol-Reject must be truncated to peer's MRU (Section 5.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze never transmits a Protocol-Reject, so no Rejected-Information field is built. A grep for LCPProtocolReject across internal/ matches only internal/component/l2tp/ppp/lcp.go:25, lcp.go:119 and the receive-side session_run.go:703 | | [`RFC1661-5.9-1`](#rfc1661-5.9-1) Discard-Request must only be sent in Opened state (Section 5.9) | no test | no test carries this requirement id; annotated {not-applicable}: ze never transmits a Discard-Request. A grep for LCPDiscardRequest across internal/ matches only the constant (internal/component/l2tp/ppp/lcp.go:28), LCPCodeName (internal/component/l2tp/ppp/lcp.go:125) and the receive-side codeToEvent mapping (internal/component/l2tp/ppp/session_run.go:705) | | [`RFC1661-6.5-3`](#rfc1661-6.5-3) When PFC negotiated, must accept both single-octet and double-octet Protocol fields (Section 6.5) | {gap}, no test | ParseFrame (internal/component/l2tp/ppp/frame.go:59-67) accepts only the two-octet Protocol form, a choice documented at internal/component/l2tp/ppp/frame.go:44-58, so a single-octet Protocol field is rejected as a malformed frame even once Protocol-Field-Compression is negotiated. Disclosed in docs/features/rfc-status.md | | [`RFC1661-6.6-1`](#rfc1661-6.6-1) All implementations must transmit frames with Address and Control fields appropriate to link framing (Section 6.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze performs no HDLC-like framing. It writes protocol-plus-payload frames to a /dev/ppp channel fd (WriteFrame, internal/component/l2tp/ppp/frame.go:81-85) and the kernel PPP driver supplies the Address and Control octets; a grep for 0xFF03 and HDLC across internal/ matches only two comments in internal/component/l2tp/ppp/lcp_options.go, one on desiredLCPOption and one on negotiatePeerOption, each recording that the kernel does the framing | | [`RFC1661-6.6-2`](#rfc1661-6.6-2) Address and Control fields must not be compressed when sending LCP packets (Section 6.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze emits no Address or Control field on any packet, LCP included: WriteFrame (internal/component/l2tp/ppp/frame.go:81-85) writes only the Protocol field and payload, and a grep for 0xFF03 and HDLC across internal/ matches only two comments in internal/component/l2tp/ppp/lcp_options.go, one on desiredLCPOption and one on negotiatePeerOption, each recording that the kernel supplies the framing | | [`RFC1661-4.6-1`](#rfc1661-4.6-1) Restart timer must be configurable (Section 4.6) | {gap}, no test | the Configure-Request retransmission timer is a fixed 3-second ticker created inline in run (internal/component/l2tp/ppp/session_run.go:217); StartSession (internal/component/l2tp/ppp/start_session.go) carries no restart-timer field and no YANG leaf sets one. Disclosed in docs/features/rfc-status.md | | [`RFC1661-4.6-2`](#rfc1661-4.6-2) Max-Terminate must be configurable (Section 4.6) | {gap}, no test | ze holds no Restart counter -- performAction treats LCPActIRC and LCPActZRC as no-ops (internal/component/l2tp/ppp/session_run.go) -- so there is no Max-Terminate value to configure; sendTerminateRequest (internal/component/l2tp/ppp/session_run.go) fires only from FSM edges. Disclosed in docs/features/rfc-status.md | | [`RFC1661-4.6-3`](#rfc1661-4.6-3) Max-Configure must be configurable (Section 4.6) | {gap}, no test | ze bounds LCP negotiation with the fixed 30-second defaultNegoTimeout (internal/component/l2tp/ppp/session_run.go, the constant and its use in run) rather than a Configure-Request transmission count, and the Restart counter LCPActIRC would initialize is a no-op (internal/component/l2tp/ppp/session_run.go, performAction), so no Max-Configure value exists to configure. Disclosed in docs/features/rfc-status.md | | [`RFC1661-4.6-4`](#rfc1661-4.6-4) Max-Failure must be configurable (Section 4.6) | {gap}, no test | ze counts no Configure-Naks sent; sendConfigureNakOrReject (internal/component/l2tp/ppp/session_run.go) picks Nak or Reject from the LCPNakOrReject verdict over NegotiatePeerOptions output on each request, so there is no Max-Failure value to configure and no threshold that converts a Nak into a Reject. Disclosed in docs/features/rfc-status.md | | [`RFC1661-4.4-1`](#rfc1661-4.4-1) On irc action, timeout period must be reset to initial value when backoff is used (Section 4.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze applies no Restart timer backoff. The retransmission timer is a fixed-interval ticker, time.NewTicker(3 * time.Second) at internal/component/l2tp/ppp/session_run.go:217, with no growing timeout value, so the condition "when Restart timer backoff is used" never holds | | [`RFC1661-4.4-2`](#rfc1661-4.4-2) On zrc action, timeout period must be set to appropriate value (Section 4.4) | {gap}, no test | performAction treats LCPActZRC as a no-op (internal/component/l2tp/ppp/session_run.go), so the Opened+RTR edge that prescribes zrc (internal/component/l2tp/ppp/ppp_fsm.go:393-394) neither zeroes a Restart counter nor sets a timeout period. Disclosed in docs/features/rfc-status.md | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1661-2-1`](#rfc1661-2-1) Protocol field: LSB of least-significant octet must equal 1; LSB of most-significant octet must equal 0; frames violating these rules must be treated as unrecognized Protocol (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NonCompliantProtocolTreatedUnrecognized`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L187) | unit/verify | unproven | | positive | [`TestRFC1661CompliantProtocolRecognized`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L212) | unit/verify | unproven | ### [`RFC1661-2-2`](#rfc1661-2-2) Information field plus Padding must fit within peer's MRU (default 1500) (Section 2) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-2-2, so no unit is bound to it. ### [`RFC1661-5-1`](#rfc1661-5-1) LCP Length must not exceed the MRU of the link (Section 5) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5-1, so no unit is bound to it. ### [`RFC1661-6-1`](#rfc1661-6-1) A negotiable Configuration Option received in a Configure-Request with an invalid or unrecognized Length should draw a Configure-Nak carrying the desired Configuration Option with an appropriate Length and Data (Section 6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661InvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L159) | unit/verify | unproven | | negative | [`TestRFC1661LCPInvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L325) | unit/verify | unproven | | negative | [`TestRFC1661LCPUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L465) | unit/verify | unproven | | negative | [`TestRFC1661LCPWrongLengthMagicIsNakedNotRejected`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L408) | unit/verify | unproven | | negative | [`TestRFC1661ReplyListsEachOptionTypeOnce`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L728) | unit/verify | unproven | | negative | [`TestRFC1661ClientInvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L154) | unit/verify | unproven | | positive | [`TestRFC1661InvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L154) | unit/verify | unproven | | positive | [`TestRFC1661LCPInvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L318) | unit/verify | unproven | | positive | [`TestRFC1661LCPWrongLengthMagicIsNakedNotRejected`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L404) | unit/verify | unproven | | positive | [`TestRFC1661ReplyListsEachOptionTypeOnce`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L724) | unit/verify | unproven | | positive | [`TestRFC1661ClientInvalidOptionLengthDrawsNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L148) | unit/verify | unproven | ### [`RFC1661-6-2`](#rfc1661-6-2) A Configuration Option whose Data is indicated by its Length to extend beyond the end of the Information field must cause the entire packet to be silently discarded without affecting the automaton (Section 6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661LCPReplyWithOptionsPastEndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L590) | unit/verify | unproven | | negative | [`TestRFC1661LCPTruncatedOptionSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L243) | unit/verify | unproven | | negative | [`TestRFC1661NCPReplyWithOptionsPastEndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L660) | unit/verify | unproven | | negative | [`TestRFC1661TruncatedOptionSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L81) | unit/verify | unproven | | negative | [`TestRFC1661ClientRequestPastEndSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L111) | unit/verify | unproven | | positive | [`TestRFC1661LCPReplyWithOptionsPastEndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L584) | unit/verify | unproven | | positive | [`TestRFC1661LCPTruncatedOptionSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L236) | unit/verify | unproven | | positive | [`TestRFC1661NCPReplyWithOptionsPastEndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L655) | unit/verify | unproven | | positive | [`TestRFC1661TruncatedOptionSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L75) | unit/verify | unproven | | positive | [`TestRFC1661ClientRequestPastEndSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L104) | unit/verify | unproven | ### [`RFC1661-3.1-1`](#rfc1661-3.1-1) Each end must first send LCP packets to configure and test the data link (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC1661LCPPacketsSentFirst`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L280) | unit/verify | unproven | ### [`RFC1661-3.1-2`](#rfc1661-3.1-2) PPP must send NCP packets to choose and configure network-layer protocols (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NoNCPPacketsWhenNoNetworkProtocol`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L347) | unit/verify | unproven | | positive | [`TestRFC1661NCPConfiguresEachFamilySeparately`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L308) | unit/verify | unproven | ### [`RFC1661-3.4-1`](#rfc1661-3.4-1) Non-LCP packets received during Link Establishment phase must be silently discarded (Section 3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-3.4-1, so no unit is bound to it. ### [`RFC1661-3.5-1`](#rfc1661-3.5-1) If peer authentication is desired, must request Authentication-Protocol during Link Establishment (Section 3.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NoAuthProtocolWhenNotDesired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L440) | unit/verify | unproven | | positive | [`TestRFC1661AuthProtocolRequestedDuringEstablishment`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L409) | unit/verify | unproven | ### [`RFC1661-3.5-2`](#rfc1661-3.5-2) Exchange of link quality determination packets must not delay authentication indefinitely (Section 3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-3.5-2, so no unit is bound to it. ### [`RFC1661-3.5-3`](#rfc1661-3.5-3) Advancement from Authentication to Network-Layer Protocol phase must not occur until authentication completes (Section 3.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NetworkPhaseRunsAfterAuthCompletes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L500) | unit/verify | unproven | | positive | [`TestRFC1661NoNetworkPhaseUntilAuthCompletes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L467) | unit/verify | unproven | ### [`RFC1661-3.5-4`](#rfc1661-3.5-4) All other packets during Authentication phase must be silently discarded (Section 3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-3.5-4, so no unit is bound to it. ### [`RFC1661-3.6-1`](#rfc1661-3.6-1) Each network-layer protocol must be separately configured by appropriate NCP (Section 3.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NCPStatesAreIndependent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L364) | unit/verify | unproven | | positive | [`TestRFC1661NCPConfiguresEachFamilySeparately`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L312) | unit/verify | unproven | ### [`RFC1661-3.6-2`](#rfc1661-3.6-2) Network-layer packets received when corresponding NCP is not Opened must be silently discarded (Section 3.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC1661NetworkLayerPacketDiscardedBeforeNCPOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L382) | unit/verify | unproven | ### [`RFC1661-3.6-3`](#rfc1661-3.6-3) Unsupported Protocol in Opened state must be returned in Protocol-Reject (Section 3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-3.6-3, so no unit is bound to it. ### [`RFC1661-3.7-1`](#rfc1661-3.7-1) Receiver of Terminate-Request must not disconnect until at least one Restart time after sending Terminate-Ack (Section 3.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC1661TerminateAckSentAndLinkHeld`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L532) | unit/verify | unproven | ### [`RFC1661-3.7-2`](#rfc1661-3.7-2) Non-LCP packets during Link Termination phase must be silently discarded (Section 3.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-3.7-2, so no unit is bound to it. ### [`RFC1661-4.3-1`](#rfc1661-4.3-1) Implementation must be prepared to immediately renegotiate Configuration Options on RCR+/RCR- in Opened (Section 4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661EchoDoesNotRenegotiate`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L620) | unit/verify | unproven | | positive | [`TestRFC1661RenegotiateOnConfigureRequestInOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L587) | unit/verify | unproven | ### [`RFC1661-4.3-2`](#rfc1661-4.3-2) Implementation must be prepared to receive new Configure-Request without admin intervention after RTR (Section 4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC1661NewConfigureRequestAcceptedAfterTerminateRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L640) | unit/verify | unproven | ### [`RFC1661-4.3-3`](#rfc1661-4.3-3) Implementation must stop sending the offending packet type on RXJ+ (Section 4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-4.3-3, so no unit is bound to it. ### [`RFC1661-5.1-1`](#rfc1661-5.1-1) An implementation wishing to open a connection must transmit a Configure-Request (Section 5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661UpWithoutOpenSendsNoConfigureRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L697) | unit/verify | unproven | | positive | [`TestRFC1661OpenTransmitsConfigureRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L666) | unit/verify | unproven | ### [`RFC1661-5.1-2`](#rfc1661-5.1-2) Upon reception of Configure-Request, an appropriate reply must be transmitted (Section 5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661ConfigureRejectForUnrecognizedOption`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L779) | unit/verify | unproven | | positive | [`TestRFC1661ConfigureAckEchoesOptionsVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L715) | unit/verify | unproven | ### [`RFC1661-5.1-3`](#rfc1661-5.1-3) Identifier field must be changed whenever Options field changes or a valid reply is received (Section 5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.1-3, so no unit is bound to it. ### [`RFC1661-5.2-1`](#rfc1661-5.2-1) If all options recognizable and acceptable, must transmit Configure-Ack (Section 5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661ConfigureRejectForUnrecognizedOption`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L783) | unit/verify | unproven | | positive | [`TestRFC1661ConfigureAckEchoesOptionsVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L719) | unit/verify | unproven | ### [`RFC1661-5.2-2`](#rfc1661-5.2-2) Acknowledged Configuration Options must not be reordered or modified (Section 5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661ConfigureAckDoesNotReorderOptions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L748) | unit/verify | unproven | | positive | [`TestRFC1661ConfigureAckEchoesOptionsVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L722) | unit/verify | unproven | ### [`RFC1661-5.2-3`](#rfc1661-5.2-3) On reception of Configure-Ack, Identifier must match last transmitted Configure-Request (Section 5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.2-3, so no unit is bound to it. ### [`RFC1661-5.2-4`](#rfc1661-5.2-4) Configure-Ack options must exactly match last transmitted Configure-Request (Section 5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.2-4, so no unit is bound to it. ### [`RFC1661-5.3-1`](#rfc1661-5.3-1) If all options recognized but some values unacceptable, must transmit Configure-Nak (Section 5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NoNakForAcceptableValue`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L904) | unit/verify | unproven | | positive | [`TestRFC1661ConfigureNakSuggestsAcceptableValue`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L869) | unit/verify | unproven | ### [`RFC1661-5.3-2`](#rfc1661-5.3-2) Boolean options (no value) must use Configure-Reject instead of Configure-Nak (Section 5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661ValuedOptionUsesNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L956) | unit/verify | unproven | | positive | [`TestRFC1661BooleanOptionsUseRejectNotNak`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L934) | unit/verify | unproven | ### [`RFC1661-5.3-3`](#rfc1661-5.3-3) Single-instance option in Nak must be modified to acceptable value (Section 5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NoNakForAcceptableValue`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L907) | unit/verify | unproven | | positive | [`TestRFC1661ConfigureNakSuggestsAcceptableValue`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L873) | unit/verify | unproven | ### [`RFC1661-5.3-4`](#rfc1661-5.3-4) Multi-instance option in Nak must list all acceptable values (Section 5.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.3-4, so no unit is bound to it. ### [`RFC1661-5.3-5`](#rfc1661-5.3-5) Nak option value fields must indicate values acceptable to the sender (Section 5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661RejectedValueStaysUnacceptable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L995) | unit/verify | unproven | | positive | [`TestRFC1661NakValueIsAcceptable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L973) | unit/verify | unproven | ### [`RFC1661-5.3-6`](#rfc1661-5.3-6) Options from Configure-Request must not be reordered in Configure-Nak (Section 5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NakOrderFollowsRequestNotAFixedOrder`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1031) | unit/verify | unproven | | positive | [`TestRFC1661NakPreservesRequestOrder`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1012) | unit/verify | unproven | ### [`RFC1661-5.3-7`](#rfc1661-5.3-7) On reception of Configure-Nak, Identifier must match last transmitted Configure-Request (Section 5.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.3-7, so no unit is bound to it. ### [`RFC1661-5.3-8`](#rfc1661-5.3-8) Implementation must handle option length different from original Configure-Request (Section 5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NakTooShortOptionNotDecoded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1071) | unit/verify | unproven | | positive | [`TestRFC1661NakHandlesDifferentOptionLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1049) | unit/verify | unproven | ### [`RFC1661-5.4-1`](#rfc1661-5.4-1) If options not recognized or not acceptable for negotiation, must transmit Configure-Reject (Section 5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NCPUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L531) | unit/verify | unproven | | negative | [`TestRFC1661NoConfigureRejectWhenAllOptionsRecognized`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L817) | unit/verify | unproven | | negative | [`TestRFC1661ClientAcceptsServerAuthProtocol`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L354) | unit/verify | unproven | | negative | [`TestRFC1661ClientUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L202) | unit/verify | unproven | | positive | [`TestRFC1661LCPUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L459) | unit/verify | unproven | | positive | [`TestRFC1661NCPUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L526) | unit/verify | unproven | | positive | [`TestRFC1661ConfigureRejectForUnrecognizedOption`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L786) | unit/verify | unproven | | positive | [`TestRFC1661ClientUnrecognizedTypeOutranksInvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L197) | unit/verify | unproven | ### [`RFC1661-5.4-2`](#rfc1661-5.4-2) Configure-Reject options must not be reordered or modified (Section 5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661LCPRejectEchoesTheRefusedOptionUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L819) | unit/verify | unproven | | negative | [`TestRFC1661ConfigureRejectDoesNotReorderOptions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L835) | unit/verify | unproven | | negative | [`TestRFC1661ClientRejectEchoesTheRefusedOptionUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L245) | unit/verify | unproven | | positive | [`TestRFC1661LCPRejectEchoesTheRefusedOptionUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_option_length_test.go#L808) | unit/verify | unproven | | positive | [`TestRFC1661ConfigureRejectForUnrecognizedOption`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L789) | unit/verify | unproven | | positive | [`TestRFC1661ClientRejectEchoesTheRefusedOptionUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L233) | unit/verify | unproven | ### [`RFC1661-5.4-3`](#rfc1661-5.4-3) On reception of Configure-Reject, Identifier must match last transmitted Configure-Request (Section 5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.4-3, so no unit is bound to it. ### [`RFC1661-5.4-4`](#rfc1661-5.4-4) Configure-Reject options must be a subset of last transmitted Configure-Request (Section 5.4, Errata 543) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.4-4, so no unit is bound to it. ### [`RFC1661-5.4-5`](#rfc1661-5.4-5) Next Configure-Request must not include any rejected options (Section 5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.4-5, so no unit is bound to it. ### [`RFC1661-5.5-1`](#rfc1661-5.5-1) Upon reception of Terminate-Request, a Terminate-Ack must be transmitted (Section 5.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NoTerminateAckForTerminateAck`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L566) | unit/verify | unproven | | positive | [`TestRFC1661TerminateAckSentAndLinkHeld`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L537) | unit/verify | unproven | ### [`RFC1661-5.6-1`](#rfc1661-5.6-1) Unknown code must be reported via Code-Reject (Section 5.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NoCodeRejectForKnownCode`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1125) | unit/verify | unproven | | positive | [`TestRFC1661CodeRejectForUnknownCode`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1093) | unit/verify | unproven | ### [`RFC1661-5.6-2`](#rfc1661-5.6-2) Identifier must be changed for each Code-Reject sent (Section 5.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.6-2, so no unit is bound to it. ### [`RFC1661-5.6-3`](#rfc1661-5.6-3) Rejected-Packet in Code-Reject must be truncated to peer's MRU (Section 5.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.6-3, so no unit is bound to it. ### [`RFC1661-5.7-1`](#rfc1661-5.7-1) In Opened state, unsupported Protocol must be reported via Protocol-Reject (Section 5.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.7-1, so no unit is bound to it. ### [`RFC1661-5.7-2`](#rfc1661-5.7-2) On reception of Protocol-Reject, must stop sending packets of indicated protocol (Section 5.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.7-2, so no unit is bound to it. ### [`RFC1661-5.7-3`](#rfc1661-5.7-3) Identifier must be changed for each Protocol-Reject sent (Section 5.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.7-3, so no unit is bound to it. ### [`RFC1661-5.7-4`](#rfc1661-5.7-4) Rejected-Information in Protocol-Reject must be truncated to peer's MRU (Section 5.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.7-4, so no unit is bound to it. ### [`RFC1661-5.8-1`](#rfc1661-5.8-1) On Echo-Request in Opened state, Echo-Reply must be transmitted (Section 5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661NoEchoOutsideOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1187) | unit/verify | unproven | | positive | [`TestRFC1661EchoReplyInOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1146) | unit/verify | unproven | ### [`RFC1661-5.8-2`](#rfc1661-5.8-2) Echo-Request and Echo-Reply must only be sent in Opened state (Section 5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661EchoReplyInOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1149) | unit/verify | unproven | | positive | [`TestRFC1661NoEchoOutsideOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1191) | unit/verify | unproven | ### [`RFC1661-5.9-1`](#rfc1661-5.9-1) Discard-Request must only be sent in Opened state (Section 5.9) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-5.9-1, so no unit is bound to it. ### [`RFC1661-5.9-2`](#rfc1661-5.9-2) Receiver must silently discard any Discard-Request (Section 5.9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC1661DiscardRequestSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1213) | unit/verify | unproven | ### [`RFC1661-6.2-1`](#rfc1661-6.2-1) Multiple Authentication-Protocol options must not be included in a single Configure-Request (Section 6.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC1661SingleAuthProtocolOptionInRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1245) | unit/verify | unproven | ### [`RFC1661-6.4-1`](#rfc1661-6.4-1) If implementation transmits Configure-Request with Magic-Number, must not Configure-Reject peer's Magic-Number option (Section 6.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661UnknownOptionRejectedWhileMagicIsNot`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1295) | unit/verify | unproven | | positive | [`TestRFC1661ZeroMagicNumberRefused`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1323) | unit/verify | unproven | ### [`RFC1661-6.4-2`](#rfc1661-6.4-2) If Magic-Number negotiated, Echo/Discard packets must carry negotiated Magic-Number (Section 6.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661EchoReplyDoesNotMirrorPeerMagic`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1362) | unit/verify | unproven | | positive | [`TestRFC1661EchoReplyInOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1159) | unit/verify | unproven | ### [`RFC1661-6.4-3`](#rfc1661-6.4-3) Magic-Number of zero must always be Nak'd if not Rejected (Section 6.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1661PeerMagicNumberAcked`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1276) | unit/verify | unproven | | negative | [`TestRFC1661ClientNaksZeroMagicWithAValueOfItsOwn`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L408) | unit/verify | unproven | | positive | [`TestRFC1661ZeroMagicNumberRefused`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L1315) | unit/verify | unproven | | positive | [`TestRFC1661ClientNaksZeroMagicWithAValueOfItsOwn`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1661_option_length_test.go#L402) | unit/verify | unproven | ### [`RFC1661-6.5-1`](#rfc1661-6.5-1) All implementations must transmit packets with two-octet PPP Protocol fields by default (Section 6.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC1661TwoOctetProtocolField`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L232) | unit/verify | unproven | ### [`RFC1661-6.5-2`](#rfc1661-6.5-2) Compressed Protocol fields must not be transmitted unless PFC option negotiated (Section 6.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC1661TwoOctetProtocolField`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1661_test.go#L236) | unit/verify | unproven | ### [`RFC1661-6.5-3`](#rfc1661-6.5-3) When PFC negotiated, must accept both single-octet and double-octet Protocol fields (Section 6.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-6.5-3, so no unit is bound to it. ### [`RFC1661-6.6-1`](#rfc1661-6.6-1) All implementations must transmit frames with Address and Control fields appropriate to link framing (Section 6.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-6.6-1, so no unit is bound to it. ### [`RFC1661-6.6-2`](#rfc1661-6.6-2) Address and Control fields must not be compressed when sending LCP packets (Section 6.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-6.6-2, so no unit is bound to it. ### [`RFC1661-4.6-1`](#rfc1661-4.6-1) Restart timer must be configurable (Section 4.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-4.6-1, so no unit is bound to it. ### [`RFC1661-4.6-2`](#rfc1661-4.6-2) Max-Terminate must be configurable (Section 4.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-4.6-2, so no unit is bound to it. ### [`RFC1661-4.6-3`](#rfc1661-4.6-3) Max-Configure must be configurable (Section 4.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-4.6-3, so no unit is bound to it. ### [`RFC1661-4.6-4`](#rfc1661-4.6-4) Max-Failure must be configurable (Section 4.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-4.6-4, so no unit is bound to it. ### [`RFC1661-4.4-1`](#rfc1661-4.4-1) On irc action, timeout period must be reset to initial value when backoff is used (Section 4.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-4.4-1, so no unit is bound to it. ### [`RFC1661-4.4-2`](#rfc1661-4.4-2) On zrc action, timeout period must be set to appropriate value (Section 4.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC1661-4.4-2, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 1661, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 1661, so its obligations are stated where they were written. --- ### Page: RFC 1877 - PPP Internet Protocol Control Protocol Extensions for Name Server Addresses https://ze-software.net/quality/rfc-compliance/rfc1877/ # RFC 1877 - PPP Internet Protocol Control Protocol Extensions for Name Server Addresses Partial. Every requirement this repository extracted from RFC 1877, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 75.0% | 3 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 6 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 5 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 25.0% | 1 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 5 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 0 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 6 | | Tagged units | 6 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc1877.md` | | Requirement shard | `rfc/requirements/rfc1877.md` | | RFC text | `rfc/full/rfc1877.txt` | ## Enrolment Enrolled: PPP IPCP Extensions for Name Server Addresses (DNS options): four MUST-level requirements. RFC1877-x-1 (link usable for IPv4 with or without DNS) both polarities via existing tests: TestIPCPDNSRejectAbsorbed (a peer Configure-Reject of the DNS options is absorbed and the session survives, still negotiating the IPv4 address) and TestIPCPIPAddressRejectIsFatal (rejecting the IP-Address IS fatal, so DNS not the address is the optional part). RFC1877-x-3 (Configure-Ack echoes the option Data verbatim when acceptable) both polarities via TestRFC1877ConfigureAckEchoesAcceptable: an acceptable request is Acked byte-for-byte (sendNCPConfigureAck writes req.Data unchanged, ncp.go:567) while an unacceptable IP-Address draws a Nak not an Ack. RFC1877-x-4 (Configure-Reject echoes the offending unsupported option) both polarities via TestRFC1877ConfigureRejectEchoesUnsupportedOnly: copyUnknownOptions (ncp.go:619) echoes an unknown type-99 option verbatim and skips the recognized IP-Address option. RFC1877-x-2 (option with Length other than 6 must be Configure-Rejected) is {gap}: Ze validates the length (errIPCPBadOptionLen) but answers a bad-length known-type DNS option with a Configure-Nak rather than a Configure-Reject (its reject path keys on unknown option type, not length); disclosed in the docs/features/rfc-status.md RFC 1877 row. The x-5 MAY is not gated. ## What the public ledger says **Status:** Partial **What the ledger says is covered** - Primary and secondary DNS option parsing and negotiation - the Configure-Ack echoes acceptable options verbatim, the Configure-Reject echoes unsupported ones, and the IPv4 link stays usable with or without DNS. Tests bound per requirement in [`rfc/requirements/rfc1877.md`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc1877.md). **What the ledger says remains** Carries the L2TP and PPPoE Partial status. One MUST gap gated in [`rfc/short/rfc1877.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc1877.md): a DNS option with a Length other than 6 is answered with a Configure-Nak (correcting the value) rather than a Configure-Reject, because Ze's reject path is keyed on unknown option TYPE, not option length. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 3 | one part of the gated population | | Annotated instead of tested | 1 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (3):** [`RFC1877-x-1`](#rfc1877-x-1), [`RFC1877-x-3`](#rfc1877-x-3), [`RFC1877-x-4`](#rfc1877-x-4) **Annotated instead of tested (1):** [`RFC1877-x-2`](#rfc1877-x-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1877-x-1` | Link must still be usable for IPv4 traffic with or without DNS assignment (Scope) | MUST | x | **positive:** `unit/verify` [`TestIPCPDNSRejectAbsorbed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L302). **negative:** `unit/verify` [`TestIPCPIPAddressRejectIsFatal`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L376) | | `RFC1877-x-2` | Option with Length other than 6 must be Configure-Rejected (Configuration Options) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{gap}:** A DNS option (Primary/Secondary DNS, type 129/131) with a Length other than 6 is validated by parseIPCPv4Option (internal/component/l2tp/ppp/ipcp.go:100-102, errIPCPBadOptionLen) and flags the Configure-Request as bad in evalIPCPRequest (internal/component/l2tp/ppp/ncp.go:397-400), but Ze responds with a Configure-Nak carrying its own DNS values rather than a Configure-Reject of the malformed option. buildNakOrReject (ncp.go:586-601) takes the Reject branch only for UNKNOWN option TYPES: ipcpHasUnknownOption (ipcp.go:142-158) checks the type, not the length, so a known-type option with a bad length falls to the Nak branch. Disclosed in docs/features/rfc-status.md | | `RFC1877-x-3` | Configure-Ack must echo the option Data verbatim when value is acceptable (Negotiation Semantics) | MUST | x | **positive:** `unit/verify` [`TestRFC1877ConfigureAckEchoesAcceptable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1877_dns_options_test.go#L13). **negative:** `unit/verify` [`TestRFC1877ConfigureAckEchoesAcceptable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1877_dns_options_test.go#L17) | | `RFC1877-x-4` | Configure-Reject must echo the offending option when option is not supported (Negotiation Semantics) | MUST | x | **positive:** `unit/verify` [`TestRFC1877ConfigureRejectEchoesUnsupportedOnly`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1877_dns_options_test.go#L66). **negative:** `unit/verify` [`TestRFC1877ConfigureRejectEchoesUnsupportedOnly`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1877_dns_options_test.go#L69) | | `RFC1877-x-5` | Either side may ignore DNS/NBNS options by Configure-Reject (Scope) | MAY | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC1877-x-2`](#rfc1877-x-2) Option with Length other than 6 must be Configure-Rejected (Configuration Options) | {gap}, no test | A DNS option (Primary/Secondary DNS, type 129/131) with a Length other than 6 is validated by parseIPCPv4Option (internal/component/l2tp/ppp/ipcp.go:100-102, errIPCPBadOptionLen) and flags the Configure-Request as bad in evalIPCPRequest (internal/component/l2tp/ppp/ncp.go:397-400), but Ze responds with a Configure-Nak carrying its own DNS values rather than a Configure-Reject of the malformed option. buildNakOrReject (ncp.go:586-601) takes the Reject branch only for UNKNOWN option TYPES: ipcpHasUnknownOption (ipcp.go:142-158) checks the type, not the length, so a known-type option with a bad length falls to the Nak branch. Disclosed in docs/features/rfc-status.md | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1877-x-1`](#rfc1877-x-1) Link must still be usable for IPv4 traffic with or without DNS assignment (Scope) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPCPIPAddressRejectIsFatal`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L376) | unit/verify | unproven | | positive | [`TestIPCPDNSRejectAbsorbed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L302) | unit/verify | unproven | ### [`RFC1877-x-2`](#rfc1877-x-2) Option with Length other than 6 must be Configure-Rejected (Configuration Options) Audit verdict: not audited: no reader has judged these tests No test carries RFC1877-x-2, so no unit is bound to it. ### [`RFC1877-x-3`](#rfc1877-x-3) Configure-Ack must echo the option Data verbatim when value is acceptable (Negotiation Semantics) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1877ConfigureAckEchoesAcceptable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1877_dns_options_test.go#L17) | unit/verify | unproven | | positive | [`TestRFC1877ConfigureAckEchoesAcceptable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1877_dns_options_test.go#L13) | unit/verify | unproven | ### [`RFC1877-x-4`](#rfc1877-x-4) Configure-Reject must echo the offending option when option is not supported (Negotiation Semantics) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC1877ConfigureRejectEchoesUnsupportedOnly`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1877_dns_options_test.go#L69) | unit/verify | unproven | | positive | [`TestRFC1877ConfigureRejectEchoesUnsupportedOnly`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/rfc1877_dns_options_test.go#L66) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-fixit-rfc-drain-quota-never-armed WP-1 | | Signed off | 2026-08-31 | | Register | manual-walk | | Source | rfc/full/rfc1877.txt | | Source fingerprint | 868068cbe12bb56c | | Record | rfc/extraction/rfc1877.json | | Mapped sentences | 0 | | Declined as scope | 0 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo ('This memo provides information for the Internet community. This memo does not specify an Internet standard of any kind.'), Abstract and Table of Contents. The Abstract says what the document extends and states no obligation. The table of contents lines are indented, so they open no section of their own. | | `1` | Additional IPCP Configuration Options | 0 | walked | Additional IPCP Configuration Options. It introduces options 129 to 132, says primary and secondary addresses are negotiated independently, says the options are 'designed to be identical in format and behavior to option 3 (IP-Address)', and suggests they not be included in the list of "IPCP Recommended Options". It carries the document's only RFC 2119 keyword, the SHOULD quoted in the register-reason; SHOULD is not a gated level and is not a site under either scan, and rfc/short/rfc1877.md declares no row for it. The section directs no MUST at any speaker. Three of the summary's four gated rows are read from its indicative prose: RFC1877-x-1 from the 'not ... Recommended Options' suggestion together with the per-option 'Default: No address is provided', and RFC1877-x-3 and RFC1877-x-4 from the 'identical in format and behavior to option 3' sentence, which imports the Configure-Ack and Configure-Reject echo rules of RFC 1661 instead of stating them here. All three are declared unsourced. | | `1.1` | not stated | 0 | walked | Primary DNS Server Address: Description, packet diagram, Type 129, Length 6, field meaning and Default. Every sentence is indicative. It describes the option's format and what the remote peer typically does ('the remote peer specifies the address by NAKing this option, and returning the IP address of a valid DNS server'), and directs no MUST at a speaker. RFC1877-x-2, the summary's 'Option with Length other than 6 must be Configure-Rejected', is read from the Length value stated here, which sections 1.2, 1.3 and 1.4 then repeat, together with RFC 1661's rule for an option a receiver will not accept. RFC 1877 states no such rule, so the id is declared unsourced once, here, rather than four times. | | `1.2` | not stated | 0 | walked | Primary NBNS Server Address: Type 130, Length 6, and the same Description, diagram, field meaning and Default shape as 1.1, with NBNS in place of DNS. No sentence states an obligation. Its Length statement is one of the three repetitions RFC1877-x-2 was read from, and that id is declared unsourced on 1.1. | | `1.3` | not stated | 0 | walked | Secondary DNS Server Address: Type 131, Length 6, same shape again. No sentence states an obligation. The field paragraph carries a copy error in RFC 1877's own text, 'The four octet Secondary-DNS-Address is the address of the primary NBNS server to be used by the local peer', where every other field paragraph names its own option; it is a defect of the source, not an obligation, and it is recorded here so a later reader does not read it as one. | | `1.4` | not stated | 0 | walked | Secondary NBNS Server Address: Type 132, Length 6, same shape, no obligation. This section also carries the whole unnumbered tail of the document, because 'References', 'Security Considerations', 'Chair's Address' and 'Author's Address' head no numbered heading and sectionHeadingRE matches none of them (internal/le/rfc/inventory.go, sectionBodies), so the derivation folds them in here. The tail was walked with the section: the reference list cites RFC 1661, RFC 1332, STD 19 and STD 13 and binds nobody; Security Considerations is one sentence, 'Security issues are not discussed in this memo.', so the document names no countermeasure and no threat; the two address blocks are contact details. Nothing in this section or its tail is MUST-level. | ### Excluded sentences The walk over RFC 1877 declined no sentence: every site it found is mapped to a requirement. ## Superseded No document obsoletes RFC 1877, so its obligations are stated where they were written. --- ### Page: RFC 1994 - PPP Challenge Handshake Authentication Protocol (CHAP) https://ze-software.net/quality/rfc-compliance/rfc1994/ # RFC 1994 - PPP Challenge Handshake Authentication Protocol (CHAP) Partial. Every requirement this repository extracted from RFC 1994, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 29.4% | 5 of 17 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 52.9% | 9 of 17 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 17 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 24 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 17 | of 27 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 17 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 17 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 17 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 17 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 17.6% | 3 of 17 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 17 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 27 | | Gated MUST-level | 17 | | Not applicable, so out of scope | 0 | | Declared gaps | 3 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 24 | | Tagged units | 24 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc1994.md` | | Requirement shard | `rfc/requirements/rfc1994.md` | | RFC text | `rfc/full/rfc1994.txt` | ## Enrolment Enrolled: PPP CHAP-MD5 (RFC 1994): authenticator (LNS) + peer (PPPoE client); 5 MET (auth-protocol advertise, Success/Failure per comparison, match->Success, mismatch->Failure, peer Response) + 9 single-polarity positive (Challenge Code 1, changing Identifier/Value, echoed Identifier, repeated-Response tolerance, other-phase discard, Message-independence, interop) + 3 gap (no Challenge retransmit, no repeated-Response replay, no 1-octet secret minimum) ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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. **What the ledger says remains** Three MUST gaps in [`rfc/short/rfc1994.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc1994.md): [`RFC1994-4.1-2`](#rfc1994-4.1-2) -- one Challenge is sent then the session fails closed on timeout (no retransmission); [`RFC1994-4.1-9`](#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`](#rfc1994-2.3-1) -- the CHAP secret has no 1-octet minimum, so an empty password is accepted. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 5 | one part of the gated population | | Annotated instead of tested | 12 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **17** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (5):** [`RFC1994-1-1`](#rfc1994-1-1), [`RFC1994-4.1-3`](#rfc1994-4.1-3), [`RFC1994-4.2-1`](#rfc1994-4.2-1), [`RFC1994-4.2-2`](#rfc1994-4.2-2), [`RFC1994-4.1-4`](#rfc1994-4.1-4) **Annotated instead of tested (12):** [`RFC1994-4.1-1`](#rfc1994-4.1-1), [`RFC1994-4.1-2`](#rfc1994-4.1-2), [`RFC1994-4.1-5`](#rfc1994-4.1-5), [`RFC1994-4.1-6`](#rfc1994-4.1-6), [`RFC1994-4.1-7`](#rfc1994-4.1-7), [`RFC1994-4.2-3`](#rfc1994-4.2-3), [`RFC1994-4.1-8`](#rfc1994-4.1-8), [`RFC1994-4.1-9`](#rfc1994-4.1-9), [`RFC1994-4.1-10`](#rfc1994-4.1-10), [`RFC1994-2.3-1`](#rfc1994-2.3-1), [`RFC1994-4.2-4`](#rfc1994-4.2-4), [`RFC1994-1.1-1`](#rfc1994-1.1-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1994-1-1` | If authentication is desired, specify Authentication-Protocol Configuration Option during Link Establishment phase (Section 1) | MUST | 1 | **positive:** `unit/verify` [`TestLocalCONFREQAdvertisesAuthMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_dispatch_test.go#L684). **negative:** `unit/verify` [`TestAuthProtoRejectClearsMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_dispatch_test.go#L155) | | `RFC1994-4.1-1` | Authenticator must transmit a Challenge packet (Code=1) (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestCHAPResponseEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L385). **negative:** no negative test. **{single-polarity}:** runCHAPAuthPhase always frames and writes a CHAP Challenge with Code=1 as its first wire act, with no must-not-challenge branch (internal/component/l2tp/ppp/chap.go:259-261, :144-146) | | `RFC1994-4.1-2` | Additional Challenge packets must be sent until valid Response received or retry counter expires (Section 4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze sends exactly one Challenge and, on Response timeout, fails the session closed rather than retransmitting the same Identifier/Value (internal/component/l2tp/ppp/chap.go:257-263 single send; internal/component/l2tp/ppp/auth.go:328-337 timeout calls s.fail with no retransmit) | | `RFC1994-4.1-3` | Authenticator must send Success or Failure based on Response comparison (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestCHAPResponseEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L386). **negative:** `unit/verify` [`TestCHAPRejectWritesFailure`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L521) | | `RFC1994-4.2-1` | If Response Value equals expected value, must transmit Success (Code=3) (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestCHAPResponseEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L387). **positive:** `unit/verify` [`TestLocalAuthCHAPMD5Accept`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authlocal/auth_test.go#L47). **negative:** `unit/verify` [`TestCHAPRejectWritesFailure`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L522). **negative:** `unit/verify` [`TestLocalAuthCHAPMD5Reject`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authlocal/auth_test.go#L82) | | `RFC1994-4.2-2` | If Response Value does not equal expected value, must transmit Failure (Code=4) (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestCHAPRejectWritesFailure`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L523). **positive:** `unit/verify` [`TestLocalAuthCHAPMD5Reject`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authlocal/auth_test.go#L84). **negative:** `unit/verify` [`TestLocalAuthCHAPMD5Accept`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authlocal/auth_test.go#L50) | | `RFC1994-4.1-4` | Peer must transmit Response (Code=2) whenever Challenge is received (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestBuildCHAPResponse`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/session_test.go#L22). **negative:** `unit/verify` [`TestBuildCHAPResponseMalformed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/session_test.go#L60) | | `RFC1994-4.1-5` | Identifier must be changed each time a Challenge is sent (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestCHAPIdentifierMonotonic`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L835). **positive:** `unit/verify` [`TestCHAPIdentifierWraps`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L880). **negative:** no negative test. **{single-polarity}:** each runCHAPAuthPhase increments the per-session chapIdentifier before sending, so every new Challenge carries a distinct Identifier, and ze never retransmits a Challenge to form a reuse negative (internal/component/l2tp/ppp/chap.go:254-255) | | `RFC1994-4.1-6` | Response Identifier must be copied from the Challenge Identifier (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestBuildCHAPResponse`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/session_test.go#L24). **negative:** no negative test. **{single-polarity}:** the peer copies the received Challenge's Identifier byte into the Response header, asserted directly with no rejecting counterpart (internal/component/l2tp/pppoeclient/session.go:400) | | `RFC1994-4.1-7` | Challenge Value must be changed each time a Challenge is sent (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestCHAPChallengeRandom`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L947). **negative:** no negative test. **{single-polarity}:** runCHAPAuthPhase draws a fresh 16-octet value from crypto/rand for every Challenge (internal/component/l2tp/ppp/chap.go:219-225, :248-249) | | `RFC1994-4.2-3` | Success/Failure Identifier must be copied from the Response Identifier (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestCHAPResponseEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L388). **negative:** no negative test. **{single-polarity}:** waitCHAPResponse only returns a Response whose Identifier equals the outstanding Challenge Identifier, and runCHAPAuthPhase writes Success/Failure with that same Identifier (internal/component/l2tp/ppp/chap.go:296-298, auth.go:318-323) | | `RFC1994-4.1-8` | Authenticator must allow repeated Response packets during Network-Layer Protocol phase (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestCHAPRepeatedResponseAfterSuccessKeepsSessionUp`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_reauth_test.go#L407). **negative:** no negative test. **{single-polarity}:** a CHAP Response arriving in the main loop after auth completes hits the frame-dispatch default and is dropped without terminating the session, so repeated Responses are tolerated (internal/component/l2tp/ppp/session_run.go:681-683) | | `RFC1994-4.1-9` | Response with current Challenge Identifier must return same reply Code as previously (Section 4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze caches no per-Challenge reply Code and does not re-send the prior Success/Failure for a repeated Response; it silently drops it (internal/component/l2tp/ppp/session_run.go:681-683; no reply-Code cache in runCHAPAuthPhase) | | `RFC1994-4.1-10` | Response packets received during any other phase must be silently discarded (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestCHAPIdentifierMismatchSilentDiscard`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_reauth_test.go#L151). **negative:** no negative test. **{single-polarity}:** outside an active auth-wait a CHAP Response is silently dropped by the frame-dispatch default, and during a wait a Response whose Identifier does not match is silently discarded and the wait continues (internal/component/l2tp/ppp/session_run.go:681-683, auth.go:318-322) | | `RFC1994-2.3-1` | Length of the secret must be at least 1 octet (Section 2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the CHAP shared secret (authlocal user password) has no minimum-length constraint, so an empty password is accepted and fed to the MD5 hash without rejection (internal/component/l2tp/plugins/authlocal/auth.go:94-99; empty password stored in register.go) | | `RFC1994-4.2-4` | Message field must not affect operation of the protocol (Section 4.2) | MUST NOT | 4.2 | **positive:** `unit/verify` [`TestCHAPSuccessMessageDoesNotAffectOutcome`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1994_chap_message_test.go#L39). **negative:** no negative test. **{single-polarity}:** the peer branches only on the Success/Failure Code (3 succeed, 4 fail) and never reads or acts on the Message field (internal/component/l2tp/pppoeclient/session.go:270-274) | | `RFC1994-1.1-1` | Implementation not including an option must be prepared to interoperate with one that does (Section 1.1) | MUST | 1.1 | **positive:** `unit/verify` [`TestNegotiatePeerAuthProtoAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/lcp_options_test.go#L211). **positive:** `unit/verify` [`TestNegotiatePeerAuthProtoRejected`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/lcp_options_test.go#L194). **negative:** no negative test. **{single-polarity}:** ze negotiates the Auth-Protocol option in both directions -- it accepts a peer-proposed option and handles the peer's Configure-Nak/Reject of it -- so it interoperates whether or not the option is used (internal/component/l2tp/ppp/lcp_options.go:174-183, auth.go:38-68) | | `RFC1994-2-1` | Connection should be terminated on authentication failure (Section 2, Section 4.2) | SHOULD | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC1994-4.1-11` | Peer should expect Challenge packets during Authentication and Network-Layer Protocol phases (Section 4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC1994-2.3-2` | Secret should be at least as large and unguessable as a well-chosen password (Section 2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC1994-2.3-3` | Each challenge value should be unique, exhibit global and temporal uniqueness (Section 2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC1994-2.3-4` | Each challenge value should be unpredictable (Section 2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC1994-1.2-1` | Provide capability of logging silently discarded packets and record in statistics counter (Section 1.2) | SHOULD | 1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC1994-x-1` | Secret should not be the same in both directions (Security Considerations) | SHOULD NOT | x | **positive:** no positive test. **negative:** no negative test | | `RFC1994-4.1-12` | Challenge may be sent at any time during Network-Layer Protocol phase (Section 4.1) | MAY | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC1994-4.1-13` | Name may contain ASCII strings or ASN.1 identifiers (Section 4.1) | MAY | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC1994-4.1-14` | Success/Failure Message may differ between replies for the same Identifier (Section 4.1) | MAY | 4.1 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC1994-4.1-2`](#rfc1994-4.1-2) Additional Challenge packets must be sent until valid Response received or retry counter expires (Section 4.1) | {gap}, no test | ze sends exactly one Challenge and, on Response timeout, fails the session closed rather than retransmitting the same Identifier/Value (internal/component/l2tp/ppp/chap.go:257-263 single send; internal/component/l2tp/ppp/auth.go:328-337 timeout calls s.fail with no retransmit) | | [`RFC1994-4.1-9`](#rfc1994-4.1-9) Response with current Challenge Identifier must return same reply Code as previously (Section 4.1) | {gap}, no test | ze caches no per-Challenge reply Code and does not re-send the prior Success/Failure for a repeated Response; it silently drops it (internal/component/l2tp/ppp/session_run.go:681-683; no reply-Code cache in runCHAPAuthPhase) | | [`RFC1994-2.3-1`](#rfc1994-2.3-1) Length of the secret must be at least 1 octet (Section 2.3) | {gap}, no test | the CHAP shared secret (authlocal user password) has no minimum-length constraint, so an empty password is accepted and fed to the MD5 hash without rejection (internal/component/l2tp/plugins/authlocal/auth.go:94-99; empty password stored in register.go) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1994-1-1`](#rfc1994-1-1) If authentication is desired, specify Authentication-Protocol Configuration Option during Link Establishment phase (Section 1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAuthProtoRejectClearsMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_dispatch_test.go#L155) | unit/verify | unproven | | positive | [`TestLocalCONFREQAdvertisesAuthMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/auth_dispatch_test.go#L684) | unit/verify | unproven | ### [`RFC1994-4.1-1`](#rfc1994-4.1-1) Authenticator must transmit a Challenge packet (Code=1) (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestCHAPResponseEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L385) | unit/verify | unproven | ### [`RFC1994-4.1-2`](#rfc1994-4.1-2) Additional Challenge packets must be sent until valid Response received or retry counter expires (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC1994-4.1-2, so no unit is bound to it. ### [`RFC1994-4.1-3`](#rfc1994-4.1-3) Authenticator must send Success or Failure based on Response comparison (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCHAPRejectWritesFailure`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L521) | unit/verify | unproven | | positive | [`TestCHAPResponseEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L386) | unit/verify | unproven | ### [`RFC1994-4.2-1`](#rfc1994-4.2-1) If Response Value equals expected value, must transmit Success (Code=3) (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLocalAuthCHAPMD5Reject`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authlocal/auth_test.go#L82) | unit/verify | unproven | | negative | [`TestCHAPRejectWritesFailure`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L522) | unit/verify | unproven | | positive | [`TestLocalAuthCHAPMD5Accept`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authlocal/auth_test.go#L47) | unit/verify | unproven | | positive | [`TestCHAPResponseEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L387) | unit/verify | unproven | ### [`RFC1994-4.2-2`](#rfc1994-4.2-2) If Response Value does not equal expected value, must transmit Failure (Code=4) (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLocalAuthCHAPMD5Accept`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authlocal/auth_test.go#L50) | unit/verify | unproven | | positive | [`TestLocalAuthCHAPMD5Reject`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authlocal/auth_test.go#L84) | unit/verify | unproven | | positive | [`TestCHAPRejectWritesFailure`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L523) | unit/verify | unproven | ### [`RFC1994-4.1-4`](#rfc1994-4.1-4) Peer must transmit Response (Code=2) whenever Challenge is received (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildCHAPResponseMalformed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/session_test.go#L60) | unit/verify | unproven | | positive | [`TestBuildCHAPResponse`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/session_test.go#L22) | unit/verify | unproven | ### [`RFC1994-4.1-5`](#rfc1994-4.1-5) Identifier must be changed each time a Challenge is sent (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestCHAPIdentifierMonotonic`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L835) | unit/verify | unproven | | positive | [`TestCHAPIdentifierWraps`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L880) | unit/verify | unproven | ### [`RFC1994-4.1-6`](#rfc1994-4.1-6) Response Identifier must be copied from the Challenge Identifier (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestBuildCHAPResponse`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/session_test.go#L24) | unit/verify | unproven | ### [`RFC1994-4.1-7`](#rfc1994-4.1-7) Challenge Value must be changed each time a Challenge is sent (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestCHAPChallengeRandom`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L947) | unit/verify | unproven | ### [`RFC1994-4.2-3`](#rfc1994-4.2-3) Success/Failure Identifier must be copied from the Response Identifier (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestCHAPResponseEmitsEvent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_test.go#L388) | unit/verify | unproven | ### [`RFC1994-4.1-8`](#rfc1994-4.1-8) Authenticator must allow repeated Response packets during Network-Layer Protocol phase (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestCHAPRepeatedResponseAfterSuccessKeepsSessionUp`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_reauth_test.go#L407) | unit/verify | unproven | ### [`RFC1994-4.1-9`](#rfc1994-4.1-9) Response with current Challenge Identifier must return same reply Code as previously (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC1994-4.1-9, so no unit is bound to it. ### [`RFC1994-4.1-10`](#rfc1994-4.1-10) Response packets received during any other phase must be silently discarded (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestCHAPIdentifierMismatchSilentDiscard`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/chap_reauth_test.go#L151) | unit/verify | unproven | ### [`RFC1994-2.3-1`](#rfc1994-2.3-1) Length of the secret must be at least 1 octet (Section 2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC1994-2.3-1, so no unit is bound to it. ### [`RFC1994-4.2-4`](#rfc1994-4.2-4) Message field must not affect operation of the protocol (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestCHAPSuccessMessageDoesNotAffectOutcome`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/rfc1994_chap_message_test.go#L39) | unit/verify | unproven | ### [`RFC1994-1.1-1`](#rfc1994-1.1-1) Implementation not including an option must be prepared to interoperate with one that does (Section 1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestNegotiatePeerAuthProtoAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/lcp_options_test.go#L211) | unit/verify | unproven | | positive | [`TestNegotiatePeerAuthProtoRejected`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/lcp_options_test.go#L194) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 1994, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 1994, so its obligations are stated where they were written. --- ### Page: RFC 1997 - BGP Communities Attribute https://ze-software.net/quality/rfc-compliance/rfc1997/ # RFC 1997 - BGP Communities Attribute Supported. Every requirement this repository extracted from RFC 1997, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 5 of 5 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 5 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 5 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 5 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 3.8% | 1 of 26 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 5 | of 9 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 5 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 5 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 5 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 5 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 5 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 9 | | Gated MUST-level | 5 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 26 | | Tagged units | 26 | | Recorded audit verdicts | 0 | | Discrimination records | 1 | | Summary | `rfc/short/rfc1997.md` | | Requirement shard | `rfc/requirements/rfc1997.md` | | RFC text | `rfc/full/rfc1997.txt` | ## Enrolment Enrolled: BGP Communities: five MUST-level requirements. RFC1997-Encoding-1 (attribute length a multiple of 4) is enforced on the wire-decode path by ParseCommunities (internal/core/bgp/attribute/community.go:201, wired via wire.go knownAttrParsers); the positive/negative pair proves a valid multiple-of-4 parses into length/4 communities while a non-multiple-of-4 is rejected as malformed (ErrInvalidLength). The three egress MUST-NOTs (Well-1 NO_EXPORT, Well-2 NO_ADVERTISE, Well-3 NO_EXPORT_SUBCONFED) and the Well-4 SHALL are enforced automatically, with no operator policy involved: wireu.ScanWellKnown reads the RECEIVED payload once per UPDATE and WellKnown.AllowsEgressTo (internal/component/bgp/wireu/wellknown.go) decides per destination, asked by both forward rails through Reactor.wellKnownAllowsEgress (internal/component/bgp/reactor/forward_wellknown.go). The word "received" in each clause is load-bearing and bounds the check: a route Ze ORIGINATES carrying NO_EXPORT is using the community for its purpose and is still advertised (test/interop/scenarios/as112-community-frr). The confederation boundary is the AS boundary because Ze configures no confederation, which is what the NO_EXPORT clause itself directs for "a stand-alone autonomous system that is not part of a confederation". Proof runs at three tiers: unit pairs over the decision and both rails, test/plugin/wellknown-no-export-egress.ci and wellknown-no-advertise-egress.ci over a running daemon's wire in the verify tier, and test/interop/scenarios/bgp-wellknown-noexport-frr with FRR (external, must not learn it) and BIRD (internal, must) as independent observers. ## What the public ledger says **Status:** Supported **What the ledger says is covered** - 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". **What the ledger says remains:** Gated per requirement in [`rfc/short/rfc1997.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc1997.md) via `./le rfc check`. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 5 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **5** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (5):** [`RFC1997-Well-1`](#rfc1997-well-1), [`RFC1997-Well-2`](#rfc1997-well-2), [`RFC1997-Well-3`](#rfc1997-well-3), [`RFC1997-Encoding-1`](#rfc1997-encoding-1), [`RFC1997-Well-4`](#rfc1997-well-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC1997-Well-1` | Routes with NO_EXPORT community MUST NOT be advertised outside a BGP confederation boundary (§Well-known Communities) | MUST NOT | Well | **positive:** `unit/verify` [`TestForwardNoExportSkipsExternalPeerOnly`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L197). **positive:** `unit/verify` [`TestWellKnownNoExportRefusesExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L34). **negative:** `unit/verify` [`TestForwardNoExportStillWithdrawsFromExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L364). **negative:** `unit/verify` [`TestForwardWithoutNoExportReachesExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L223). **negative:** `unit/verify` [`TestWellKnownNoExportAllowsInternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L48). **positive:** `functional/verify` [`wellknown-no-export-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-export-egress.ci#L1). **negative:** `functional/verify` [`wellknown-no-export-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-export-egress.ci#L7). **negative:** `functional/verify` [`wellknown-no-export-withdraw-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-export-withdraw-egress.ci#L1). **positive:** `interop/nightly` [`checkNoExportBoundary`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1055). **negative:** `interop/nightly` [`checkNoExportBoundary`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1056) | | `RFC1997-Well-2` | Routes with NO_ADVERTISE community MUST NOT be advertised to other BGP peers (§Well-known Communities) | MUST NOT | Well | **positive:** `unit/verify` [`TestForwardNoAdvertiseSkipsEveryPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L242). **positive:** `unit/verify` [`TestWellKnownNoAdvertiseRefusesEveryPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L59). **negative:** `unit/verify` [`TestWellKnownNoAdvertiseAbsentAdvertisesToEveryPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L72). **positive:** `functional/verify` [`wellknown-no-advertise-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-advertise-egress.ci#L1). **negative:** `functional/verify` [`wellknown-no-advertise-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-advertise-egress.ci#L5) | | `RFC1997-Well-3` | Routes with NO_EXPORT_SUBCONFED community MUST NOT be advertised to external BGP peers, including peers in other member ASes inside a confederation (§Well-known Communities) | MUST NOT | Well | **positive:** `unit/verify` [`TestForwardNoExportSubconfedSkipsExternalPeerOnly`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L259). **positive:** `unit/verify` [`TestWellKnownNoExportSubconfedRefusesExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L83). **negative:** `unit/verify` [`TestWellKnownNoExportSubconfedAllowsInternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L95). **positive:** `functional/verify` [`wellknown-no-advertise-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-advertise-egress.ci#L8). **negative:** `functional/verify` [`wellknown-no-advertise-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-advertise-egress.ci#L12) | | `RFC1997-Encoding-1` | Attribute length MUST be a multiple of 4 (§Encoding Rules) | MUST | Encoding | **positive:** `unit/verify` [`TestCommunitiesParse`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L44). **negative:** `unit/verify` [`TestCommunitiesParseRejectsNonMultipleOf4`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L54) | | `RFC1997-Well-4` | Well-known community operations SHALL be implemented in any community-attribute-aware BGP speaker (§Well-known Communities) | SHALL | Well | **positive:** `unit/verify` [`TestForwardWellKnownNeedsNoOperatorPolicy`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L279). **positive:** `unit/verify` [`TestWellKnownAllThreeOperationsImplemented`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L104). **negative:** `unit/verify` [`TestForwardOtherReservedCommunitiesReachExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L297). **negative:** `unit/verify` [`TestWellKnownIgnoresOtherReservedCommunities`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L122) | | `RFC1997-Aggregation-1` | When aggregating routes without ATOMIC_AGGREGATE, the resulting aggregate SHOULD have a COMMUNITIES attribute containing all communities from all aggregated routes (§Aggregation) | SHOULD | Aggregation | **positive:** no positive test. **negative:** no negative test | | `RFC1997-Operation-1` | A BGP speaker MAY use the COMMUNITIES attribute to control which routing information it accepts, prefers, or distributes (§Operation) | MAY | Operation | **positive:** no positive test. **negative:** no negative test | | `RFC1997-Operation-2` | A BGP speaker receiving a route without COMMUNITIES MAY append this attribute when propagating to peers (§Operation) | MAY | Operation | **positive:** no positive test. **negative:** no negative test | | `RFC1997-Operation-3` | A BGP speaker receiving a route with COMMUNITIES MAY modify the attribute according to local policy (§Operation) | MAY | Operation | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 1997 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC1997-Well-1`](#rfc1997-well-1) Routes with NO_EXPORT community MUST NOT be advertised outside a BGP confederation boundary (§Well-known Communities) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestForwardNoExportStillWithdrawsFromExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L364) | unit/verify | unproven | | negative | [`TestForwardWithoutNoExportReachesExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L223) | unit/verify | unproven | | negative | [`TestWellKnownNoExportAllowsInternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L48) | unit/verify | unproven | | negative | [`checkNoExportBoundary`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1056) | interop/nightly | unproven | | negative | [`wellknown-no-export-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-export-egress.ci#L7) | functional/verify | unproven | | negative | [`wellknown-no-export-withdraw-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-export-withdraw-egress.ci#L1) | functional/verify | unproven | | positive | [`TestForwardNoExportSkipsExternalPeerOnly`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L197) | unit/verify | unproven | | positive | [`TestWellKnownNoExportRefusesExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L34) | unit/verify | unproven | | positive | [`checkNoExportBoundary`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1055) | interop/nightly | revert, verified | | positive | [`wellknown-no-export-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-export-egress.ci#L1) | functional/verify | unproven | ### [`RFC1997-Well-2`](#rfc1997-well-2) Routes with NO_ADVERTISE community MUST NOT be advertised to other BGP peers (§Well-known Communities) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestWellKnownNoAdvertiseAbsentAdvertisesToEveryPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L72) | unit/verify | unproven | | negative | [`wellknown-no-advertise-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-advertise-egress.ci#L5) | functional/verify | unproven | | positive | [`TestForwardNoAdvertiseSkipsEveryPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L242) | unit/verify | unproven | | positive | [`TestWellKnownNoAdvertiseRefusesEveryPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L59) | unit/verify | unproven | | positive | [`wellknown-no-advertise-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-advertise-egress.ci#L1) | functional/verify | unproven | ### [`RFC1997-Well-3`](#rfc1997-well-3) Routes with NO_EXPORT_SUBCONFED community MUST NOT be advertised to external BGP peers, including peers in other member ASes inside a confederation (§Well-known Communities) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestWellKnownNoExportSubconfedAllowsInternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L95) | unit/verify | unproven | | negative | [`wellknown-no-advertise-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-advertise-egress.ci#L12) | functional/verify | unproven | | positive | [`TestForwardNoExportSubconfedSkipsExternalPeerOnly`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L259) | unit/verify | unproven | | positive | [`TestWellKnownNoExportSubconfedRefusesExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L83) | unit/verify | unproven | | positive | [`wellknown-no-advertise-egress.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/wellknown-no-advertise-egress.ci#L8) | functional/verify | unproven | ### [`RFC1997-Encoding-1`](#rfc1997-encoding-1) Attribute length MUST be a multiple of 4 (§Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCommunitiesParseRejectsNonMultipleOf4`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L54) | unit/verify | unproven | | positive | [`TestCommunitiesParse`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L44) | unit/verify | unproven | ### [`RFC1997-Well-4`](#rfc1997-well-4) Well-known community operations SHALL be implemented in any community-attribute-aware BGP speaker (§Well-known Communities) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestForwardOtherReservedCommunitiesReachExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L297) | unit/verify | unproven | | negative | [`TestWellKnownIgnoresOtherReservedCommunities`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L122) | unit/verify | unproven | | positive | [`TestForwardWellKnownNeedsNoOperatorPolicy`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_wellknown_test.go#L279) | unit/verify | unproven | | positive | [`TestWellKnownAllThreeOperationsImplemented`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/wellknown_test.go#L104) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase 5, rfc1997 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc1997.txt | | Source fingerprint | ea2a9bee2501b9f7 | | Record | rfc/extraction/rfc1997.json | | Mapped sentences | 4 | | Declined as scope | 1 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | The whole document | 5 | walked | The whole document. RFC 1997 carries no numbered section headings, so the derivation returns one section spanning the title block to the References, and a skip of it would hide every gated id behind a skip kind. Walked heading by heading. Status of This Memo, the Abstract and the Introduction are indicative: BGP controls distribution by prefix or AS_PATH today, and this document proposes grouping destinations so a routing decision can also be made on the identity of a group. Terms and Definitions defines a community as a group of destinations sharing a common property and says each autonomous system administrator may define which communities a destination belongs to, which names the role site front:1 binds. Examples is two operational narratives (NSFNET AUP tagging, and filtering the more-specific components of an aggregate) and directs nobody. The COMMUNITIES attribute heading is the wire-format section, written entirely in the indicative: the attribute is optional transitive of variable length, it has Type Code 8, and 'The attribute consists of a set of four octet values, each of which specify a community.' That last sentence is where RFC1997-Encoding-1 is read from, and it is declared unsourced below because no capitalised or lowercase MUST-level keyword states it. The reserved ranges and the AS-in-the-first-two-octets convention follow under 'the following presumptions may be made', and are carried by the Reserved Ranges and Community Value Structure tables of rfc/short/rfc1997.md. Well-known Communities holds four of the five sites and every gated row this summary declares: sites front:2 to front:5, all mapped below. Operation is three lowercase-'may' sentences the site scan does not see, declared unsourced below as RFC1997-Operation-1 to -3. Aggregation is one lowercase-'should' sentence, declared unsourced below as RFC1997-Aggregation-1. Applicability states the attribute may be used with BGP version 2 and all subsequent versions, which is a compatibility fact rather than a directive. Security Considerations is one sentence saying security issues are not discussed in the memo, so it states no countermeasure. Acknowledgments, Authors' Addresses and the References to RFC 1771 and RFC 1965 bind no speaker. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `front:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the autonomous system administrator who assigns community values, a role the RFC names itself in Terms and Definitions: 'Each autonomous system administrator may define which communities a destination belongs to.' The sentence sits under 'however for administrative assignment, the following presumptions may be made', and it tells that administrator to draw a value from the range keyed by their own AS number in the first two octets, with the final two octets defined by that AS (the RFC's own example is AS 690 using 0x02B20000 through 0x02B2FFFF). It directs no encoding, decoding or propagation behavior a BGP speaker performs: a speaker carries the 32-bit value the operator configured and never derives it from an AS number, and ParseCommunities in internal/core/bgp/attribute reads each 4-octet value opaquely. The value layout it describes is carried by the Community Value Structure table of rfc/short/rfc1997.md. The lowercase 'shall' is why the site scan sees it only under the prose register. | The rest of the community attribute values shall be encoded using an autonomous system number in the first two octets. | ## Superseded No document obsoletes RFC 1997, so its obligations are stated where they were written. --- ### Page: RFC 2003 - IP Encapsulation within IP https://ze-software.net/quality/rfc-compliance/rfc2003/ # RFC 2003 - IP Encapsulation within IP No row in the public ledger. Every requirement this repository extracted from RFC 2003, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 13 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 13 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 13 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 13 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 13 | of 36 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 13 | of 13 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 13 of 13 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 13 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 13 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 13 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 36 | | Gated MUST-level | 13 | | Not applicable, so out of scope | 13 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2003.md` | | Requirement shard | `rfc/requirements/rfc2003.md` | | RFC text | `rfc/full/rfc2003.txt` | ## Enrolment Enrolled: IP Encapsulation within IP (IP-in-IP, protocol 4): thirteen MUST-level requirements, all {not-applicable}. ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go setting IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go). The kernel ipip module and the VPP dataplane own the outer-header construction (Don't-Fragment copy, TTL-zero encap guard), decapsulation (inner-TTL-zero discard), source-address loop prevention, the ICMP relay/suppression rules, the Time-Exceeded-to-Host-Unreachable mapping, and path-MTU soft state. This is the same delegation rationale as the enrolled RFC 2784 and RFC 2890 (GRE). ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 2003. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 13 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **13** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (13):** [`RFC2003-3.1-1`](#rfc2003-3.1-1), [`RFC2003-3.1-2`](#rfc2003-3.1-2), [`RFC2003-3.1-3`](#rfc2003-3.1-3), [`RFC2003-3.2-1`](#rfc2003-3.2-1), [`RFC2003-3.2-2`](#rfc2003-3.2-2), [`RFC2003-4.1-1`](#rfc2003-4.1-1), [`RFC2003-4.1-2`](#rfc2003-4.1-2), [`RFC2003-4.1-3`](#rfc2003-4.1-3), [`RFC2003-4.1-4`](#rfc2003-4.1-4), [`RFC2003-4.4-1`](#rfc2003-4.4-1), [`RFC2003-4.3-1`](#rfc2003-4.3-1), [`RFC2003-4.5-1`](#rfc2003-4.5-1), [`RFC2003-5.1-1`](#rfc2003-5.1-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2003-3.1-1` | If the Don't Fragment bit is set in the inner IP header, it MUST be set in the outer IP header (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this outer-header/encapsulation obligation has no ze code path | | `RFC2003-3.1-2` | An encapsulator MUST NOT encapsulate a datagram with TTL = 0 (§3.1) | MUST NOT | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this outer-header/encapsulation obligation has no ze code path | | `RFC2003-3.1-3` | If after decapsulation the inner datagram has TTL = 0, the decapsulator MUST discard the datagram (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this decapsulation obligation has no ze code path | | `RFC2003-3.2-1` | If IP Source Address of the datagram matches the router's own IP address, the router MUST NOT tunnel the datagram (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this tunnel loop-prevention check has no ze code path | | `RFC2003-3.2-2` | If IP Source Address of the datagram matches the tunnel destination address, the router MUST NOT tunnel the datagram (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this tunnel loop-prevention check has no ze code path | | `RFC2003-4.1-1` | The encapsulator MUST relay ICMP Datagram Too Big messages to the sender of the original unencapsulated datagram (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | `RFC2003-4.1-2` | If returning Destination Unreachable for Network Unreachable and destination is not on the same network, the Code field MUST be set to 0 (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | `RFC2003-4.1-3` | ICMP Port Unreachable MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.1) | MUST NOT | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | `RFC2003-4.1-4` | ICMP Source Route Failed MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.1) | MUST NOT | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | `RFC2003-4.4-1` | ICMP Time Exceeded MUST be reported to the sender as Host Unreachable (Type 3, Code 1) (§4.4) | MUST | 4.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | `RFC2003-4.3-1` | ICMP Redirect MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.3) | MUST NOT | 4.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | `RFC2003-4.5-1` | If ICMP Parameter Problem points to a field inserted by the encapsulator, the message MUST NOT be relayed to the original sender (§4.5) | MUST NOT | 4.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | `RFC2003-5.1-1` | All encapsulator implementations MUST support Path MTU Discovery soft state within their tunnels (§5.1) | MUST | 5.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this path-MTU soft-state obligation is set by the PMtuDisc flag ze programs (tunnel_linux.go:210-214) but maintained by the kernel/VPP dataplane, not by ze | | `RFC2003-3.1-4` | If the resulting inner TTL is 0 after decrement, an ICMP Time Exceeded message SHOULD be returned to the sender (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-3.2-3` | Datagram with source address matching the router's own address SHOULD be discarded (§3.2) | SHOULD | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-3.2-4` | Datagram with source address matching the tunnel destination SHOULD be discarded (§3.2) | SHOULD | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4.1-5` | ICMP Destination Unreachable (Network Unreachable, Code 0) SHOULD be returned to the original sender (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4.1-6` | The encapsulator SHOULD relay Host Unreachable messages to the sender (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4.1-7` | When the encapsulator receives ICMP Protocol Unreachable, it SHOULD send Destination Unreachable with Code 0 or 1 to the original sender (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4.1-8` | ICMP Source Route Failed SHOULD be handled by the encapsulator itself (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4.2-1` | The encapsulator SHOULD NOT relay ICMP Source Quench messages to the original sender (§4.2) | SHOULD NOT | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4.2-2` | The encapsulator SHOULD activate congestion control mechanisms for Source Quench (§4.2) | SHOULD | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-5-1` | The encapsulator SHOULD maintain soft state about each tunnel: MTU, TTL, reachability (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-5.1-2` | The encapsulator SHOULD normally do Path MTU Discovery, setting DF in the outer header (§5.1) | SHOULD | 5.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-5.1-3` | The MTU conveyed to the original sender SHOULD be the tunnel MTU minus the encapsulating IP header size (§5.1) | SHOULD | 5.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-5.2-1` | The encapsulator SHOULD reflect congestion conditions in soft state for the tunnel (§5.2) | SHOULD | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-5.2-2` | The encapsulator SHOULD use appropriate means for controlling congestion when forwarding into the tunnel (§5.2) | SHOULD | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-5.2-3` | The encapsulator SHOULD NOT send ICMP Source Quench messages to the original sender (§5.2) | SHOULD NOT | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-6.2-1` | Host implementations receiving encapsulated datagrams SHOULD admit only those from authenticated, trusted sources or matching other security criteria (§6.2) | SHOULD | 6.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-3.1-5` | If the Don't Fragment bit is not set in the inner header, it MAY be set in the outer header (§3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-3-1` | The security options of the inner IP header MAY affect the choice of security options for the outer header (§3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-3.1-6` | New options specific to the tunnel path MAY be added to the outer header (§3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4-1` | The encapsulator MAY relay ICMP messages from within the tunnel to the original sender when enough information is available (§4) | MAY | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4.3-2` | The encapsulator MAY handle ICMP Redirect messages itself (§4.3) | MAY | 4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-4.5-2` | The encapsulator MAY relay ICMP Parameter Problem to the original sender if it points to a field from the inner datagram (§4.5) | MAY | 4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2003-5.1-4` | The encapsulator MAY keep a copy of the sent datagram to allow fragmentation and resend on Datagram Too Big (§5.1) | MAY | 5.1 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2003-3.1-1`](#rfc2003-3.1-1) If the Don't Fragment bit is set in the inner IP header, it MUST be set in the outer IP header (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this outer-header/encapsulation obligation has no ze code path | | [`RFC2003-3.1-2`](#rfc2003-3.1-2) An encapsulator MUST NOT encapsulate a datagram with TTL = 0 (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this outer-header/encapsulation obligation has no ze code path | | [`RFC2003-3.1-3`](#rfc2003-3.1-3) If after decapsulation the inner datagram has TTL = 0, the decapsulator MUST discard the datagram (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this decapsulation obligation has no ze code path | | [`RFC2003-3.2-1`](#rfc2003-3.2-1) If IP Source Address of the datagram matches the router's own IP address, the router MUST NOT tunnel the datagram (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this tunnel loop-prevention check has no ze code path | | [`RFC2003-3.2-2`](#rfc2003-3.2-2) If IP Source Address of the datagram matches the tunnel destination address, the router MUST NOT tunnel the datagram (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this tunnel loop-prevention check has no ze code path | | [`RFC2003-4.1-1`](#rfc2003-4.1-1) The encapsulator MUST relay ICMP Datagram Too Big messages to the sender of the original unencapsulated datagram (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | [`RFC2003-4.1-2`](#rfc2003-4.1-2) If returning Destination Unreachable for Network Unreachable and destination is not on the same network, the Code field MUST be set to 0 (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | [`RFC2003-4.1-3`](#rfc2003-4.1-3) ICMP Port Unreachable MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | [`RFC2003-4.1-4`](#rfc2003-4.1-4) ICMP Source Route Failed MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | [`RFC2003-4.4-1`](#rfc2003-4.4-1) ICMP Time Exceeded MUST be reported to the sender as Host Unreachable (Type 3, Code 1) (§4.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | [`RFC2003-4.3-1`](#rfc2003-4.3-1) ICMP Redirect MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | [`RFC2003-4.5-1`](#rfc2003-4.5-1) If ICMP Parameter Problem points to a field inserted by the encapsulator, the message MUST NOT be relayed to the original sender (§4.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this ICMP-handling obligation has no ze code path | | [`RFC2003-5.1-1`](#rfc2003-5.1-1) All encapsulator implementations MUST support Path MTU Discovery soft state within their tunnels (§5.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze constructs no IP-in-IP header and runs no encapsulation, decapsulation, ICMP-relay, or loop-prevention datapath: it programs only the tunnel configuration via netlink buildIptun (internal/plugins/iface/netlink/tunnel_linux.go:196, Proto IPPROTO_IPIP) and VPP ipip_add_tunnel (internal/plugins/iface/vpp/tunnel.go:113), and the kernel ipip module and VPP dataplane own the outer-header construction, decapsulation, ICMP handling, path-MTU soft state, and loop prevention, so this path-MTU soft-state obligation is set by the PMtuDisc flag ze programs (tunnel_linux.go:210-214) but maintained by the kernel/VPP dataplane, not by ze | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2003-3.1-1`](#rfc2003-3.1-1) If the Don't Fragment bit is set in the inner IP header, it MUST be set in the outer IP header (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-3.1-1, so no unit is bound to it. ### [`RFC2003-3.1-2`](#rfc2003-3.1-2) An encapsulator MUST NOT encapsulate a datagram with TTL = 0 (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-3.1-2, so no unit is bound to it. ### [`RFC2003-3.1-3`](#rfc2003-3.1-3) If after decapsulation the inner datagram has TTL = 0, the decapsulator MUST discard the datagram (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-3.1-3, so no unit is bound to it. ### [`RFC2003-3.2-1`](#rfc2003-3.2-1) If IP Source Address of the datagram matches the router's own IP address, the router MUST NOT tunnel the datagram (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-3.2-1, so no unit is bound to it. ### [`RFC2003-3.2-2`](#rfc2003-3.2-2) If IP Source Address of the datagram matches the tunnel destination address, the router MUST NOT tunnel the datagram (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-3.2-2, so no unit is bound to it. ### [`RFC2003-4.1-1`](#rfc2003-4.1-1) The encapsulator MUST relay ICMP Datagram Too Big messages to the sender of the original unencapsulated datagram (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-4.1-1, so no unit is bound to it. ### [`RFC2003-4.1-2`](#rfc2003-4.1-2) If returning Destination Unreachable for Network Unreachable and destination is not on the same network, the Code field MUST be set to 0 (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-4.1-2, so no unit is bound to it. ### [`RFC2003-4.1-3`](#rfc2003-4.1-3) ICMP Port Unreachable MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-4.1-3, so no unit is bound to it. ### [`RFC2003-4.1-4`](#rfc2003-4.1-4) ICMP Source Route Failed MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-4.1-4, so no unit is bound to it. ### [`RFC2003-4.4-1`](#rfc2003-4.4-1) ICMP Time Exceeded MUST be reported to the sender as Host Unreachable (Type 3, Code 1) (§4.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-4.4-1, so no unit is bound to it. ### [`RFC2003-4.3-1`](#rfc2003-4.3-1) ICMP Redirect MUST NOT be relayed to the sender of the original unencapsulated datagram (§4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-4.3-1, so no unit is bound to it. ### [`RFC2003-4.5-1`](#rfc2003-4.5-1) If ICMP Parameter Problem points to a field inserted by the encapsulator, the message MUST NOT be relayed to the original sender (§4.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-4.5-1, so no unit is bound to it. ### [`RFC2003-5.1-1`](#rfc2003-5.1-1) All encapsulator implementations MUST support Path MTU Discovery soft state within their tunnels (§5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2003-5.1-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2003, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2003, so its obligations are stated where they were written. --- ### Page: RFC 2131 - Dynamic Host Configuration Protocol https://ze-software.net/quality/rfc-compliance/rfc2131/ # RFC 2131 - Dynamic Host Configuration Protocol Partial. Every requirement this repository extracted from RFC 2131, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 23.4% | 15 of 64 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 6.2% | 4 of 64 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 64 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 34 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 64 | of 101 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 27 | of 64 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 42.2% | 27 of 64 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 64 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 64 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 28.1% | 18 of 64 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 64 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 101 | | Gated MUST-level | 64 | | Not applicable, so out of scope | 27 | | Declared gaps | 18 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 34 | | Tagged units | 34 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2131.md` | | Requirement shard | `rfc/requirements/rfc2131.md` | | RFC text | `rfc/full/rfc2131.txt` | ## Enrolment Enrolled: Dynamic Host Configuration Protocol ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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 (`internal/plugins/iface/dhcp/`) 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 [`rfc/short/rfc2131.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2131.md). **What the ledger says remains** Eighteen MUST gaps, each annotated in [`rfc/short/rfc2131.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2131.md). - **Server:** DHCPNAK delivery ([`RFC2131-4.3.2-1`](#rfc2131-4.3.2-1) unicasts to a non-zero ciaddr instead of broadcasting, [`RFC2131-4.3.2-2`](#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`](#rfc2131-4.3.2-3)); a declined address is handed back to the client that declined it ([`RFC2131-4.3.3-1`](#rfc2131-4.3.3-1)); only one address per subnet is accepted as the server identifier ([`RFC2131-4.1-1`](#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`](#rfc2131-4.1-2)); the client identifier option 61 is never read, so every client is keyed by chaddr ([`RFC2131-4.2-1`](#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`](#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`](#rfc2131-4.3.1-7)). - **Client:** option 61 is sent at acquisition and omitted on every renewal ([`RFC2131-2-2`](#rfc2131-2-2)); the configured client-id is emitted with no uniqueness check ([`RFC2131-2-1`](#rfc2131-2-1)); an address found in use is kept rather than declined, and no DHCPDECLINE path exists ([`RFC2131-3.1-7`](#rfc2131-3.1-7)); retransmission doubles the timeout with no randomization ([`RFC2131-4.1-8`](#rfc2131-4.1-8)); the renewal is broadcast rather than unicast to the server identifier ([`RFC2131-4.1-10`](#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`](#rfc2131-4.4.5-1)); and a renewal ACK carrying a different yiaddr leaves the previous address installed ([`RFC2131-4.4.5-5`](#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). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 15 | one part of the gated population | | Annotated instead of tested | 49 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **64** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (15):** [`RFC2131-4.3-4`](#rfc2131-4.3-4), [`RFC2131-4.3-5`](#rfc2131-4.3-5), [`RFC2131-4.3-6`](#rfc2131-4.3-6), [`RFC2131-4.3-7`](#rfc2131-4.3-7), [`RFC2131-4.3-8`](#rfc2131-4.3-8), [`RFC2131-4.3-9`](#rfc2131-4.3-9), [`RFC2131-4.2-2`](#rfc2131-4.2-2), [`RFC2131-3-1`](#rfc2131-3-1), [`RFC2131-4.1-3`](#rfc2131-4.1-3), [`RFC2131-4.1-6`](#rfc2131-4.1-6), [`RFC2131-2-3`](#rfc2131-2-3), [`RFC2131-4.4.5-2`](#rfc2131-4.4.5-2), [`RFC2131-4.4.5-3`](#rfc2131-4.4.5-3), [`RFC2131-4.3.1-4`](#rfc2131-4.3.1-4), [`RFC2131-4.3.1-6`](#rfc2131-4.3.1-6) **Annotated instead of tested (49):** [`RFC2131-4.3-1`](#rfc2131-4.3-1), [`RFC2131-4.3-2`](#rfc2131-4.3-2), [`RFC2131-4.3-3`](#rfc2131-4.3-3), [`RFC2131-4.3.5-1`](#rfc2131-4.3.5-1), [`RFC2131-4.3.2-1`](#rfc2131-4.3.2-1), [`RFC2131-4.3.2-2`](#rfc2131-4.3.2-2), [`RFC2131-4.3.2-3`](#rfc2131-4.3.2-3), [`RFC2131-4.3.3-1`](#rfc2131-4.3.3-1), [`RFC2131-4.1-1`](#rfc2131-4.1-1), [`RFC2131-4.1-2`](#rfc2131-4.1-2), [`RFC2131-4.2-1`](#rfc2131-4.2-1), [`RFC2131-2-1`](#rfc2131-2-1), [`RFC2131-2-2`](#rfc2131-2-2), [`RFC2131-3.1-1`](#rfc2131-3.1-1), [`RFC2131-3.1-2`](#rfc2131-3.1-2), [`RFC2131-3.1-3`](#rfc2131-3.1-3), [`RFC2131-3.1-4`](#rfc2131-3.1-4), [`RFC2131-3.1-5`](#rfc2131-3.1-5), [`RFC2131-3.2-1`](#rfc2131-3.2-1), [`RFC2131-3.1-6`](#rfc2131-3.1-6), [`RFC2131-3.1-7`](#rfc2131-3.1-7), [`RFC2131-4.4.5-1`](#rfc2131-4.4.5-1), [`RFC2131-4.1-4`](#rfc2131-4.1-4), [`RFC2131-4.1-5`](#rfc2131-4.1-5), [`RFC2131-4.1-7`](#rfc2131-4.1-7), [`RFC2131-4.1-8`](#rfc2131-4.1-8), [`RFC2131-4.1-9`](#rfc2131-4.1-9), [`RFC2131-4.1-10`](#rfc2131-4.1-10), [`RFC2131-3.4-1`](#rfc2131-3.4-1), [`RFC2131-2-4`](#rfc2131-2-4), [`RFC2131-3.5-1`](#rfc2131-3.5-1), [`RFC2131-3.1-8`](#rfc2131-3.1-8), [`RFC2131-4.3.1-1`](#rfc2131-4.3.1-1), [`RFC2131-4.3.1-2`](#rfc2131-4.3.1-2), [`RFC2131-4.3.1-3`](#rfc2131-4.3.1-3), [`RFC2131-4.3.1-5`](#rfc2131-4.3.1-5), [`RFC2131-4.3.1-7`](#rfc2131-4.3.1-7), [`RFC2131-4.4.1-1`](#rfc2131-4.4.1-1), [`RFC2131-4.4.2-1`](#rfc2131-4.4.2-1), [`RFC2131-4.4.2-2`](#rfc2131-4.4.2-2), [`RFC2131-4.4.3-1`](#rfc2131-4.4.3-1), [`RFC2131-4.3.2-5`](#rfc2131-4.3.2-5), [`RFC2131-4.4.5-5`](#rfc2131-4.4.5-5), [`RFC2131-4.4.5-6`](#rfc2131-4.4.5-6), [`RFC2131-4.4.5-7`](#rfc2131-4.4.5-7), [`RFC2131-3.2-3`](#rfc2131-3.2-3), [`RFC2131-4.4-1`](#rfc2131-4.4-1), [`RFC2131-4.4-2`](#rfc2131-4.4-2), [`RFC2131-4.4-3`](#rfc2131-4.4-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2131-4.3-1` | Server identifier MUST be included in DHCPOFFER, DHCPACK, and DHCPNAK (§4.3, Table 3) | MUST | 4.3 | **positive:** `unit/verify` [`TestServerIdentifierInEveryReply`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L169). **negative:** no negative test. **{single-polarity}:** every reply path appends option 54 unconditionally -- buildReply at internal/plugins/dhcpserver/handler.go:246 and buildNak at handler.go:349 -- so no input suppresses the server identifier and there is no omission to assert negatively | | `RFC2131-4.3-2` | IP address lease time MUST be included in DHCPOFFER (§4.3, Table 3) | MUST | 4.3 | **positive:** `unit/verify` [`TestLeaseTimeInOfferAndAck`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L194). **negative:** no negative test. **{single-polarity}:** the lease time is appended to every OFFER and ACK unconditionally (buildReply internal/plugins/dhcpserver/handler.go:271-273, inside the OFFER/ACK branch opened at handler.go:248), so no input yields a DHCPOFFER without option 51 to assert negatively | | `RFC2131-4.3-3` | IP address lease time MUST be included in DHCPACK for DHCPREQUEST (§4.3, Table 3) | MUST | 4.3 | **positive:** `unit/verify` [`TestLeaseTimeInOfferAndAck`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L198). **negative:** no negative test. **{single-polarity}:** the lease time is appended to every OFFER and ACK unconditionally (buildReply internal/plugins/dhcpserver/handler.go:271-273, inside the OFFER/ACK branch opened at handler.go:248), so no DHCPREQUEST yields a DHCPACK without option 51 to assert negatively | | `RFC2131-4.3-4` | IP address lease time MUST NOT be included in DHCPNAK (§4.3, Table 3) | MUST NOT | 4.3 | **positive:** `unit/verify` [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L245). **negative:** `unit/verify` [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L333) | | `RFC2131-4.3-5` | Requested IP address option MUST NOT be included in DHCPOFFER, DHCPACK, or DHCPNAK (§4.3, Table 3) | MUST NOT | 4.3 | **positive:** `unit/verify` [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L224). **negative:** `unit/verify` [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L312) | | `RFC2131-4.3-6` | Client identifier MUST NOT be included in DHCPOFFER or DHCPACK (§4.3, Table 3) | MUST NOT | 4.3 | **positive:** `unit/verify` [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L239). **negative:** `unit/verify` [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L327) | | `RFC2131-4.3-7` | Parameter request list MUST NOT be included in DHCPOFFER, DHCPACK, or DHCPNAK (§4.3, Table 3) | MUST NOT | 4.3 | **positive:** `unit/verify` [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L228). **negative:** `unit/verify` [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L316) | | `RFC2131-4.3-8` | Maximum message size MUST NOT be included in DHCPOFFER, DHCPACK, or DHCPNAK (§4.3, Table 3) | MUST NOT | 4.3 | **positive:** `unit/verify` [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L232). **negative:** `unit/verify` [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L320) | | `RFC2131-4.3-9` | Options other than message MUST NOT be included in DHCPNAK (§4.3, Table 3) | MUST NOT | 4.3 | **positive:** `unit/verify` [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L249). **negative:** `unit/verify` [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L337) | | `RFC2131-4.3.5-1` | Server MUST NOT send lease expiration time for DHCPINFORM (§4.3.5) | MUST NOT | 4.3.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze answers no DHCPINFORM at all -- the message-type switch in handle (internal/plugins/dhcpserver/handler.go:121-134) dispatches DISCOVER, REQUEST, RELEASE and DECLINE, and every other type including msgInform (handler.go:37) falls to the default branch that returns nil, so ze builds no DHCPINFORM response at all | | `RFC2131-4.3.2-1` | When giaddr is 0x0, server MUST broadcast DHCPNAK to 0xFFFFFFFF (§4.3.2, §3.2) | MUST | 4.3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** with giaddr zero the delivery path unicasts a DHCPNAK to a non-zero ciaddr instead of broadcasting -- responseAddr tests giaddr, then ciaddr, before it ever reaches the broadcast decision (internal/plugins/dhcpserver/register.go:275-298) and never special-cases the NAK message type, so a REQUEST whose ciaddr is on-subnet and whose requested address is off-subnet is NAKed to ciaddr:68 | | `RFC2131-4.3.2-2` | When giaddr is set in DHCPNAK, server MUST set the broadcast bit (§4.3.2) | MUST | 4.3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** buildNak copies the client's flags word verbatim (internal/plugins/dhcpserver/handler.go:339) and never forces the BROADCAST bit, so a DHCPNAK relayed through a non-zero giaddr leaves the server with the broadcast bit clear whenever the client left it clear | | `RFC2131-4.3.2-3` | If server has no record of client in INIT-REBOOT, server MUST remain silent (§4.3.2) | MUST | 4.3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the INIT-REBOOT branch commits a binding for any in-subnet requested address without consulting the lease table (handleRequest internal/plugins/dhcpserver/handler.go:178-183 calls commitBinding at handler.go:194), so a client the server holds no record of draws a DHCPACK rather than silence | | `RFC2131-4.3.3-1` | Server MUST mark the network address as unavailable on DHCPDECLINE (§4.3.3) | MUST | 4.3.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** markUnavailable sets the pool bit and the static set for the declined address (internal/plugins/dhcpserver/pool.go:144-163), but the declining client's MAC-to-address cache entry survives -- pool.release returns early for a staticSet address (pool.go:174-177) -- so pool.allocate hands that same declined address back to that client on its next DISCOVER (pool.go:68-72) | | `RFC2131-4.1-1` | A server with multiple network addresses MUST be prepared to accept any of its addresses as identifying that server (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** each handler accepts exactly one address as its server identifier -- handleRequest discards a REQUEST whose option 54 differs from its own serverIP (internal/plugins/dhcpserver/handler.go:167-169) -- and that address is the single per-subnet value derived at register.go:93-101, so a REQUEST naming another of the server's own addresses is silently dropped | | `RFC2131-4.1-2` | Server MUST choose a server identifier address reachable from the client (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** with no default-router configured for a subnet the server identifier is not an address the server answers on -- register.go:93 prefers sub.DefaultRouter, which parseSubnet leaves unset when the leaf is absent (internal/plugins/dhcpserver/config.go:189-195), and falls back to the first pool address (internal/plugins/dhcpserver/register.go:95-97), which the server itself hands out to a client, or to the subnet network address (register.go:99-101), which is no host at all; buildReply then emits that value as option 54 (internal/plugins/dhcpserver/handler.go:246), so a client unicasting to it reaches nothing | | `RFC2131-4.2-1` | If client supplies a client identifier, server MUST use it to identify the client (§4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze never reads the client identifier -- option 61 is absent from the option codes at internal/plugins/dhcpserver/handler.go:41-61 and no parse call asks for it -- and keys every binding on the hardware address instead (extractMAC handler.go:456, leaseTable byMAC lease.go:23, pool.macToAddr pool.go:22), so a client that supplies a client identifier is still identified by chaddr | | `RFC2131-4.2-2` | If client does not provide a client identifier, server MUST use chaddr to identify the client (§4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestChaddrIdentifiesClientWithoutClientIdentifier`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L366). **negative:** `unit/verify` [`TestUnidentifiableClientRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L390) | | `RFC2131-2-1` | Client identifier MUST be unique within the subnet (§2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's client emits the operator-configured client identifier verbatim and nothing derives or checks it -- v4RequestModifiers appends OptClientIdentifier([]byte(c.config.ClientID)) (internal/plugins/iface/dhcp/dhcp_v4_linux.go:304-306) from the config leaf read at internal/component/iface/config.go:1267-1269 and carried at internal/component/iface/register.go:891, and no validator constrains the value, so two units on one subnet configured with the same client-id emit colliding identifiers | | `RFC2131-2-2` | If client uses a client identifier in one message, it MUST use the same identifier in all subsequent messages (§2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's client sends option 61 at acquisition and omits it on every renewal -- the DORA call passes v4RequestModifiers() (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, built at :299-308) while renewV4 calls client.Renew(ctx, lease) with no modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:143), and the renewal REQUEST is built by NewRenewFromAck (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290) whose WithReply copies only opcode, HW type, xid, chaddr and flags (vendor/github.com/insomniacslk/dhcp/dhcpv4/modifiers.go:56-68) and never an option, so the identifier the server bound at acquisition is absent from every later message | | `RFC2131-3.1-1` | DHCPREQUEST in SELECTING state MUST include the server identifier option (§3.1, Table 4) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze hands SELECTING message construction to the vendored library -- runV4 calls client.Request (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48), which builds the REQUEST in NewRequestFromOffer (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:256-270) where the server identifier is copied from the OFFER at dhcpv4.go:263; ze contributes only hostname and client-id modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:299-308) and authors none of those bytes | | `RFC2131-3.1-2` | DHCPREQUEST in SELECTING state MUST include the requested IP address option (§3.1, Table 4) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze hands SELECTING message construction to the vendored library -- runV4 calls client.Request (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48), which builds the REQUEST in NewRequestFromOffer (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:256-270) where the requested IP address option is set from the OFFER's yiaddr at dhcpv4.go:261; ze contributes only hostname and client-id modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:299-308) and authors none of those bytes | | `RFC2131-3.1-3` | DHCPREQUEST in INIT-REBOOT state MUST include the requested IP address option (§3.1, Table 4) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client never enters INIT-REBOOT -- every acquisition is a full DORA (client.Request internal/plugins/iface/dhcp/dhcp_v4_linux.go:48) and every renewal is a RENEWING request built from the stored ACK (client.Renew internal/plugins/iface/dhcp/dhcp_v4_linux.go:143); no ze code path caches an address to reboot with, so ze produces no INIT-REBOOT REQUEST | | `RFC2131-3.1-4` | DHCPREQUEST in INIT-REBOOT state MUST NOT include the server identifier option (§3.1, Table 4) | MUST NOT | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client never enters INIT-REBOOT -- every acquisition is a full DORA (client.Request internal/plugins/iface/dhcp/dhcp_v4_linux.go:48) and every renewal is a RENEWING request built from the stored ACK (client.Renew internal/plugins/iface/dhcp/dhcp_v4_linux.go:143); no ze code path caches an address to reboot with, so ze produces no INIT-REBOOT REQUEST | | `RFC2131-3.1-5` | DHCPREQUEST in RENEWING/REBINDING MUST NOT include server identifier or requested IP address (§3.1, Table 4) | MUST NOT | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the RENEWING/REBINDING REQUEST is constructed entirely inside the vendored library -- renewV4 calls client.Renew with no modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:143) and NewRenewFromAck sets message type, ciaddr, the broadcast flag and a requested-options list only (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290), adding neither a server identifier nor a requested IP address; ze authors none of those bytes | | `RFC2131-3.2-1` | Client in INIT-REBOOT MUST NOT fill in ciaddr (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client never enters INIT-REBOOT -- every acquisition is a full DORA (client.Request internal/plugins/iface/dhcp/dhcp_v4_linux.go:48) and every renewal is a RENEWING request built from the stored ACK (client.Renew internal/plugins/iface/dhcp/dhcp_v4_linux.go:143); no ze code path caches an address to reboot with, so ze produces no INIT-REBOOT REQUEST | | `RFC2131-3.1-6` | Client MUST use same client identifier in DHCPRELEASE as used to obtain the lease (§3.1, §3.2) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client sends no DHCPRELEASE, so no ze path builds the message whose fields this governs -- runV4 ends a lease by removing the address locally (internal/plugins/iface/dhcp/dhcp_v4_linux.go:120, 211-235), and nclient4's Release (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/lease.go:26) is called nowhere in ze | | `RFC2131-3.1-7` | If client detects allocated address is already in use, it MUST send DHCPDECLINE (§3.1, §3.2) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's client installs the ACKed address without checking it is free and has no DHCPDECLINE path -- handleV4Lease goes straight from the ACK to ReplaceAddressWithLifetime (internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) with no probe of the address, and neither ze nor the vendored nclient4 client contains a DECLINE producer, so an address found in use is kept rather than declined | | `RFC2131-4.4.5-1` | Client MUST stop network processing when lease expires (§4.4.5) | MUST | 4.4.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze tears the lease down late -- runV4 budgets fixed sleeps of lease/2, 3*lease/8 and lease/8 (internal/plugins/iface/dhcp/dhcp_v4_linux.go:80-118) and calls renewV4 between them, so the blocking renewal attempts add to that budget: each failed renewal spends the vendored retry schedule of 5 seconds doubling over 3 tries (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:32, 35, retryFn 649-669), leaving the lease's default route (internal/plugins/iface/dhcp/dhcp_v4_linux.go:190) installed for around 70 seconds past expiry before removeV4Addr runs (internal/plugins/iface/dhcp/dhcp_v4_linux.go:120, 228-235); only the address itself leaves on time, because the kernel holds the lease duration as its valid lifetime (internal/plugins/iface/dhcp/dhcp_v4_linux.go:180, internal/plugins/iface/netlink/manage_linux.go:268-286) | | `RFC2131-3-1` | Options field MUST start with magic cookie 0x63825363 (§3) | MUST | 3 | **positive:** `unit/verify` [`TestMagicCookieRequiredAndEmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L423). **negative:** `unit/verify` [`TestMagicCookieRequiredAndEmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L431) | | `RFC2131-4.1-3` | Options field MUST end with end option (255) (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestReplyOptionsTerminatedByEnd`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L453). **negative:** `unit/verify` [`TestReplyOptionsTerminatedByEnd`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L471) | | `RFC2131-4.1-4` | If option overload is used, it MUST appear in the options field (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze neither emits nor honors option overload (52) -- buildReply and buildNak leave sname and file zeroed (internal/plugins/dhcpserver/handler.go:220-290 and 333-354) and every option reader starts at pkt[240:], the options field alone (parseMsgType handler.go:367, parseOptionAddr handler.go:386, parseOptionBytes handler.go:471), so no option ever lives in the sname or file field | | `RFC2131-4.1-5` | Options in sname/file fields MUST begin with the first octet, be terminated by end option, and be followed by pad (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze neither emits nor honors option overload (52) -- buildReply and buildNak leave sname and file zeroed (internal/plugins/dhcpserver/handler.go:220-290 and 333-354) and every option reader starts at pkt[240:], the options field alone (parseMsgType handler.go:367, parseOptionAddr handler.go:386, parseOptionBytes handler.go:471), so no option ever lives in the sname or file field | | `RFC2131-4.1-6` | Each option MUST be entirely contained in its field (options/sname/file) (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestOptionsContainedWithinTheirField`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L492). **negative:** `unit/verify` [`TestOptionsContainedWithinTheirField`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L515) | | `RFC2131-4.1-7` | Options field MUST be interpreted first, then file, then sname (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze neither emits nor honors option overload (52) -- buildReply and buildNak leave sname and file zeroed (internal/plugins/dhcpserver/handler.go:220-290 and 333-354) and every option reader starts at pkt[240:], the options field alone (parseMsgType handler.go:367, parseOptionAddr handler.go:386, parseOptionBytes handler.go:471), so no option ever lives in the sname or file field; ze reads options from the options field only, so it holds no second option stream to order against it | | `RFC2131-4.1-8` | Client MUST adopt a retransmission strategy with randomized exponential backoff (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's client retransmits on a doubling timeout with no randomization -- runV4 and renewV4 build the client with no ClientOpt (internal/plugins/iface/dhcp/dhcp_v4_linux.go:37, 128), so it runs the vendored defaults of a 5 second timeout over 3 tries (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:32, 35, 183) through retryFn, which only doubles the timeout between tries (client.go:649-669) | | `RFC2131-4.1-9` | Client MUST choose xid values to minimize collision with other clients (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the transaction ID is drawn inside the vendored library -- every message ze's client sends is built through dhcpv4.New (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:144-150), which takes the xid from crypto/rand via GenerateTransactionID (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:121-139); ze calls only client.Request and client.Renew (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, 143) and authors no xid | | `RFC2131-4.1-10` | Client MUST use the IP address from the server identifier option for unicast requests (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's renewal is not unicast to the server identifier -- renewV4 creates the client with nclient4.New(c.ifaceName) and no WithServerAddr (internal/plugins/iface/dhcp/dhcp_v4_linux.go:128), so serverAddr keeps the library default of 255.255.255.255:67 (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:50-53, assigned at client.go:183) and Renew sends the RENEWING REQUEST to that broadcast address (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/lease.go:60) instead of to the server identifier the ACK carried | | `RFC2131-3.4-1` | Server MUST NOT check for existing lease when responding to DHCPINFORM (§3.4) | MUST NOT | 3.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze answers no DHCPINFORM at all -- the message-type switch in handle (internal/plugins/dhcpserver/handler.go:121-134) dispatches DISCOVER, REQUEST, RELEASE and DECLINE, and every other type including msgInform (handler.go:37) falls to the default branch that returns nil, so ze builds no DHCPINFORM response at all, so no response path exists that could consult an existing lease | | `RFC2131-2-3` | Flags field bits 1-15 MUST be set to zero by clients and ignored by servers/relay agents (§2) | MUST | 2 | **positive:** `unit/verify` [`TestFlagsReservedBitsIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L549). **negative:** `unit/verify` [`TestFlagsReservedBitsIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L560) | | `RFC2131-2-4` | Client MUST be prepared to receive DHCP messages with options field of at least 312 octets (§2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze reads no DHCP bytes itself -- the receive path is the vendored library's receiveLoop, which reads each datagram into a MaxMessageSize (1500 octet) buffer before decoding (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:256-271, MaxMessageSize at client.go:38), and ze only consumes the decoded result of client.Request and client.Renew (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, 143) | | `RFC2131-3.5-1` | If client includes a parameter request list in DHCPDISCOVER, it MUST include that list in subsequent DHCPREQUEST messages (§3.5) | MUST | 3.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the parameter request list is written by the vendored library, the same list in both messages -- NewDiscovery (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:198-209) and NewRequestFromOffer (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:256-270) each add the identical WithRequestedOptions set, and ze passes only hostname and client-id modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:299-308), so it authors no request list | | `RFC2131-4.4.5-2` | T1 MUST be earlier than T2 (§4.4.5) | MUST | 4.4.5 | **positive:** `unit/verify` [`TestRenewalTimersOrderedWithinLease`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L603). **negative:** `unit/verify` [`TestShortLeaseTimeRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L630) | | `RFC2131-4.4.5-3` | T2 MUST be earlier than lease expiry (§4.4.5) | MUST | 4.4.5 | **positive:** `unit/verify` [`TestRenewalTimersOrderedWithinLease`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L607). **negative:** `unit/verify` [`TestShortLeaseTimeRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L635) | | `RFC2131-3.1-8` | DHCPREQUEST in SELECTING MUST use the same secs field and broadcast address as the original DHCPDISCOVER (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the secs field and the broadcast flag of the SELECTING REQUEST are the vendored library's -- it leaves secs zero in both the DISCOVER and the REQUEST (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:164-180 sets no secs) and copies the flags word across with WithReply (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:258, vendor/github.com/insomniacslk/dhcp/dhcpv4/modifiers.go:56-68), while ze's DORA call and its modifiers touch neither (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, 299-308) | | `RFC2131-3.1-9` | Server SHOULD probe offered address (e.g., ICMP Echo) before allocating (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.3.4-1` | Server SHOULD retain released client parameters for possible reuse (§4.3.4) | SHOULD | 4.3.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.1-10` | Server SHOULD mark offered address as available if no DHCPREQUEST received (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.3.2-4` | Server SHOULD respond with DHCPNAK to wrong-subnet INIT-REBOOT client (§4.3.2, §3.2) | SHOULD | 4.3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.3.3-2` | Server SHOULD notify administrator on DHCPDECLINE (§4.3.3) | SHOULD | 4.3.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.3.5-2` | DHCPINFORM response SHOULD NOT fill in yiaddr (§4.3.5) | SHOULD NOT | 4.3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.4-2` | Server SHOULD unicast DHCPACK for DHCPINFORM to ciaddr (§3.4) | SHOULD | 3.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.4-3` | Server SHOULD check network address in DHCPINFORM for consistency (§3.4) | SHOULD | 3.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.1-11` | DHCPACK parameters SHOULD NOT conflict with earlier DHCPOFFER (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.1-12` | Server SHOULD NOT check offered network address again at DHCPREQUEST time (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.1-13` | Client SHOULD perform final check on parameters (e.g., ARP) after DHCPACK (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.1-14` | Client SHOULD wait minimum 10 seconds before restarting after DHCPDECLINE (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.1-15` | Client SHOULD notify the user when initialization fails and restarts (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.5-2` | Client SHOULD include maximum DHCP message size option (§3.5) | SHOULD | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.7-1` | Client SHOULD use DHCP to reacquire/verify IP address at boot time or after disconnection (§3.7) | SHOULD | 3.7 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-2-5` | TCP/IP software SHOULD accept and forward IP packets to the IP layer before address is configured (§2) | SHOULD | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.1-11` | Client unable to receive unicast SHOULD set BROADCAST bit (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.1-12` | Server/relay SHOULD examine BROADCAST bit and broadcast when set (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.4.5-4` | T1 and T2 SHOULD include random fuzz to avoid synchronization (§4.4.5) | SHOULD | 4.4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.1-13` | Retransmission delay SHOULD start at 4 seconds with +/-1 randomization and double up to 64 seconds (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-2.2-1` | Allocating server SHOULD probe reused address before allocating (§2.2) | SHOULD | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-2.2-2` | Client SHOULD probe newly received address (e.g., with ARP) (§2.2) | SHOULD | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.1-16` | DHCPDISCOVER MAY include options suggesting address and lease duration (§3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.1-17` | Server MAY choose to mark offered addresses as unavailable (§3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.2-3` | Server MAY refuse to allocate even when addresses are available (§4.2) | MAY | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.1-14` | Server MAY use any of its network addresses in outgoing DHCP messages (§4.1) | MAY | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.2-2` | Client MAY choose to use previously allocated address for remainder of unexpired lease if no response (§3.2) | MAY | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.3.1-1` | Server MUST select configuration parameters by applying rules in specified order (§4.3.1) | MUST | 4.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze applies no parameter-selection procedure -- buildReply emits one fixed option set in one fixed order (internal/plugins/dhcpserver/handler.go:245-285) and the parameter request list is never parsed (optParamReqList is defined at handler.go:52 and read nowhere in production), so the ordered rules of Section 4.3.1 govern no ze code path | | `RFC2131-4.3.1-2` | If server has an explicitly configured default value for a requested parameter, it MUST include that value (§4.3.1) | MUST | 4.3.1 | **positive:** `unit/verify` [`TestUnconfiguredParametersOmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L697). **negative:** no negative test. **{single-polarity}:** ze emits its configured values unconditionally (buildReply internal/plugins/dhcpserver/handler.go:245-285), so a configured value is present whether or not the client asked for it and no input suppresses one to assert negatively | | `RFC2131-4.3.1-3` | If server recognizes a parameter defined in the Host Requirements Document, it MUST include the default value (§4.3.1) | MUST | 4.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze returns only the values its own subnet configuration holds (buildReply internal/plugins/dhcpserver/handler.go:245-285); it reads no parameter request list (optParamReqList handler.go:52 is parsed nowhere in production) and carries no Host Requirements defaults, so a recognized parameter the client requests draws no default value | | `RFC2131-4.3.1-4` | If server has no value for a requested parameter, it MUST NOT return a value for that parameter (§4.3.1) | MUST NOT | 4.3.1 | **positive:** `unit/verify` [`TestUnconfiguredParametersOmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L668). **negative:** `unit/verify` [`TestUnconfiguredParametersOmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L682) | | `RFC2131-4.3.1-5` | Server MUST supply as many of the requested parameters as possible and MUST omit any it cannot provide (§4.3.1) | MUST | 4.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the parameter request list is never parsed (optParamReqList internal/plugins/dhcpserver/handler.go:52 is read nowhere in production), so ze supplies its fixed configured option set (buildReply handler.go:245-285) rather than as many of the client's requested parameters as it can | | `RFC2131-4.3.1-6` | Server MUST include each requested parameter only once unless explicitly allowed (§4.3.1) | MUST | 4.3.1 | **positive:** `unit/verify` [`TestEachParameterEmittedOnce`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L723). **negative:** `unit/verify` [`TestEachParameterEmittedOnce`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L744) | | `RFC2131-4.3.1-7` | Vendor class identifier parameters MUST be identified by an exact match between client and server class identifiers (§4.3.1) | MUST | 4.3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze identifies the vendor class by a ten-octet prefix comparison against the string PXEClient: (isPXEClient internal/plugins/dhcpserver/handler.go:493-496) rather than by an exact match against a configured class identifier, and that prefix match is what selects the class-specific options appended at handler.go:292-330 | | `RFC2131-4.4.1-1` | Client MUST include its hardware address in the 'chaddr' field if necessary for DHCP reply delivery (§4.4.1) | MUST | 4.4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** chaddr is filled by the vendored library from the bound interface -- nclient4.New resolves the interface MAC (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:199-209) and NewDiscovery/NewRequestFromOffer set it with WithHwAddr (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:198-209, 256-270); ze only names the interface to bind (internal/plugins/iface/dhcp/dhcp_v4_linux.go:37) | | `RFC2131-4.4.2-1` | Client sending DHCPDECLINE MUST insert its known network address as 'requested IP address' option (§4.4.2) | MUST | 4.4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client sends no DHCPDECLINE, so no ze path builds the message whose fields this governs -- runV4 installs the ACKed address directly (handleV4Lease internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) and neither ze nor the vendored nclient4 client holds a DECLINE producer; that absence is itself recorded as the gap on RFC2131-3.1-7 | | `RFC2131-4.4.2-2` | Client sending DHCPDECLINE/DHCPRELEASE of requested IP MUST NOT include 'server identifier' in INIT-REBOOT (§4.4.2) | MUST NOT | 4.4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client sends no DHCPDECLINE, so no ze path builds the message whose fields this governs -- runV4 installs the ACKed address directly (handleV4Lease internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) and neither ze nor the vendored nclient4 client holds a DECLINE producer; that absence is itself recorded as the gap on RFC2131-3.1-7 | | `RFC2131-4.4.3-1` | DHCPINFORM messages MUST be directed to the 'DHCP server' UDP port (§4.4.3) | MUST | 4.4.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's client sends no DHCPINFORM -- nclient4 exposes Inform (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:483-498) and ze calls it nowhere; runV4 and renewV4 use only Request and Renew (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, 143) | | `RFC2131-4.3.2-5` | DHCPREQUEST in REBINDING state MUST be broadcast to 0xFFFFFFFF (§4.3.2) | MUST | 4.3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's T2 attempt reuses the same vendored Renew call as its T1 attempt (internal/plugins/iface/dhcp/dhcp_v4_linux.go:103 and 143), so both the message and its destination are the library's: NewRenewFromAck builds it (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290) and it goes to the default serverAddr of 255.255.255.255:67 (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:50-53, 183, used at nclient4/lease.go:60); ze chooses neither | | `RFC2131-4.4.5-5` | Client given a new network address after lease expiry MUST NOT continue using the previous address (§4.4.5) | MUST NOT | 4.4.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** a renewal that returns a different yiaddr leaves the previous address in use -- renewV4 hands the new ACK to handleV4Lease (internal/plugins/iface/dhcp/dhcp_v4_linux.go:154), which installs the new address with ReplaceAddressWithLifetime (internal/plugins/iface/dhcp/dhcp_v4_linux.go:180), a per-address replace that touches no other address (internal/plugins/iface/netlink/manage_linux.go:268-286), and runV4 then overwrites its ack reference (internal/plugins/iface/dhcp/dhcp_v4_linux.go:87-88) so the previous address is never passed to removeV4Addr; both addresses stay configured on the interface | | `RFC2131-4.4.5-6` | Client in RENEWING state MUST NOT include 'server identifier' in the DHCPREQUEST (§4.4.5) | MUST NOT | 4.4.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the RENEWING/REBINDING REQUEST is constructed entirely inside the vendored library -- renewV4 calls client.Renew with no modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:143) and NewRenewFromAck sets message type, ciaddr, the broadcast flag and a requested-options list only (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290), adding neither a server identifier nor a requested IP address; ze authors none of those bytes | | `RFC2131-4.4.5-7` | Client in REBINDING state MUST NOT include 'server identifier' in the DHCPREQUEST (§4.4.5) | MUST NOT | 4.4.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the RENEWING/REBINDING REQUEST is constructed entirely inside the vendored library -- renewV4 calls client.Renew with no modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:143) and NewRenewFromAck sets message type, ciaddr, the broadcast flag and a requested-options list only (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290), adding neither a server identifier nor a requested IP address; ze authors none of those bytes | | `RFC2131-3.2-3` | Client MUST NOT fill in 'ciaddr' when it has not received its network address (INIT-REBOOT) (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client never enters INIT-REBOOT -- every acquisition is a full DORA (client.Request internal/plugins/iface/dhcp/dhcp_v4_linux.go:48) and every renewal is a RENEWING request built from the stored ACK (client.Renew internal/plugins/iface/dhcp/dhcp_v4_linux.go:143); no ze code path caches an address to reboot with, so ze produces no INIT-REBOOT REQUEST | | `RFC2131-4.4-1` | DHCPDECLINE/DHCPRELEASE: vendor class identifier MUST NOT be included (§4.4, Table 5) | MUST NOT | 4.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client sends no DHCPDECLINE, so no ze path builds the message whose fields this governs -- runV4 installs the ACKed address directly (handleV4Lease internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) and neither ze nor the vendored nclient4 client holds a DECLINE producer; that absence is itself recorded as the gap on RFC2131-3.1-7 | | `RFC2131-4.4-2` | DHCPDECLINE: requested IP address MUST be included (§4.4, Table 5) | MUST | 4.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client sends no DHCPDECLINE, so no ze path builds the message whose fields this governs -- runV4 installs the ACKed address directly (handleV4Lease internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) and neither ze nor the vendored nclient4 client holds a DECLINE producer; that absence is itself recorded as the gap on RFC2131-3.1-7 | | `RFC2131-4.4-3` | DHCPRELEASE: server identifier MUST be included (§4.4, Table 5) | MUST | 4.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCPv4 client sends no DHCPRELEASE, so no ze path builds the message whose fields this governs -- runV4 ends a lease by removing the address locally (internal/plugins/iface/dhcp/dhcp_v4_linux.go:120, 211-235), and nclient4's Release (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/lease.go:26) is called nowhere in ze | | `RFC2131-4.4.3-2` | Client SHOULD NOT request lease time parameters in DHCPINFORM (§4.4.3) | SHOULD NOT | 4.4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.4.1-2` | Client SHOULD wait a random time between one and ten seconds before first DHCPDISCOVER (§4.4.1) | SHOULD | 4.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.4.1-3` | Client SHOULD broadcast ARP reply to announce new IP address after DHCPACK (§4.4.1) | SHOULD | 4.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.3.2-6` | Server SHOULD check 'ciaddr' for correctness before replying to REBINDING DHCPREQUEST (§4.3.2) | SHOULD | 4.3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.2-4` | Client SHOULD provide mechanism for user to select vendor class identifier values (§4.2) | SHOULD | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.4.5-8` | Client SHOULD wait one-half of remaining time before retransmitting DHCPREQUEST in RENEWING/REBINDING (§4.4.5) | SHOULD | 4.4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.4.5-9` | Client SHOULD continue network processing if given its previous address after lease expiry (§4.4.5) | SHOULD | 4.4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.4.5-10` | Client SHOULD notify the local users if given a new address after lease expiry (§4.4.5) | SHOULD | 4.4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-3.5-3` | Server SHOULD respond with DHCPNAK if 'requested IP address' is invalid (§3.5) | SHOULD | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2131-4.3.1-8` | Address selection for new allocation SHOULD follow specified priority order (§4.3.1) | SHOULD | 4.3.1 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2131-4.3.5-1`](#rfc2131-4.3.5-1) Server MUST NOT send lease expiration time for DHCPINFORM (§4.3.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze answers no DHCPINFORM at all -- the message-type switch in handle (internal/plugins/dhcpserver/handler.go:121-134) dispatches DISCOVER, REQUEST, RELEASE and DECLINE, and every other type including msgInform (handler.go:37) falls to the default branch that returns nil, so ze builds no DHCPINFORM response at all | | [`RFC2131-4.3.2-1`](#rfc2131-4.3.2-1) When giaddr is 0x0, server MUST broadcast DHCPNAK to 0xFFFFFFFF (§4.3.2, §3.2) | {gap}, no test | with giaddr zero the delivery path unicasts a DHCPNAK to a non-zero ciaddr instead of broadcasting -- responseAddr tests giaddr, then ciaddr, before it ever reaches the broadcast decision (internal/plugins/dhcpserver/register.go:275-298) and never special-cases the NAK message type, so a REQUEST whose ciaddr is on-subnet and whose requested address is off-subnet is NAKed to ciaddr:68 | | [`RFC2131-4.3.2-2`](#rfc2131-4.3.2-2) When giaddr is set in DHCPNAK, server MUST set the broadcast bit (§4.3.2) | {gap}, no test | buildNak copies the client's flags word verbatim (internal/plugins/dhcpserver/handler.go:339) and never forces the BROADCAST bit, so a DHCPNAK relayed through a non-zero giaddr leaves the server with the broadcast bit clear whenever the client left it clear | | [`RFC2131-4.3.2-3`](#rfc2131-4.3.2-3) If server has no record of client in INIT-REBOOT, server MUST remain silent (§4.3.2) | {gap}, no test | the INIT-REBOOT branch commits a binding for any in-subnet requested address without consulting the lease table (handleRequest internal/plugins/dhcpserver/handler.go:178-183 calls commitBinding at handler.go:194), so a client the server holds no record of draws a DHCPACK rather than silence | | [`RFC2131-4.3.3-1`](#rfc2131-4.3.3-1) Server MUST mark the network address as unavailable on DHCPDECLINE (§4.3.3) | {gap}, no test | markUnavailable sets the pool bit and the static set for the declined address (internal/plugins/dhcpserver/pool.go:144-163), but the declining client's MAC-to-address cache entry survives -- pool.release returns early for a staticSet address (pool.go:174-177) -- so pool.allocate hands that same declined address back to that client on its next DISCOVER (pool.go:68-72) | | [`RFC2131-4.1-1`](#rfc2131-4.1-1) A server with multiple network addresses MUST be prepared to accept any of its addresses as identifying that server (§4.1) | {gap}, no test | each handler accepts exactly one address as its server identifier -- handleRequest discards a REQUEST whose option 54 differs from its own serverIP (internal/plugins/dhcpserver/handler.go:167-169) -- and that address is the single per-subnet value derived at register.go:93-101, so a REQUEST naming another of the server's own addresses is silently dropped | | [`RFC2131-4.1-2`](#rfc2131-4.1-2) Server MUST choose a server identifier address reachable from the client (§4.1) | {gap}, no test | with no default-router configured for a subnet the server identifier is not an address the server answers on -- register.go:93 prefers sub.DefaultRouter, which parseSubnet leaves unset when the leaf is absent (internal/plugins/dhcpserver/config.go:189-195), and falls back to the first pool address (internal/plugins/dhcpserver/register.go:95-97), which the server itself hands out to a client, or to the subnet network address (register.go:99-101), which is no host at all; buildReply then emits that value as option 54 (internal/plugins/dhcpserver/handler.go:246), so a client unicasting to it reaches nothing | | [`RFC2131-4.2-1`](#rfc2131-4.2-1) If client supplies a client identifier, server MUST use it to identify the client (§4.2) | {gap}, no test | ze never reads the client identifier -- option 61 is absent from the option codes at internal/plugins/dhcpserver/handler.go:41-61 and no parse call asks for it -- and keys every binding on the hardware address instead (extractMAC handler.go:456, leaseTable byMAC lease.go:23, pool.macToAddr pool.go:22), so a client that supplies a client identifier is still identified by chaddr | | [`RFC2131-2-1`](#rfc2131-2-1) Client identifier MUST be unique within the subnet (§2) | {gap}, no test | ze's client emits the operator-configured client identifier verbatim and nothing derives or checks it -- v4RequestModifiers appends OptClientIdentifier([]byte(c.config.ClientID)) (internal/plugins/iface/dhcp/dhcp_v4_linux.go:304-306) from the config leaf read at internal/component/iface/config.go:1267-1269 and carried at internal/component/iface/register.go:891, and no validator constrains the value, so two units on one subnet configured with the same client-id emit colliding identifiers | | [`RFC2131-2-2`](#rfc2131-2-2) If client uses a client identifier in one message, it MUST use the same identifier in all subsequent messages (§2) | {gap}, no test | ze's client sends option 61 at acquisition and omits it on every renewal -- the DORA call passes v4RequestModifiers() (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, built at :299-308) while renewV4 calls client.Renew(ctx, lease) with no modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:143), and the renewal REQUEST is built by NewRenewFromAck (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290) whose WithReply copies only opcode, HW type, xid, chaddr and flags (vendor/github.com/insomniacslk/dhcp/dhcpv4/modifiers.go:56-68) and never an option, so the identifier the server bound at acquisition is absent from every later message | | [`RFC2131-3.1-1`](#rfc2131-3.1-1) DHCPREQUEST in SELECTING state MUST include the server identifier option (§3.1, Table 4) | no test | no test carries this requirement id; annotated {not-applicable}: ze hands SELECTING message construction to the vendored library -- runV4 calls client.Request (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48), which builds the REQUEST in NewRequestFromOffer (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:256-270) where the server identifier is copied from the OFFER at dhcpv4.go:263; ze contributes only hostname and client-id modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:299-308) and authors none of those bytes | | [`RFC2131-3.1-2`](#rfc2131-3.1-2) DHCPREQUEST in SELECTING state MUST include the requested IP address option (§3.1, Table 4) | no test | no test carries this requirement id; annotated {not-applicable}: ze hands SELECTING message construction to the vendored library -- runV4 calls client.Request (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48), which builds the REQUEST in NewRequestFromOffer (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:256-270) where the requested IP address option is set from the OFFER's yiaddr at dhcpv4.go:261; ze contributes only hostname and client-id modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:299-308) and authors none of those bytes | | [`RFC2131-3.1-3`](#rfc2131-3.1-3) DHCPREQUEST in INIT-REBOOT state MUST include the requested IP address option (§3.1, Table 4) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client never enters INIT-REBOOT -- every acquisition is a full DORA (client.Request internal/plugins/iface/dhcp/dhcp_v4_linux.go:48) and every renewal is a RENEWING request built from the stored ACK (client.Renew internal/plugins/iface/dhcp/dhcp_v4_linux.go:143); no ze code path caches an address to reboot with, so ze produces no INIT-REBOOT REQUEST | | [`RFC2131-3.1-4`](#rfc2131-3.1-4) DHCPREQUEST in INIT-REBOOT state MUST NOT include the server identifier option (§3.1, Table 4) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client never enters INIT-REBOOT -- every acquisition is a full DORA (client.Request internal/plugins/iface/dhcp/dhcp_v4_linux.go:48) and every renewal is a RENEWING request built from the stored ACK (client.Renew internal/plugins/iface/dhcp/dhcp_v4_linux.go:143); no ze code path caches an address to reboot with, so ze produces no INIT-REBOOT REQUEST | | [`RFC2131-3.1-5`](#rfc2131-3.1-5) DHCPREQUEST in RENEWING/REBINDING MUST NOT include server identifier or requested IP address (§3.1, Table 4) | no test | no test carries this requirement id; annotated {not-applicable}: the RENEWING/REBINDING REQUEST is constructed entirely inside the vendored library -- renewV4 calls client.Renew with no modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:143) and NewRenewFromAck sets message type, ciaddr, the broadcast flag and a requested-options list only (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290), adding neither a server identifier nor a requested IP address; ze authors none of those bytes | | [`RFC2131-3.2-1`](#rfc2131-3.2-1) Client in INIT-REBOOT MUST NOT fill in ciaddr (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client never enters INIT-REBOOT -- every acquisition is a full DORA (client.Request internal/plugins/iface/dhcp/dhcp_v4_linux.go:48) and every renewal is a RENEWING request built from the stored ACK (client.Renew internal/plugins/iface/dhcp/dhcp_v4_linux.go:143); no ze code path caches an address to reboot with, so ze produces no INIT-REBOOT REQUEST | | [`RFC2131-3.1-6`](#rfc2131-3.1-6) Client MUST use same client identifier in DHCPRELEASE as used to obtain the lease (§3.1, §3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client sends no DHCPRELEASE, so no ze path builds the message whose fields this governs -- runV4 ends a lease by removing the address locally (internal/plugins/iface/dhcp/dhcp_v4_linux.go:120, 211-235), and nclient4's Release (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/lease.go:26) is called nowhere in ze | | [`RFC2131-3.1-7`](#rfc2131-3.1-7) If client detects allocated address is already in use, it MUST send DHCPDECLINE (§3.1, §3.2) | {gap}, no test | ze's client installs the ACKed address without checking it is free and has no DHCPDECLINE path -- handleV4Lease goes straight from the ACK to ReplaceAddressWithLifetime (internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) with no probe of the address, and neither ze nor the vendored nclient4 client contains a DECLINE producer, so an address found in use is kept rather than declined | | [`RFC2131-4.4.5-1`](#rfc2131-4.4.5-1) Client MUST stop network processing when lease expires (§4.4.5) | {gap}, no test | ze tears the lease down late -- runV4 budgets fixed sleeps of lease/2, 3*lease/8 and lease/8 (internal/plugins/iface/dhcp/dhcp_v4_linux.go:80-118) and calls renewV4 between them, so the blocking renewal attempts add to that budget: each failed renewal spends the vendored retry schedule of 5 seconds doubling over 3 tries (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:32, 35, retryFn 649-669), leaving the lease's default route (internal/plugins/iface/dhcp/dhcp_v4_linux.go:190) installed for around 70 seconds past expiry before removeV4Addr runs (internal/plugins/iface/dhcp/dhcp_v4_linux.go:120, 228-235); only the address itself leaves on time, because the kernel holds the lease duration as its valid lifetime (internal/plugins/iface/dhcp/dhcp_v4_linux.go:180, internal/plugins/iface/netlink/manage_linux.go:268-286) | | [`RFC2131-4.1-4`](#rfc2131-4.1-4) If option overload is used, it MUST appear in the options field (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze neither emits nor honors option overload (52) -- buildReply and buildNak leave sname and file zeroed (internal/plugins/dhcpserver/handler.go:220-290 and 333-354) and every option reader starts at pkt[240:], the options field alone (parseMsgType handler.go:367, parseOptionAddr handler.go:386, parseOptionBytes handler.go:471), so no option ever lives in the sname or file field | | [`RFC2131-4.1-5`](#rfc2131-4.1-5) Options in sname/file fields MUST begin with the first octet, be terminated by end option, and be followed by pad (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze neither emits nor honors option overload (52) -- buildReply and buildNak leave sname and file zeroed (internal/plugins/dhcpserver/handler.go:220-290 and 333-354) and every option reader starts at pkt[240:], the options field alone (parseMsgType handler.go:367, parseOptionAddr handler.go:386, parseOptionBytes handler.go:471), so no option ever lives in the sname or file field | | [`RFC2131-4.1-7`](#rfc2131-4.1-7) Options field MUST be interpreted first, then file, then sname (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze neither emits nor honors option overload (52) -- buildReply and buildNak leave sname and file zeroed (internal/plugins/dhcpserver/handler.go:220-290 and 333-354) and every option reader starts at pkt[240:], the options field alone (parseMsgType handler.go:367, parseOptionAddr handler.go:386, parseOptionBytes handler.go:471), so no option ever lives in the sname or file field; ze reads options from the options field only, so it holds no second option stream to order against it | | [`RFC2131-4.1-8`](#rfc2131-4.1-8) Client MUST adopt a retransmission strategy with randomized exponential backoff (§4.1) | {gap}, no test | ze's client retransmits on a doubling timeout with no randomization -- runV4 and renewV4 build the client with no ClientOpt (internal/plugins/iface/dhcp/dhcp_v4_linux.go:37, 128), so it runs the vendored defaults of a 5 second timeout over 3 tries (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:32, 35, 183) through retryFn, which only doubles the timeout between tries (client.go:649-669) | | [`RFC2131-4.1-9`](#rfc2131-4.1-9) Client MUST choose xid values to minimize collision with other clients (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: the transaction ID is drawn inside the vendored library -- every message ze's client sends is built through dhcpv4.New (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:144-150), which takes the xid from crypto/rand via GenerateTransactionID (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:121-139); ze calls only client.Request and client.Renew (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, 143) and authors no xid | | [`RFC2131-4.1-10`](#rfc2131-4.1-10) Client MUST use the IP address from the server identifier option for unicast requests (§4.1) | {gap}, no test | ze's renewal is not unicast to the server identifier -- renewV4 creates the client with nclient4.New(c.ifaceName) and no WithServerAddr (internal/plugins/iface/dhcp/dhcp_v4_linux.go:128), so serverAddr keeps the library default of 255.255.255.255:67 (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:50-53, assigned at client.go:183) and Renew sends the RENEWING REQUEST to that broadcast address (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/lease.go:60) instead of to the server identifier the ACK carried | | [`RFC2131-3.4-1`](#rfc2131-3.4-1) Server MUST NOT check for existing lease when responding to DHCPINFORM (§3.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze answers no DHCPINFORM at all -- the message-type switch in handle (internal/plugins/dhcpserver/handler.go:121-134) dispatches DISCOVER, REQUEST, RELEASE and DECLINE, and every other type including msgInform (handler.go:37) falls to the default branch that returns nil, so ze builds no DHCPINFORM response at all, so no response path exists that could consult an existing lease | | [`RFC2131-2-4`](#rfc2131-2-4) Client MUST be prepared to receive DHCP messages with options field of at least 312 octets (§2) | no test | no test carries this requirement id; annotated {not-applicable}: ze reads no DHCP bytes itself -- the receive path is the vendored library's receiveLoop, which reads each datagram into a MaxMessageSize (1500 octet) buffer before decoding (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:256-271, MaxMessageSize at client.go:38), and ze only consumes the decoded result of client.Request and client.Renew (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, 143) | | [`RFC2131-3.5-1`](#rfc2131-3.5-1) If client includes a parameter request list in DHCPDISCOVER, it MUST include that list in subsequent DHCPREQUEST messages (§3.5) | no test | no test carries this requirement id; annotated {not-applicable}: the parameter request list is written by the vendored library, the same list in both messages -- NewDiscovery (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:198-209) and NewRequestFromOffer (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:256-270) each add the identical WithRequestedOptions set, and ze passes only hostname and client-id modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:299-308), so it authors no request list | | [`RFC2131-3.1-8`](#rfc2131-3.1-8) DHCPREQUEST in SELECTING MUST use the same secs field and broadcast address as the original DHCPDISCOVER (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: the secs field and the broadcast flag of the SELECTING REQUEST are the vendored library's -- it leaves secs zero in both the DISCOVER and the REQUEST (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:164-180 sets no secs) and copies the flags word across with WithReply (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:258, vendor/github.com/insomniacslk/dhcp/dhcpv4/modifiers.go:56-68), while ze's DORA call and its modifiers touch neither (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, 299-308) | | [`RFC2131-4.3.1-1`](#rfc2131-4.3.1-1) Server MUST select configuration parameters by applying rules in specified order (§4.3.1) | {gap}, no test | ze applies no parameter-selection procedure -- buildReply emits one fixed option set in one fixed order (internal/plugins/dhcpserver/handler.go:245-285) and the parameter request list is never parsed (optParamReqList is defined at handler.go:52 and read nowhere in production), so the ordered rules of Section 4.3.1 govern no ze code path | | [`RFC2131-4.3.1-3`](#rfc2131-4.3.1-3) If server recognizes a parameter defined in the Host Requirements Document, it MUST include the default value (§4.3.1) | {gap}, no test | ze returns only the values its own subnet configuration holds (buildReply internal/plugins/dhcpserver/handler.go:245-285); it reads no parameter request list (optParamReqList handler.go:52 is parsed nowhere in production) and carries no Host Requirements defaults, so a recognized parameter the client requests draws no default value | | [`RFC2131-4.3.1-5`](#rfc2131-4.3.1-5) Server MUST supply as many of the requested parameters as possible and MUST omit any it cannot provide (§4.3.1) | {gap}, no test | the parameter request list is never parsed (optParamReqList internal/plugins/dhcpserver/handler.go:52 is read nowhere in production), so ze supplies its fixed configured option set (buildReply handler.go:245-285) rather than as many of the client's requested parameters as it can | | [`RFC2131-4.3.1-7`](#rfc2131-4.3.1-7) Vendor class identifier parameters MUST be identified by an exact match between client and server class identifiers (§4.3.1) | {gap}, no test | ze identifies the vendor class by a ten-octet prefix comparison against the string PXEClient: (isPXEClient internal/plugins/dhcpserver/handler.go:493-496) rather than by an exact match against a configured class identifier, and that prefix match is what selects the class-specific options appended at handler.go:292-330 | | [`RFC2131-4.4.1-1`](#rfc2131-4.4.1-1) Client MUST include its hardware address in the 'chaddr' field if necessary for DHCP reply delivery (§4.4.1) | no test | no test carries this requirement id; annotated {not-applicable}: chaddr is filled by the vendored library from the bound interface -- nclient4.New resolves the interface MAC (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:199-209) and NewDiscovery/NewRequestFromOffer set it with WithHwAddr (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:198-209, 256-270); ze only names the interface to bind (internal/plugins/iface/dhcp/dhcp_v4_linux.go:37) | | [`RFC2131-4.4.2-1`](#rfc2131-4.4.2-1) Client sending DHCPDECLINE MUST insert its known network address as 'requested IP address' option (§4.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client sends no DHCPDECLINE, so no ze path builds the message whose fields this governs -- runV4 installs the ACKed address directly (handleV4Lease internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) and neither ze nor the vendored nclient4 client holds a DECLINE producer; that absence is itself recorded as the gap on RFC2131-3.1-7 | | [`RFC2131-4.4.2-2`](#rfc2131-4.4.2-2) Client sending DHCPDECLINE/DHCPRELEASE of requested IP MUST NOT include 'server identifier' in INIT-REBOOT (§4.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client sends no DHCPDECLINE, so no ze path builds the message whose fields this governs -- runV4 installs the ACKed address directly (handleV4Lease internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) and neither ze nor the vendored nclient4 client holds a DECLINE producer; that absence is itself recorded as the gap on RFC2131-3.1-7 | | [`RFC2131-4.4.3-1`](#rfc2131-4.4.3-1) DHCPINFORM messages MUST be directed to the 'DHCP server' UDP port (§4.4.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's client sends no DHCPINFORM -- nclient4 exposes Inform (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:483-498) and ze calls it nowhere; runV4 and renewV4 use only Request and Renew (internal/plugins/iface/dhcp/dhcp_v4_linux.go:48, 143) | | [`RFC2131-4.3.2-5`](#rfc2131-4.3.2-5) DHCPREQUEST in REBINDING state MUST be broadcast to 0xFFFFFFFF (§4.3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's T2 attempt reuses the same vendored Renew call as its T1 attempt (internal/plugins/iface/dhcp/dhcp_v4_linux.go:103 and 143), so both the message and its destination are the library's: NewRenewFromAck builds it (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290) and it goes to the default serverAddr of 255.255.255.255:67 (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go:50-53, 183, used at nclient4/lease.go:60); ze chooses neither | | [`RFC2131-4.4.5-5`](#rfc2131-4.4.5-5) Client given a new network address after lease expiry MUST NOT continue using the previous address (§4.4.5) | {gap}, no test | a renewal that returns a different yiaddr leaves the previous address in use -- renewV4 hands the new ACK to handleV4Lease (internal/plugins/iface/dhcp/dhcp_v4_linux.go:154), which installs the new address with ReplaceAddressWithLifetime (internal/plugins/iface/dhcp/dhcp_v4_linux.go:180), a per-address replace that touches no other address (internal/plugins/iface/netlink/manage_linux.go:268-286), and runV4 then overwrites its ack reference (internal/plugins/iface/dhcp/dhcp_v4_linux.go:87-88) so the previous address is never passed to removeV4Addr; both addresses stay configured on the interface | | [`RFC2131-4.4.5-6`](#rfc2131-4.4.5-6) Client in RENEWING state MUST NOT include 'server identifier' in the DHCPREQUEST (§4.4.5) | no test | no test carries this requirement id; annotated {not-applicable}: the RENEWING/REBINDING REQUEST is constructed entirely inside the vendored library -- renewV4 calls client.Renew with no modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:143) and NewRenewFromAck sets message type, ciaddr, the broadcast flag and a requested-options list only (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290), adding neither a server identifier nor a requested IP address; ze authors none of those bytes | | [`RFC2131-4.4.5-7`](#rfc2131-4.4.5-7) Client in REBINDING state MUST NOT include 'server identifier' in the DHCPREQUEST (§4.4.5) | no test | no test carries this requirement id; annotated {not-applicable}: the RENEWING/REBINDING REQUEST is constructed entirely inside the vendored library -- renewV4 calls client.Renew with no modifiers (internal/plugins/iface/dhcp/dhcp_v4_linux.go:143) and NewRenewFromAck sets message type, ciaddr, the broadcast flag and a requested-options list only (vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go:275-290), adding neither a server identifier nor a requested IP address; ze authors none of those bytes | | [`RFC2131-3.2-3`](#rfc2131-3.2-3) Client MUST NOT fill in 'ciaddr' when it has not received its network address (INIT-REBOOT) (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client never enters INIT-REBOOT -- every acquisition is a full DORA (client.Request internal/plugins/iface/dhcp/dhcp_v4_linux.go:48) and every renewal is a RENEWING request built from the stored ACK (client.Renew internal/plugins/iface/dhcp/dhcp_v4_linux.go:143); no ze code path caches an address to reboot with, so ze produces no INIT-REBOOT REQUEST | | [`RFC2131-4.4-1`](#rfc2131-4.4-1) DHCPDECLINE/DHCPRELEASE: vendor class identifier MUST NOT be included (§4.4, Table 5) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client sends no DHCPDECLINE, so no ze path builds the message whose fields this governs -- runV4 installs the ACKed address directly (handleV4Lease internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) and neither ze nor the vendored nclient4 client holds a DECLINE producer; that absence is itself recorded as the gap on RFC2131-3.1-7 | | [`RFC2131-4.4-2`](#rfc2131-4.4-2) DHCPDECLINE: requested IP address MUST be included (§4.4, Table 5) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client sends no DHCPDECLINE, so no ze path builds the message whose fields this governs -- runV4 installs the ACKed address directly (handleV4Lease internal/plugins/iface/dhcp/dhcp_v4_linux.go:158-184) and neither ze nor the vendored nclient4 client holds a DECLINE producer; that absence is itself recorded as the gap on RFC2131-3.1-7 | | [`RFC2131-4.4-3`](#rfc2131-4.4-3) DHCPRELEASE: server identifier MUST be included (§4.4, Table 5) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCPv4 client sends no DHCPRELEASE, so no ze path builds the message whose fields this governs -- runV4 ends a lease by removing the address locally (internal/plugins/iface/dhcp/dhcp_v4_linux.go:120, 211-235), and nclient4's Release (vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/lease.go:26) is called nowhere in ze | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2131-4.3-1`](#rfc2131-4.3-1) Server identifier MUST be included in DHCPOFFER, DHCPACK, and DHCPNAK (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestServerIdentifierInEveryReply`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L169) | unit/verify | unproven | ### [`RFC2131-4.3-2`](#rfc2131-4.3-2) IP address lease time MUST be included in DHCPOFFER (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestLeaseTimeInOfferAndAck`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L194) | unit/verify | unproven | ### [`RFC2131-4.3-3`](#rfc2131-4.3-3) IP address lease time MUST be included in DHCPACK for DHCPREQUEST (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestLeaseTimeInOfferAndAck`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L198) | unit/verify | unproven | ### [`RFC2131-4.3-4`](#rfc2131-4.3-4) IP address lease time MUST NOT be included in DHCPNAK (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L333) | unit/verify | unproven | | positive | [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L245) | unit/verify | unproven | ### [`RFC2131-4.3-5`](#rfc2131-4.3-5) Requested IP address option MUST NOT be included in DHCPOFFER, DHCPACK, or DHCPNAK (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L312) | unit/verify | unproven | | positive | [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L224) | unit/verify | unproven | ### [`RFC2131-4.3-6`](#rfc2131-4.3-6) Client identifier MUST NOT be included in DHCPOFFER or DHCPACK (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L327) | unit/verify | unproven | | positive | [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L239) | unit/verify | unproven | ### [`RFC2131-4.3-7`](#rfc2131-4.3-7) Parameter request list MUST NOT be included in DHCPOFFER, DHCPACK, or DHCPNAK (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L316) | unit/verify | unproven | | positive | [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L228) | unit/verify | unproven | ### [`RFC2131-4.3-8`](#rfc2131-4.3-8) Maximum message size MUST NOT be included in DHCPOFFER, DHCPACK, or DHCPNAK (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L320) | unit/verify | unproven | | positive | [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L232) | unit/verify | unproven | ### [`RFC2131-4.3-9`](#rfc2131-4.3-9) Options other than message MUST NOT be included in DHCPNAK (§4.3, Table 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReplyDoesNotEchoClientOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L337) | unit/verify | unproven | | positive | [`TestReplyOmitsClientOnlyOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L249) | unit/verify | unproven | ### [`RFC2131-4.3.5-1`](#rfc2131-4.3.5-1) Server MUST NOT send lease expiration time for DHCPINFORM (§4.3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.5-1, so no unit is bound to it. ### [`RFC2131-4.3.2-1`](#rfc2131-4.3.2-1) When giaddr is 0x0, server MUST broadcast DHCPNAK to 0xFFFFFFFF (§4.3.2, §3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.2-1, so no unit is bound to it. ### [`RFC2131-4.3.2-2`](#rfc2131-4.3.2-2) When giaddr is set in DHCPNAK, server MUST set the broadcast bit (§4.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.2-2, so no unit is bound to it. ### [`RFC2131-4.3.2-3`](#rfc2131-4.3.2-3) If server has no record of client in INIT-REBOOT, server MUST remain silent (§4.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.2-3, so no unit is bound to it. ### [`RFC2131-4.3.3-1`](#rfc2131-4.3.3-1) Server MUST mark the network address as unavailable on DHCPDECLINE (§4.3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.3-1, so no unit is bound to it. ### [`RFC2131-4.1-1`](#rfc2131-4.1-1) A server with multiple network addresses MUST be prepared to accept any of its addresses as identifying that server (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.1-1, so no unit is bound to it. ### [`RFC2131-4.1-2`](#rfc2131-4.1-2) Server MUST choose a server identifier address reachable from the client (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.1-2, so no unit is bound to it. ### [`RFC2131-4.2-1`](#rfc2131-4.2-1) If client supplies a client identifier, server MUST use it to identify the client (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.2-1, so no unit is bound to it. ### [`RFC2131-4.2-2`](#rfc2131-4.2-2) If client does not provide a client identifier, server MUST use chaddr to identify the client (§4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestUnidentifiableClientRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L390) | unit/verify | unproven | | positive | [`TestChaddrIdentifiesClientWithoutClientIdentifier`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L366) | unit/verify | unproven | ### [`RFC2131-2-1`](#rfc2131-2-1) Client identifier MUST be unique within the subnet (§2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-2-1, so no unit is bound to it. ### [`RFC2131-2-2`](#rfc2131-2-2) If client uses a client identifier in one message, it MUST use the same identifier in all subsequent messages (§2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-2-2, so no unit is bound to it. ### [`RFC2131-3.1-1`](#rfc2131-3.1-1) DHCPREQUEST in SELECTING state MUST include the server identifier option (§3.1, Table 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.1-1, so no unit is bound to it. ### [`RFC2131-3.1-2`](#rfc2131-3.1-2) DHCPREQUEST in SELECTING state MUST include the requested IP address option (§3.1, Table 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.1-2, so no unit is bound to it. ### [`RFC2131-3.1-3`](#rfc2131-3.1-3) DHCPREQUEST in INIT-REBOOT state MUST include the requested IP address option (§3.1, Table 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.1-3, so no unit is bound to it. ### [`RFC2131-3.1-4`](#rfc2131-3.1-4) DHCPREQUEST in INIT-REBOOT state MUST NOT include the server identifier option (§3.1, Table 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.1-4, so no unit is bound to it. ### [`RFC2131-3.1-5`](#rfc2131-3.1-5) DHCPREQUEST in RENEWING/REBINDING MUST NOT include server identifier or requested IP address (§3.1, Table 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.1-5, so no unit is bound to it. ### [`RFC2131-3.2-1`](#rfc2131-3.2-1) Client in INIT-REBOOT MUST NOT fill in ciaddr (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.2-1, so no unit is bound to it. ### [`RFC2131-3.1-6`](#rfc2131-3.1-6) Client MUST use same client identifier in DHCPRELEASE as used to obtain the lease (§3.1, §3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.1-6, so no unit is bound to it. ### [`RFC2131-3.1-7`](#rfc2131-3.1-7) If client detects allocated address is already in use, it MUST send DHCPDECLINE (§3.1, §3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.1-7, so no unit is bound to it. ### [`RFC2131-4.4.5-1`](#rfc2131-4.4.5-1) Client MUST stop network processing when lease expires (§4.4.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4.5-1, so no unit is bound to it. ### [`RFC2131-3-1`](#rfc2131-3-1) Options field MUST start with magic cookie 0x63825363 (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestMagicCookieRequiredAndEmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L431) | unit/verify | unproven | | positive | [`TestMagicCookieRequiredAndEmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L423) | unit/verify | unproven | ### [`RFC2131-4.1-3`](#rfc2131-4.1-3) Options field MUST end with end option (255) (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReplyOptionsTerminatedByEnd`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L471) | unit/verify | unproven | | positive | [`TestReplyOptionsTerminatedByEnd`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L453) | unit/verify | unproven | ### [`RFC2131-4.1-4`](#rfc2131-4.1-4) If option overload is used, it MUST appear in the options field (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.1-4, so no unit is bound to it. ### [`RFC2131-4.1-5`](#rfc2131-4.1-5) Options in sname/file fields MUST begin with the first octet, be terminated by end option, and be followed by pad (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.1-5, so no unit is bound to it. ### [`RFC2131-4.1-6`](#rfc2131-4.1-6) Each option MUST be entirely contained in its field (options/sname/file) (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOptionsContainedWithinTheirField`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L515) | unit/verify | unproven | | positive | [`TestOptionsContainedWithinTheirField`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L492) | unit/verify | unproven | ### [`RFC2131-4.1-7`](#rfc2131-4.1-7) Options field MUST be interpreted first, then file, then sname (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.1-7, so no unit is bound to it. ### [`RFC2131-4.1-8`](#rfc2131-4.1-8) Client MUST adopt a retransmission strategy with randomized exponential backoff (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.1-8, so no unit is bound to it. ### [`RFC2131-4.1-9`](#rfc2131-4.1-9) Client MUST choose xid values to minimize collision with other clients (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.1-9, so no unit is bound to it. ### [`RFC2131-4.1-10`](#rfc2131-4.1-10) Client MUST use the IP address from the server identifier option for unicast requests (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.1-10, so no unit is bound to it. ### [`RFC2131-3.4-1`](#rfc2131-3.4-1) Server MUST NOT check for existing lease when responding to DHCPINFORM (§3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.4-1, so no unit is bound to it. ### [`RFC2131-2-3`](#rfc2131-2-3) Flags field bits 1-15 MUST be set to zero by clients and ignored by servers/relay agents (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFlagsReservedBitsIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L560) | unit/verify | unproven | | positive | [`TestFlagsReservedBitsIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L549) | unit/verify | unproven | ### [`RFC2131-2-4`](#rfc2131-2-4) Client MUST be prepared to receive DHCP messages with options field of at least 312 octets (§2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-2-4, so no unit is bound to it. ### [`RFC2131-3.5-1`](#rfc2131-3.5-1) If client includes a parameter request list in DHCPDISCOVER, it MUST include that list in subsequent DHCPREQUEST messages (§3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.5-1, so no unit is bound to it. ### [`RFC2131-4.4.5-2`](#rfc2131-4.4.5-2) T1 MUST be earlier than T2 (§4.4.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestShortLeaseTimeRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L630) | unit/verify | unproven | | positive | [`TestRenewalTimersOrderedWithinLease`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L603) | unit/verify | unproven | ### [`RFC2131-4.4.5-3`](#rfc2131-4.4.5-3) T2 MUST be earlier than lease expiry (§4.4.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestShortLeaseTimeRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L635) | unit/verify | unproven | | positive | [`TestRenewalTimersOrderedWithinLease`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L607) | unit/verify | unproven | ### [`RFC2131-3.1-8`](#rfc2131-3.1-8) DHCPREQUEST in SELECTING MUST use the same secs field and broadcast address as the original DHCPDISCOVER (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.1-8, so no unit is bound to it. ### [`RFC2131-4.3.1-1`](#rfc2131-4.3.1-1) Server MUST select configuration parameters by applying rules in specified order (§4.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.1-1, so no unit is bound to it. ### [`RFC2131-4.3.1-2`](#rfc2131-4.3.1-2) If server has an explicitly configured default value for a requested parameter, it MUST include that value (§4.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestUnconfiguredParametersOmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L697) | unit/verify | unproven | ### [`RFC2131-4.3.1-3`](#rfc2131-4.3.1-3) If server recognizes a parameter defined in the Host Requirements Document, it MUST include the default value (§4.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.1-3, so no unit is bound to it. ### [`RFC2131-4.3.1-4`](#rfc2131-4.3.1-4) If server has no value for a requested parameter, it MUST NOT return a value for that parameter (§4.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestUnconfiguredParametersOmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L682) | unit/verify | unproven | | positive | [`TestUnconfiguredParametersOmitted`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L668) | unit/verify | unproven | ### [`RFC2131-4.3.1-5`](#rfc2131-4.3.1-5) Server MUST supply as many of the requested parameters as possible and MUST omit any it cannot provide (§4.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.1-5, so no unit is bound to it. ### [`RFC2131-4.3.1-6`](#rfc2131-4.3.1-6) Server MUST include each requested parameter only once unless explicitly allowed (§4.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEachParameterEmittedOnce`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L744) | unit/verify | unproven | | positive | [`TestEachParameterEmittedOnce`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/rfc2131_test.go#L723) | unit/verify | unproven | ### [`RFC2131-4.3.1-7`](#rfc2131-4.3.1-7) Vendor class identifier parameters MUST be identified by an exact match between client and server class identifiers (§4.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.1-7, so no unit is bound to it. ### [`RFC2131-4.4.1-1`](#rfc2131-4.4.1-1) Client MUST include its hardware address in the 'chaddr' field if necessary for DHCP reply delivery (§4.4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4.1-1, so no unit is bound to it. ### [`RFC2131-4.4.2-1`](#rfc2131-4.4.2-1) Client sending DHCPDECLINE MUST insert its known network address as 'requested IP address' option (§4.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4.2-1, so no unit is bound to it. ### [`RFC2131-4.4.2-2`](#rfc2131-4.4.2-2) Client sending DHCPDECLINE/DHCPRELEASE of requested IP MUST NOT include 'server identifier' in INIT-REBOOT (§4.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4.2-2, so no unit is bound to it. ### [`RFC2131-4.4.3-1`](#rfc2131-4.4.3-1) DHCPINFORM messages MUST be directed to the 'DHCP server' UDP port (§4.4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4.3-1, so no unit is bound to it. ### [`RFC2131-4.3.2-5`](#rfc2131-4.3.2-5) DHCPREQUEST in REBINDING state MUST be broadcast to 0xFFFFFFFF (§4.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.3.2-5, so no unit is bound to it. ### [`RFC2131-4.4.5-5`](#rfc2131-4.4.5-5) Client given a new network address after lease expiry MUST NOT continue using the previous address (§4.4.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4.5-5, so no unit is bound to it. ### [`RFC2131-4.4.5-6`](#rfc2131-4.4.5-6) Client in RENEWING state MUST NOT include 'server identifier' in the DHCPREQUEST (§4.4.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4.5-6, so no unit is bound to it. ### [`RFC2131-4.4.5-7`](#rfc2131-4.4.5-7) Client in REBINDING state MUST NOT include 'server identifier' in the DHCPREQUEST (§4.4.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4.5-7, so no unit is bound to it. ### [`RFC2131-3.2-3`](#rfc2131-3.2-3) Client MUST NOT fill in 'ciaddr' when it has not received its network address (INIT-REBOOT) (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-3.2-3, so no unit is bound to it. ### [`RFC2131-4.4-1`](#rfc2131-4.4-1) DHCPDECLINE/DHCPRELEASE: vendor class identifier MUST NOT be included (§4.4, Table 5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4-1, so no unit is bound to it. ### [`RFC2131-4.4-2`](#rfc2131-4.4-2) DHCPDECLINE: requested IP address MUST be included (§4.4, Table 5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4-2, so no unit is bound to it. ### [`RFC2131-4.4-3`](#rfc2131-4.4-3) DHCPRELEASE: server identifier MUST be included (§4.4, Table 5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2131-4.4-3, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2131, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2131, so its obligations are stated where they were written. --- ### Page: RFC 2132 - DHCP Options and BOOTP Vendor Extensions https://ze-software.net/quality/rfc-compliance/rfc2132/ # RFC 2132 - DHCP Options and BOOTP Vendor Extensions Partial. Every requirement this repository extracted from RFC 2132, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 34 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 23.5% | 8 of 34 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 34 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 8 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 34 | of 68 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 25 | of 34 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 73.5% | 25 of 34 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 34 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 34 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 2.9% | 1 of 34 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 34 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 68 | | Gated MUST-level | 34 | | Not applicable, so out of scope | 25 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 8 | | Tagged units | 8 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2132.md` | | Requirement shard | `rfc/requirements/rfc2132.md` | | RFC text | `rfc/full/rfc2132.txt` | ## Enrolment Enrolled: DHCP Options and BOOTP Vendor Extensions (RFC 2132): DHCP server option TLV encoding. 8 single-polarity positive (subnet mask before router, router/DNS length multiple of 4, every option carries a length octet, unrecognized vendor-specific/class-specific info ignored, PXE class prefix-match) + 1 gap (RFC2132-9.8-1 Parameter Request List option 55 not honored, fixed option order) + 25 not-applicable (length MUSTs for options ze never emits, client-identifier uniqueness is a client obligation) ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc2132.md). **What the ledger says remains** One MUST gap, tracked in [`rfc/short/rfc2132.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2132.md): [`RFC2132-9.8-1`](#rfc2132-9.8-1) -- the server emits a fixed option set in a fixed order (buildReply [`internal/plugins/dhcpserver/handler.go`](https://github.com/ze-software/ze/blob/main/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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 34 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **34** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (34):** [`RFC2132-3.3-1`](#rfc2132-3.3-1), [`RFC2132-3.5-1`](#rfc2132-3.5-1), [`RFC2132-3.6-1`](#rfc2132-3.6-1), [`RFC2132-3.7-1`](#rfc2132-3.7-1), [`RFC2132-3.8-1`](#rfc2132-3.8-1), [`RFC2132-3.9-1`](#rfc2132-3.9-1), [`RFC2132-3.10-1`](#rfc2132-3.10-1), [`RFC2132-3.11-1`](#rfc2132-3.11-1), [`RFC2132-3.12-1`](#rfc2132-3.12-1), [`RFC2132-3.13-1`](#rfc2132-3.13-1), [`RFC2132-8.2-1`](#rfc2132-8.2-1), [`RFC2132-8.3-1`](#rfc2132-8.3-1), [`RFC2132-8.9-1`](#rfc2132-8.9-1), [`RFC2132-8.10-1`](#rfc2132-8.10-1), [`RFC2132-8.12-1`](#rfc2132-8.12-1), [`RFC2132-8.13-1`](#rfc2132-8.13-1), [`RFC2132-8.14-1`](#rfc2132-8.14-1), [`RFC2132-8.15-1`](#rfc2132-8.15-1), [`RFC2132-8.16-1`](#rfc2132-8.16-1), [`RFC2132-8.17-1`](#rfc2132-8.17-1), [`RFC2132-8.18-1`](#rfc2132-8.18-1), [`RFC2132-8.19-1`](#rfc2132-8.19-1), [`RFC2132-8.20-1`](#rfc2132-8.20-1), [`RFC2132-8.21-1`](#rfc2132-8.21-1), [`RFC2132-4.7-1`](#rfc2132-4.7-1), [`RFC2132-4.3-1`](#rfc2132-4.3-1), [`RFC2132-5.8-1`](#rfc2132-5.8-1), [`RFC2132-2-1`](#rfc2132-2-1), [`RFC2132-2-2`](#rfc2132-2-2), [`RFC2132-2-3`](#rfc2132-2-3), [`RFC2132-8.4-1`](#rfc2132-8.4-1), [`RFC2132-9.13-1`](#rfc2132-9.13-1), [`RFC2132-9.8-1`](#rfc2132-9.8-1), [`RFC2132-9.14-1`](#rfc2132-9.14-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2132-3.3-1` | If both subnet mask and router option are specified in a DHCP reply, the subnet mask option MUST be first (§3.3) | MUST | 3.3 | **positive:** `unit/verify` [`TestOptionSubnetMaskBeforeRouter`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1331). **negative:** no negative test. **{single-polarity}:** ze constructs every OFFER/ACK with the subnet mask (option 1) emitted before the router (option 3) in fixed code order (buildReply internal/plugins/dhcpserver/handler.go:251 then :255); no code path emits them reversed, so there is no out-of-order output to assert as a negative | | `RFC2132-3.5-1` | Router option length MUST always be a multiple of 4 (§3.5) | MUST | 3.5 | **positive:** `unit/verify` [`TestEmittedIPListOptionLengthsMultipleOfFour`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1354). **negative:** no negative test. **{single-polarity}:** ze emits option 3 from a single netip.Addr via As4() = exactly 4 octets (buildReply internal/plugins/dhcpserver/handler.go:255), so the router option length is always a multiple of 4 and no code path can emit a non-multiple-of-4 router option to test negatively | | `RFC2132-3.6-1` | Time server option length MUST always be a multiple of 4 (§3.6) | MUST | 3.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Time Server option (code 4), so this sender length constraint binds no ze code path | | `RFC2132-3.7-1` | Name server option length MUST always be a multiple of 4 (§3.7) | MUST | 3.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Name Server option (code 5), so this sender length constraint binds no ze code path | | `RFC2132-3.8-1` | Domain name server option length MUST always be a multiple of 4 (§3.8) | MUST | 3.8 | **positive:** `unit/verify` [`TestEmittedIPListOptionLengthsMultipleOfFour`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1363). **negative:** no negative test. **{single-polarity}:** ze emits option 6 by concatenating 4-octet As4() addresses (buildReply internal/plugins/dhcpserver/handler.go:258-264), so the DNS option length is always 4*n and no code path can emit a non-multiple-of-4 DNS option to test negatively | | `RFC2132-3.9-1` | Log server option length MUST always be a multiple of 4 (§3.9) | MUST | 3.9 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Log Server option (code 7), so this sender length constraint binds no ze code path | | `RFC2132-3.10-1` | Cookie server option length MUST always be a multiple of 4 (§3.10) | MUST | 3.10 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Cookie Server option (code 8), so this sender length constraint binds no ze code path | | `RFC2132-3.11-1` | LPR server option length MUST always be a multiple of 4 (§3.11) | MUST | 3.11 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the LPR Server option (code 9), so this sender length constraint binds no ze code path | | `RFC2132-3.12-1` | Impress server option length MUST always be a multiple of 4 (§3.12) | MUST | 3.12 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Impress Server option (code 10), so this sender length constraint binds no ze code path | | `RFC2132-3.13-1` | Resource location server option length MUST always be a multiple of 4 (§3.13) | MUST | 3.13 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Resource Location Server option (code 11), so this sender length constraint binds no ze code path | | `RFC2132-8.2-1` | NIS servers option length MUST be a multiple of 4 (§8.2) | MUST | 8.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the NIS Servers option (code 41), so this sender length constraint binds no ze code path | | `RFC2132-8.3-1` | NTP servers option length MUST be a multiple of 4 (§8.3) | MUST | 8.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the NTP Servers option (code 42), so this sender length constraint binds no ze code path | | `RFC2132-8.9-1` | X Window Font Server option length MUST be a multiple of 4 (§8.9) | MUST | 8.9 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the X Window Font Server option (code 48), so this sender length constraint binds no ze code path | | `RFC2132-8.10-1` | X Window Display Manager option length MUST be a multiple of 4 (§8.10) | MUST | 8.10 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the X Window Display Manager option (code 49), so this sender length constraint binds no ze code path | | `RFC2132-8.12-1` | NIS+ Servers option length MUST be a multiple of 4 (§8.12) | MUST | 8.12 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the NIS+ Servers option (code 65), so this sender length constraint binds no ze code path | | `RFC2132-8.13-1` | Mobile IP Home Agent option length MUST be a multiple of 4 (§8.13) | MUST | 8.13 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Mobile IP Home Agent option (code 68), so this sender length constraint binds no ze code path | | `RFC2132-8.14-1` | SMTP server option length MUST always be a multiple of 4 (§8.14) | MUST | 8.14 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the SMTP Server option (code 69), so this sender length constraint binds no ze code path | | `RFC2132-8.15-1` | POP3 server option length MUST always be a multiple of 4 (§8.15) | MUST | 8.15 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the POP3 Server option (code 70), so this sender length constraint binds no ze code path | | `RFC2132-8.16-1` | NNTP server option length MUST always be a multiple of 4 (§8.16) | MUST | 8.16 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the NNTP Server option (code 71), so this sender length constraint binds no ze code path | | `RFC2132-8.17-1` | WWW server option length MUST always be a multiple of 4 (§8.17) | MUST | 8.17 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the WWW Server option (code 72), so this sender length constraint binds no ze code path | | `RFC2132-8.18-1` | Finger server option length MUST always be a multiple of 4 (§8.18) | MUST | 8.18 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Finger Server option (code 73), so this sender length constraint binds no ze code path | | `RFC2132-8.19-1` | IRC server option length MUST always be a multiple of 4 (§8.19) | MUST | 8.19 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the IRC Server option (code 74), so this sender length constraint binds no ze code path | | `RFC2132-8.20-1` | StreetTalk server option length MUST always be a multiple of 4 (§8.20) | MUST | 8.20 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the StreetTalk Server option (code 75), so this sender length constraint binds no ze code path | | `RFC2132-8.21-1` | STDA server option length MUST always be a multiple of 4 (§8.21) | MUST | 8.21 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the STDA Server option (code 76), so this sender length constraint binds no ze code path | | `RFC2132-4.7-1` | Path MTU plateau table option length MUST be a multiple of 2 (§4.7) | MUST | 4.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Path MTU Plateau Table option (code 25), so this sender length constraint binds no ze code path | | `RFC2132-4.3-1` | Policy filter option length MUST be a multiple of 8 (§4.3) | MUST | 4.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Policy Filter option (code 21), so this sender length constraint binds no ze code path | | `RFC2132-5.8-1` | Static route option length MUST be a multiple of 8 (§5.8) | MUST | 5.8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Static Route option (code 33), so this sender length constraint binds no ze code path | | `RFC2132-2-1` | Any options defined subsequent to this document MUST contain a length octet even if fixed or zero (§2) | MUST | 2 | **positive:** `unit/verify` [`TestEveryEmittedOptionHasLengthOctet`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1413). **negative:** no negative test. **{single-polarity}:** every option ze emits goes through safeAppendOption, which always writes a length octet (internal/plugins/dhcpserver/handler.go:361-363); only the exempt Pad/End markers are written without one, so ze emits no length-octet-less option to test negatively | | `RFC2132-2-2` | Receiver MUST be prepared to delete trailing nulls from ASCII options (§2) | MUST | 2 | **positive:** `unit/verify` [`TestASCIIOptionParsingTolerantOfTrailingNull`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1452). **negative:** no negative test. **{single-polarity}:** ze reads ASCII-carrying options 60 and 77 only by fixed-prefix match (isPXEClient internal/plugins/dhcpserver/handler.go:493, isIPXE handler.go:498), so a trailing NUL never corrupts interpretation; no ze receive path rejects or mishandles ASCII option data because of a trailing NUL, so there is no negative case | | `RFC2132-2-3` | Receiver MUST NOT require that a trailing null be included in ASCII data (§2) | MUST NOT | 2 | **positive:** `unit/verify` [`TestASCIIOptionParsingTolerantOfTrailingNull`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1444). **negative:** no negative test. **{single-polarity}:** ze's ASCII-option receiver prefix-matches options 60 and 77 (isPXEClient internal/plugins/dhcpserver/handler.go:493, isIPXE handler.go:498) and so never requires a trailing NUL; option data without one is accepted, and there is no ze path that demands a trailing NUL to test negatively | | `RFC2132-8.4-1` | Servers not equipped to interpret vendor-specific information MUST ignore it (§8.4) | MUST | 8.4 | **positive:** `unit/verify` [`TestIgnoresClientVendorSpecificOption43`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1501). **negative:** no negative test. **{single-polarity}:** ze never reads a received option 43 -- vendor-specific information appears only in ze's Tx path (handler.go:325) and the option-parse loop skips any code it does not request (parseMsgType/parseOptionBytes advance past unrequested options, internal/plugins/dhcpserver/handler.go:367-403), so ze ignores client vendor-specific info and no code path interprets or rejects it | | `RFC2132-9.13-1` | Servers not equipped to interpret class-specific information MUST ignore it (§9.13) | MUST | 9.13 | **positive:** `unit/verify` [`TestIgnoresUnknownVendorClass`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1548). **negative:** no negative test. **{single-polarity}:** ze inspects option 60 only for the "PXEClient:" prefix (isPXEClient internal/plugins/dhcpserver/handler.go:493-496); any other vendor class yields false and no class-specific handling, so an unrecognized class is ignored and no code path rejects a packet on class content to test negatively | | `RFC2132-9.8-1` | Server MUST try to insert requested options in the order requested by the client (§9.8) | MUST | 9.8 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze emits a fixed option set in a fixed order (buildReply internal/plugins/dhcpserver/handler.go:245-285) and never reads the client Parameter Request List (option 55 is defined at handler.go:52 but parsed nowhere in production), so it does not try to insert requested options in the client's requested order | | `RFC2132-9.14-1` | Each client's client-identifier MUST be unique among identifiers on the subnet (§9.14) | MUST | 9.14 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the uniqueness obligation binds the client's choice of client-identifier (option 61); ze is a server that keys leases and pool allocations by hardware address/chaddr (extractMAC internal/plugins/dhcpserver/handler.go:456, pool.allocate pool.go:64, leaseTable byMAC lease.go:23) and never reads or generates option 61, so it neither produces client-identifiers nor can enforce cross-client uniqueness | | `RFC2132-2-4` | Options containing NVT ASCII data SHOULD NOT include a trailing NULL (§2) | SHOULD NOT | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.5-2` | Routers SHOULD be listed in order of preference (§3.5) | SHOULD | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.6-2` | Time servers SHOULD be listed in order of preference (§3.6) | SHOULD | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.7-2` | Name servers SHOULD be listed in order of preference (§3.7) | SHOULD | 3.7 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.8-2` | Domain name servers SHOULD be listed in order of preference (§3.8) | SHOULD | 3.8 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.9-2` | Log servers SHOULD be listed in order of preference (§3.9) | SHOULD | 3.9 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.10-2` | Cookie servers SHOULD be listed in order of preference (§3.10) | SHOULD | 3.10 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.11-2` | LPR servers SHOULD be listed in order of preference (§3.11) | SHOULD | 3.11 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.12-2` | Impress servers SHOULD be listed in order of preference (§3.12) | SHOULD | 3.12 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-3.13-2` | Resource location servers SHOULD be listed in order of preference (§3.13) | SHOULD | 3.13 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.2-2` | NIS servers SHOULD be listed in order of preference (§8.2) | SHOULD | 8.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.3-2` | NTP servers SHOULD be listed in order of preference (§8.3) | SHOULD | 8.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.4-2` | Clients not receiving desired vendor-specific information SHOULD make an attempt to operate without it (§8.4) | SHOULD | 8.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.9-2` | X Window Font servers SHOULD be listed in order of preference (§8.9) | SHOULD | 8.9 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.10-2` | X Window Display Manager addresses SHOULD be listed in order of preference (§8.10) | SHOULD | 8.10 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.12-2` | NIS+ servers SHOULD be listed in order of preference (§8.12) | SHOULD | 8.12 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.13-2` | Mobile IP home agents SHOULD be listed in order of preference (§8.13) | SHOULD | 8.13 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.14-2` | SMTP servers SHOULD be listed in order of preference (§8.14) | SHOULD | 8.14 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.15-2` | POP3 servers SHOULD be listed in order of preference (§8.15) | SHOULD | 8.15 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.16-2` | NNTP servers SHOULD be listed in order of preference (§8.16) | SHOULD | 8.16 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.17-2` | WWW servers SHOULD be listed in order of preference (§8.17) | SHOULD | 8.17 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.18-2` | Finger servers SHOULD be listed in order of preference (§8.18) | SHOULD | 8.18 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.19-2` | IRC servers SHOULD be listed in order of preference (§8.19) | SHOULD | 8.19 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.20-2` | StreetTalk servers SHOULD be listed in order of preference (§8.20) | SHOULD | 8.20 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.21-2` | STDA servers SHOULD be listed in order of preference (§8.21) | SHOULD | 8.21 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-9.13-2` | Servers responding to vendor class identifier SHOULD only use option 43 to return vendor-specific information (§9.13) | SHOULD | 9.13 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-9.14-2` | Client identifiers SHOULD be treated as opaque objects by DHCP servers (§9.14) | SHOULD | 9.14 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-9.14-3` | Client identifier type field SHOULD be one of the ARP hardware types from STD 2 (§9.14) | SHOULD | 9.14 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.4-3` | Vendor SHOULD encode multiple items in vendor-specific information using encapsulated vendor-specific options (§8.4) | SHOULD | 8.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.4-4` | Encapsulated vendor-specific extensions SHOULD NOT contain a magic cookie field (§8.4) | SHOULD NOT | 8.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.4-5` | Encapsulated vendor-specific option codes SHOULD conform to the tag-length-value syntax (§8.4) | SHOULD | 8.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-9.8-2` | Client MAY list options in order of preference in Parameter Request List (§9.8) | MAY | 9.8 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-9.14-4` | Client identifier MAY consist of type-value pairs similar to htype/chaddr (§9.14) | MAY | 9.14 | **positive:** no positive test. **negative:** no negative test | | `RFC2132-8.4-6` | Vendor-specific option codes other than 0 or 255 MAY be redefined within the encapsulated field (§8.4) | MAY | 8.4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2132-3.6-1`](#rfc2132-3.6-1) Time server option length MUST always be a multiple of 4 (§3.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Time Server option (code 4), so this sender length constraint binds no ze code path | | [`RFC2132-3.7-1`](#rfc2132-3.7-1) Name server option length MUST always be a multiple of 4 (§3.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Name Server option (code 5), so this sender length constraint binds no ze code path | | [`RFC2132-3.9-1`](#rfc2132-3.9-1) Log server option length MUST always be a multiple of 4 (§3.9) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Log Server option (code 7), so this sender length constraint binds no ze code path | | [`RFC2132-3.10-1`](#rfc2132-3.10-1) Cookie server option length MUST always be a multiple of 4 (§3.10) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Cookie Server option (code 8), so this sender length constraint binds no ze code path | | [`RFC2132-3.11-1`](#rfc2132-3.11-1) LPR server option length MUST always be a multiple of 4 (§3.11) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the LPR Server option (code 9), so this sender length constraint binds no ze code path | | [`RFC2132-3.12-1`](#rfc2132-3.12-1) Impress server option length MUST always be a multiple of 4 (§3.12) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Impress Server option (code 10), so this sender length constraint binds no ze code path | | [`RFC2132-3.13-1`](#rfc2132-3.13-1) Resource location server option length MUST always be a multiple of 4 (§3.13) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Resource Location Server option (code 11), so this sender length constraint binds no ze code path | | [`RFC2132-8.2-1`](#rfc2132-8.2-1) NIS servers option length MUST be a multiple of 4 (§8.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the NIS Servers option (code 41), so this sender length constraint binds no ze code path | | [`RFC2132-8.3-1`](#rfc2132-8.3-1) NTP servers option length MUST be a multiple of 4 (§8.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the NTP Servers option (code 42), so this sender length constraint binds no ze code path | | [`RFC2132-8.9-1`](#rfc2132-8.9-1) X Window Font Server option length MUST be a multiple of 4 (§8.9) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the X Window Font Server option (code 48), so this sender length constraint binds no ze code path | | [`RFC2132-8.10-1`](#rfc2132-8.10-1) X Window Display Manager option length MUST be a multiple of 4 (§8.10) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the X Window Display Manager option (code 49), so this sender length constraint binds no ze code path | | [`RFC2132-8.12-1`](#rfc2132-8.12-1) NIS+ Servers option length MUST be a multiple of 4 (§8.12) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the NIS+ Servers option (code 65), so this sender length constraint binds no ze code path | | [`RFC2132-8.13-1`](#rfc2132-8.13-1) Mobile IP Home Agent option length MUST be a multiple of 4 (§8.13) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Mobile IP Home Agent option (code 68), so this sender length constraint binds no ze code path | | [`RFC2132-8.14-1`](#rfc2132-8.14-1) SMTP server option length MUST always be a multiple of 4 (§8.14) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the SMTP Server option (code 69), so this sender length constraint binds no ze code path | | [`RFC2132-8.15-1`](#rfc2132-8.15-1) POP3 server option length MUST always be a multiple of 4 (§8.15) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the POP3 Server option (code 70), so this sender length constraint binds no ze code path | | [`RFC2132-8.16-1`](#rfc2132-8.16-1) NNTP server option length MUST always be a multiple of 4 (§8.16) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the NNTP Server option (code 71), so this sender length constraint binds no ze code path | | [`RFC2132-8.17-1`](#rfc2132-8.17-1) WWW server option length MUST always be a multiple of 4 (§8.17) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the WWW Server option (code 72), so this sender length constraint binds no ze code path | | [`RFC2132-8.18-1`](#rfc2132-8.18-1) Finger server option length MUST always be a multiple of 4 (§8.18) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Finger Server option (code 73), so this sender length constraint binds no ze code path | | [`RFC2132-8.19-1`](#rfc2132-8.19-1) IRC server option length MUST always be a multiple of 4 (§8.19) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the IRC Server option (code 74), so this sender length constraint binds no ze code path | | [`RFC2132-8.20-1`](#rfc2132-8.20-1) StreetTalk server option length MUST always be a multiple of 4 (§8.20) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the StreetTalk Server option (code 75), so this sender length constraint binds no ze code path | | [`RFC2132-8.21-1`](#rfc2132-8.21-1) STDA server option length MUST always be a multiple of 4 (§8.21) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the STDA Server option (code 76), so this sender length constraint binds no ze code path | | [`RFC2132-4.7-1`](#rfc2132-4.7-1) Path MTU plateau table option length MUST be a multiple of 2 (§4.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Path MTU Plateau Table option (code 25), so this sender length constraint binds no ze code path | | [`RFC2132-4.3-1`](#rfc2132-4.3-1) Policy filter option length MUST be a multiple of 8 (§4.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Policy Filter option (code 21), so this sender length constraint binds no ze code path | | [`RFC2132-5.8-1`](#rfc2132-5.8-1) Static route option length MUST be a multiple of 8 (§5.8) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DHCP server emits only options 1/3/6/15/51/53/54/58/59 and PXE 43/60/66/67 (buildReply internal/plugins/dhcpserver/handler.go:245-285, appendPXEOptions handler.go:298-325); it never emits the Static Route option (code 33), so this sender length constraint binds no ze code path | | [`RFC2132-9.8-1`](#rfc2132-9.8-1) Server MUST try to insert requested options in the order requested by the client (§9.8) | {gap}, no test | ze emits a fixed option set in a fixed order (buildReply internal/plugins/dhcpserver/handler.go:245-285) and never reads the client Parameter Request List (option 55 is defined at handler.go:52 but parsed nowhere in production), so it does not try to insert requested options in the client's requested order | | [`RFC2132-9.14-1`](#rfc2132-9.14-1) Each client's client-identifier MUST be unique among identifiers on the subnet (§9.14) | no test | no test carries this requirement id; annotated {not-applicable}: the uniqueness obligation binds the client's choice of client-identifier (option 61); ze is a server that keys leases and pool allocations by hardware address/chaddr (extractMAC internal/plugins/dhcpserver/handler.go:456, pool.allocate pool.go:64, leaseTable byMAC lease.go:23) and never reads or generates option 61, so it neither produces client-identifiers nor can enforce cross-client uniqueness | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2132-3.3-1`](#rfc2132-3.3-1) If both subnet mask and router option are specified in a DHCP reply, the subnet mask option MUST be first (§3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestOptionSubnetMaskBeforeRouter`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1331) | unit/verify | unproven | ### [`RFC2132-3.5-1`](#rfc2132-3.5-1) Router option length MUST always be a multiple of 4 (§3.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEmittedIPListOptionLengthsMultipleOfFour`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1354) | unit/verify | unproven | ### [`RFC2132-3.6-1`](#rfc2132-3.6-1) Time server option length MUST always be a multiple of 4 (§3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-3.6-1, so no unit is bound to it. ### [`RFC2132-3.7-1`](#rfc2132-3.7-1) Name server option length MUST always be a multiple of 4 (§3.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-3.7-1, so no unit is bound to it. ### [`RFC2132-3.8-1`](#rfc2132-3.8-1) Domain name server option length MUST always be a multiple of 4 (§3.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEmittedIPListOptionLengthsMultipleOfFour`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1363) | unit/verify | unproven | ### [`RFC2132-3.9-1`](#rfc2132-3.9-1) Log server option length MUST always be a multiple of 4 (§3.9) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-3.9-1, so no unit is bound to it. ### [`RFC2132-3.10-1`](#rfc2132-3.10-1) Cookie server option length MUST always be a multiple of 4 (§3.10) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-3.10-1, so no unit is bound to it. ### [`RFC2132-3.11-1`](#rfc2132-3.11-1) LPR server option length MUST always be a multiple of 4 (§3.11) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-3.11-1, so no unit is bound to it. ### [`RFC2132-3.12-1`](#rfc2132-3.12-1) Impress server option length MUST always be a multiple of 4 (§3.12) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-3.12-1, so no unit is bound to it. ### [`RFC2132-3.13-1`](#rfc2132-3.13-1) Resource location server option length MUST always be a multiple of 4 (§3.13) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-3.13-1, so no unit is bound to it. ### [`RFC2132-8.2-1`](#rfc2132-8.2-1) NIS servers option length MUST be a multiple of 4 (§8.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.2-1, so no unit is bound to it. ### [`RFC2132-8.3-1`](#rfc2132-8.3-1) NTP servers option length MUST be a multiple of 4 (§8.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.3-1, so no unit is bound to it. ### [`RFC2132-8.9-1`](#rfc2132-8.9-1) X Window Font Server option length MUST be a multiple of 4 (§8.9) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.9-1, so no unit is bound to it. ### [`RFC2132-8.10-1`](#rfc2132-8.10-1) X Window Display Manager option length MUST be a multiple of 4 (§8.10) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.10-1, so no unit is bound to it. ### [`RFC2132-8.12-1`](#rfc2132-8.12-1) NIS+ Servers option length MUST be a multiple of 4 (§8.12) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.12-1, so no unit is bound to it. ### [`RFC2132-8.13-1`](#rfc2132-8.13-1) Mobile IP Home Agent option length MUST be a multiple of 4 (§8.13) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.13-1, so no unit is bound to it. ### [`RFC2132-8.14-1`](#rfc2132-8.14-1) SMTP server option length MUST always be a multiple of 4 (§8.14) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.14-1, so no unit is bound to it. ### [`RFC2132-8.15-1`](#rfc2132-8.15-1) POP3 server option length MUST always be a multiple of 4 (§8.15) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.15-1, so no unit is bound to it. ### [`RFC2132-8.16-1`](#rfc2132-8.16-1) NNTP server option length MUST always be a multiple of 4 (§8.16) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.16-1, so no unit is bound to it. ### [`RFC2132-8.17-1`](#rfc2132-8.17-1) WWW server option length MUST always be a multiple of 4 (§8.17) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.17-1, so no unit is bound to it. ### [`RFC2132-8.18-1`](#rfc2132-8.18-1) Finger server option length MUST always be a multiple of 4 (§8.18) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.18-1, so no unit is bound to it. ### [`RFC2132-8.19-1`](#rfc2132-8.19-1) IRC server option length MUST always be a multiple of 4 (§8.19) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.19-1, so no unit is bound to it. ### [`RFC2132-8.20-1`](#rfc2132-8.20-1) StreetTalk server option length MUST always be a multiple of 4 (§8.20) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.20-1, so no unit is bound to it. ### [`RFC2132-8.21-1`](#rfc2132-8.21-1) STDA server option length MUST always be a multiple of 4 (§8.21) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-8.21-1, so no unit is bound to it. ### [`RFC2132-4.7-1`](#rfc2132-4.7-1) Path MTU plateau table option length MUST be a multiple of 2 (§4.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-4.7-1, so no unit is bound to it. ### [`RFC2132-4.3-1`](#rfc2132-4.3-1) Policy filter option length MUST be a multiple of 8 (§4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-4.3-1, so no unit is bound to it. ### [`RFC2132-5.8-1`](#rfc2132-5.8-1) Static route option length MUST be a multiple of 8 (§5.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-5.8-1, so no unit is bound to it. ### [`RFC2132-2-1`](#rfc2132-2-1) Any options defined subsequent to this document MUST contain a length octet even if fixed or zero (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEveryEmittedOptionHasLengthOctet`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1413) | unit/verify | unproven | ### [`RFC2132-2-2`](#rfc2132-2-2) Receiver MUST be prepared to delete trailing nulls from ASCII options (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestASCIIOptionParsingTolerantOfTrailingNull`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1452) | unit/verify | unproven | ### [`RFC2132-2-3`](#rfc2132-2-3) Receiver MUST NOT require that a trailing null be included in ASCII data (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestASCIIOptionParsingTolerantOfTrailingNull`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1444) | unit/verify | unproven | ### [`RFC2132-8.4-1`](#rfc2132-8.4-1) Servers not equipped to interpret vendor-specific information MUST ignore it (§8.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIgnoresClientVendorSpecificOption43`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1501) | unit/verify | unproven | ### [`RFC2132-9.13-1`](#rfc2132-9.13-1) Servers not equipped to interpret class-specific information MUST ignore it (§9.13) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIgnoresUnknownVendorClass`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L1548) | unit/verify | unproven | ### [`RFC2132-9.8-1`](#rfc2132-9.8-1) Server MUST try to insert requested options in the order requested by the client (§9.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-9.8-1, so no unit is bound to it. ### [`RFC2132-9.14-1`](#rfc2132-9.14-1) Each client's client-identifier MUST be unique among identifiers on the subnet (§9.14) Audit verdict: not audited: no reader has judged these tests No test carries RFC2132-9.14-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2132, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2132, so its obligations are stated where they were written. --- ### Page: RFC 2181 - Clarifications to the DNS Specification https://ze-software.net/quality/rfc-compliance/rfc2181/ # RFC 2181 - Clarifications to the DNS Specification Partial. Every requirement this repository extracted from RFC 2181, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 4.3% | 1 of 23 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 47.8% | 11 of 23 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 23 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 13 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 23 | of 70 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 10 | of 23 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 43.5% | 10 of 23 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 23 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 23 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 4.3% | 1 of 23 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 23 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 70 | | Gated MUST-level | 23 | | Not applicable, so out of scope | 10 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 13 | | Tagged units | 13 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2181.md` | | Requirement shard | `rfc/requirements/rfc2181.md` | | RFC text | `rfc/full/rfc2181.txt` | ## Enrolment Enrolled: Clarifications to the DNS Specification (RFC 2181): ze is authoritative (internal/plugins/geodns, internal/plugins/as112) plus a stub resolver+cache (internal/component/resolve/dns). 1 MET (section 8 TTL 0..2147483647 bound, both polarities) + 11 single-polarity positive (UDP reply source-IP/port fidelity 4.1-1/4.2-1/4.2-2, RRSet equal TTLs 5.2-1, cache replaces RRSets without merging 5.4-1/5.4-2, canonical NS targets with A glue 10.3-1/10.3-2, wire label/name limits and unrestricted labels 11-1/11-2/11-3) + 1 gap (5.1-1 no TC/Truncate on oversized RRSet) + 10 not-applicable (SIG/DNSSEC 5.3.1-2/5.3.1-3/5.3.1-4/5.4.1-8/5.4.1-9, recursive-resolver ranking 5.4.1-3, AXFR 5.5-4, CNAME/PTR authoring 10.1-4/10.1.1-1/10.2-1) ## What the public ledger says **Status:** Partial **What the ledger says is covered** - Authoritative GeoDNS/AS112 answers with RRSet-consistent per-record TTLs, the section 8 0..2147483647 TTL bound ([`internal/plugins/geodns/config.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2181.md). **What the ledger says remains** One MUST gap ([`RFC2181-5.1-1`](#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). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 1 | one part of the gated population | | Annotated instead of tested | 22 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **23** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (1):** [`RFC2181-8-1`](#rfc2181-8-1) **Annotated instead of tested (22):** [`RFC2181-4.1-1`](#rfc2181-4.1-1), [`RFC2181-4.2-1`](#rfc2181-4.2-1), [`RFC2181-4.2-2`](#rfc2181-4.2-2), [`RFC2181-5.1-1`](#rfc2181-5.1-1), [`RFC2181-5.2-1`](#rfc2181-5.2-1), [`RFC2181-5.3.1-2`](#rfc2181-5.3.1-2), [`RFC2181-5.3.1-3`](#rfc2181-5.3.1-3), [`RFC2181-5.3.1-4`](#rfc2181-5.3.1-4), [`RFC2181-5.4-1`](#rfc2181-5.4-1), [`RFC2181-5.4-2`](#rfc2181-5.4-2), [`RFC2181-5.4.1-3`](#rfc2181-5.4.1-3), [`RFC2181-5.4.1-8`](#rfc2181-5.4.1-8), [`RFC2181-5.4.1-9`](#rfc2181-5.4.1-9), [`RFC2181-5.5-4`](#rfc2181-5.5-4), [`RFC2181-10.1-4`](#rfc2181-10.1-4), [`RFC2181-10.1.1-1`](#rfc2181-10.1.1-1), [`RFC2181-10.2-1`](#rfc2181-10.2-1), [`RFC2181-10.3-1`](#rfc2181-10.3-1), [`RFC2181-10.3-2`](#rfc2181-10.3-2), [`RFC2181-11-1`](#rfc2181-11-1), [`RFC2181-11-2`](#rfc2181-11-2), [`RFC2181-11-3`](#rfc2181-11-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2181-4.1-1` | When responding to a query over UDP, a server must send the reply with the IP source address set to the address that was in the destination address field of the query packet. (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC2181_UDPReplySourceAndPort`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L315). **negative:** no negative test. **{single-polarity}:** each UDP listener binds one specific IP at internal/core/dnsserver/manager.go:160 so the kernel sources every reply from the query destination address, and ze has no wildcard-bind or explicit-source path that could send from another address | | `RFC2181-4.2-1` | Replies to all queries must be directed to the port from which they were sent. (§4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestRFC2181_UDPReplySourceAndPort`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L320). **negative:** no negative test. **{single-polarity}:** the reply is written on the same socket the query arrived on via internal/core/dnsserver/handler.go:62 so miekg/dns directs it to the query source port, a property ze cannot violate | | `RFC2181-4.2-2` | For queries received by UDP, the server must take note of the source port and use it as the destination port in the response. (§4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestRFC2181_UDPReplySourceAndPort`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L325). **negative:** no negative test. **{single-polarity}:** miekg/dns ServeUDP records the datagram source port and uses it as the reply destination for the write at internal/core/dnsserver/handler.go:62, so ze always answers to the query source port | | `RFC2181-5.1-1` | The response must be marked "truncated" if the entire RRSet will not fit in the response. (§5.1) | MUST | 5.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** geodns and as112 never set the TC bit or call miekg Truncate, and miekg WriteMsg at vendor/github.com/miekg/dns/server.go:747 packs and sends without auto-truncating, so an oversized RRSet would be sent unmarked | | `RFC2181-5.2-1` | The TTLs of all RRs in an RRSet must be the same; in no case may a server send an RRSet with TTLs not all equal. (§5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestRFC2181_RRSetEqualTTL`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L367). **negative:** no negative test. **{single-polarity}:** geodns assigns one TTL per host record set at internal/plugins/geodns/config.go:271 and as112 uses fixed per-zone TTL constants, so an emitted RRSet never carries unequal TTLs and no code path can produce one | | `RFC2181-5.3.1-2` | Where SIG records are returned in the answer section (a query for SIG records, or type=ANY), the entire SIG RRSet must be included, as for any other RR type. (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no SIG or DNSSEC records -- geodns emits only A/AAAA/SRV at internal/plugins/geodns/record.go:10 and as112 only SOA/NS/TXT, so there is no SIG RRSet to include | | `RFC2181-5.3.1-3` | A server receiving SIG records in the authority section (or, probably incorrectly, as additional data) must understand that the entire RRSet has almost certainly not been included. (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's stub resolver extracts only answer-section A/AAAA/TXT/PTR/CNAME/MX/NS/SRV at internal/component/resolve/dns/resolver.go:299 and processes no SIG records, so there is no partial SIG RRSet to reason about | | `RFC2181-5.3.1-4` | Such a server must not cache that SIG record in a way that would permit it to be returned in response to a query for SIG records. (§5.3.1) | MUST NOT | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the resolver caches only records it extracted from the answer section at internal/component/resolve/dns/resolver.go:299 and never handles SIG, so an authority-section SIG can never be cached or returned | | `RFC2181-5.4-1` | Servers must never merge RRs from a response with RRs in their cache to form an RRSet. (§5.4) | MUST NOT | 5.4 | **positive:** `unit/verify` [`TestRFC2181_CacheReplacesRRSetNoMerge`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/cache_test.go#L226). **negative:** no negative test. **{single-polarity}:** the resolver cache replaces the whole RRSet for a name+type at internal/component/resolve/dns/cache.go:145 by removing the existing entry before storing the new records, so response RRs are never merged with cached ones | | `RFC2181-5.4-2` | When a response would form an RRSet with cached data, the server must either ignore the response RRs or discard the entire cached RRSet, as appropriate. (§5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestRFC2181_CacheReplacesRRSetNoMerge`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/cache_test.go#L234). **negative:** no negative test. **{single-polarity}:** put discards the entire cached RRSet before storing the new answer at internal/component/resolve/dns/cache.go:145, taking the discard-cached branch of the rule rather than merging | | `RFC2181-5.4.1-3` | Data trustworthiness shall rank, most to least: primary zone file (non-glue), zone transfer (non-glue), authoritative answer-section data, authority-section data of an authoritative answer, glue, non-authoritative answer data, then additional information and non-authoritative authority-section data. (§5.4.1) | SHALL | 5.4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's resolver is a stub forwarder to one configured upstream at internal/component/resolve/dns/resolver.go:263 with a single-source cache keyed by name+type, so it never ranks data from competing trustworthiness sources | | `RFC2181-5.4.1-8` | When DNS security is in use and an authenticated reply has been received and verified, the authenticated data shall be considered more trustworthy than unauthenticated data of the same type. (§5.4.1) | SHALL | 5.4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the stub resolver performs no per-record trust ranking -- it relies on a validating upstream returning SERVFAIL at internal/component/resolve/dns/resolver.go:99 rather than comparing authenticated against unauthenticated data | | `RFC2181-5.4.1-9` | DNSSEC-aware servers must still correctly set the AA bit in responses, to enable correct operation with servers that are not security aware. (§5.4.1) | MUST | 5.4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers implement no DNSSEC signing so the DNSSEC-aware precondition does not hold; the AA bit is nonetheless always set at internal/core/dnsserver/handler.go:73 | | `RFC2181-5.5-4` | Where a duplicate RRSet is required (e.g. the SOA at the first and last record of an AXFR), the TTL transmitted in each case must be the same. (§5.5) | MUST | 5.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no AXFR -- the geodns YANG notes this at internal/plugins/geodns/yang/ze-geodns-conf.yang:95 and no plugin emits an SOA twice in one message, so no duplicate RRSet arises | | `RFC2181-8-1` | A TTL is an unsigned number in the range 0..2147483647 (2^31 - 1); when transmitted it shall be encoded in the less significant 31 bits of the 32-bit TTL field, with the most significant (sign) bit set to zero. (§8) | SHALL | 8 | **positive:** `unit/verify` [`TestRFC2181_TTLSignBitBound`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/config_test.go#L225). **negative:** `unit/verify` [`TestRFC2181_TTLSignBitBound`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/config_test.go#L238) | | `RFC2181-10.1-4` | An alias (the label of a CNAME record) may have no data other than SIG, NXT, and KEY RRs; a CNAME must not coexist with any other data. (§10.1) | MUST NOT | 10.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no CNAME records -- geodns emits only A/AAAA/SRV at internal/plugins/geodns/record.go:10 and as112 only SOA/NS/TXT, so no CNAME can coexist with other data | | `RFC2181-10.1.1-1` | Care must be taken to be very clear whether the label or the value (the canonical name) of a CNAME resource record is intended. (§10.1.1) | MUST | 10.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze authors no CNAME records at internal/plugins/geodns/record.go:10, so there is no label-versus-canonical-name ambiguity for an implementation to resolve | | `RFC2181-10.2-1` | The value of a PTR record must not be an alias; it should be a canonical name. (§10.2) | MUST NOT | 10.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze authors no PTR records -- geodns serves A/AAAA/SRV and as112 reverse zones return NODATA with the SOA at internal/plugins/as112/zones.go:273 rather than any PTR, so no PTR value can be an alias | | `RFC2181-10.3-1` | The domain name used as the value of an NS record, or as part of the value of an MX record, must not be an alias, and must never have a CNAME RR. (§10.3) | MUST NOT | 10.3 | **positive:** `unit/verify` [`TestRFC2181_NSCanonicalWithGlue`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L407). **negative:** no negative test. **{single-polarity}:** geodns synthesizes NS targets as canonical ns<n>.<zone> names at internal/plugins/geodns/server.go:154 and as112 uses fixed canonical names, so ze never emits a CNAME as an NS or MX value and serves no MX at all | | `RFC2181-10.3-2` | That domain name must have as its value one or more address records. (§10.3) | MUST | 10.3 | **positive:** `unit/verify` [`TestRFC2181_NSCanonicalWithGlue`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L424). **negative:** no negative test. **{single-polarity}:** geodns emits A glue for every synthesized NS target at internal/plugins/geodns/server.go:161, and as112 NS targets are canonical names whose address records are authoritative elsewhere, so the target name always has address records | | `RFC2181-11-1` | Any one label is limited to between 1 and 63 octets; a full domain name is limited to 255 octets, including the separators. (§11) | MUST | 11 | **positive:** `unit/verify` [`TestRFC2181_WireNameLimits`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L451). **negative:** no negative test. **{single-polarity}:** the DNS wire codec ze packs and unpacks through rejects a label of 64+ octets at vendor/github.com/miekg/dns/msg.go:281 and caps a name at 255, and geodns/as112 emit only short synthetic names, so no over-limit name is produced | | `RFC2181-11-2` | Implementations of the DNS protocols must not place any restrictions on the labels that can be used. (§11) | MUST NOT | 11 | **positive:** `unit/verify` [`TestRFC2181_LabelsUnrestricted`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/config_test.go#L271). **negative:** no negative test. **{single-polarity}:** geodns applies no label-content restriction -- parseHost at internal/plugins/geodns/config.go:270 accepts any label characters and only requires a configured-zone suffix, so underscore and other non-hostname labels are served | | `RFC2181-11-3` | DNS servers must not refuse to serve a zone because it contains labels that might not be acceptable to some DNS client programs. (§11) | MUST NOT | 11 | **positive:** `unit/verify` [`TestRFC2181_LabelsUnrestricted`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/config_test.go#L260). **negative:** no negative test. **{single-polarity}:** geodns never refuses a zone for questionable labels -- config parsing at internal/plugins/geodns/config.go:246 rejects only a missing zone suffix or an invalid IP, never label characters | | `RFC2181-4.1-3` | If the required source address is not permitted for this purpose, the legal source address chosen should be one that maximises the possibility that the client can use it for further queries. (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-4.2-3` | Replies should always be sent from the port to which they were directed. (§4.2) | SHOULD | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5-1` | Servers should suppress duplicate RRs (equal label, class, type, and data) if encountered. (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.2-2` | A client that receives a response containing an RRSet whose RRs have differing TTLs should treat this as an error. (§5.2) | SHOULD | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.2-3` | If such an RRSet is from a non-authoritative source, the client should ignore the RRSet and, if the values are required, seek to acquire them from an authoritative source. (§5.2) | SHOULD | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.2-4` | Clients configured to send all queries to one or more particular servers should treat those servers as authoritative for this purpose. (§5.2) | SHOULD | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.2-5` | Should an authoritative source send such a malformed RRSet, the client should treat all its RRs as if every TTL had been set to the value of the lowest TTL in the RRSet. (§5.2) | SHOULD | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4-4` | A server should update a cached RRSet's TTL from an identical received answer only if that answer would be considered more authoritative than the previously cached answer. (§5.4) | SHOULD | 5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-1` | When deciding whether to accept a reply's RRSet or retain one already cached, a server should consider the relative likely trustworthiness of the various data. (§5.4.1) | SHOULD | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-2` | An authoritative answer from a reply should replace cached data that had been obtained from additional information in an earlier reply. (§5.4.1) | SHOULD | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-4` | Clients should assume that records other than the alias record in an authoritative answer may have come from the server's cache. (§5.4.1) | SHOULD | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-5` | Where authoritative answers are required, the client should query again using the canonical name associated with the alias. (§5.4.1) | SHOULD | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-6` | Unauthenticated RRs cached from the least trustworthy groupings (additional data, and the authority section of a non-authoritative answer) should not be cached in such a way that they would ever be returned as answers to a received query. (§5.4.1) | SHOULD NOT | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-10` | Where glue for the same name exists in multiple zones and differs in value, the nameserver should select data from a primary zone file in preference to secondary. (§5.4.1) | SHOULD | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-12` | Where a server can detect from two zone files that one or more are incorrectly configured so as to create conflicts, it should refuse to load the zones determined to be erroneous and issue suitable diagnostics. (§5.4.1) | SHOULD | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.5-1` | A Resource Record Set should only be included once in any DNS reply. (§5.5) | SHOULD | 5.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.5-3` | An RRSet should not be repeated in the same or any other section, except where explicitly required by a specification. (§5.5) | SHOULD NOT | 5.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-6.1-1` | A server for a zone should not return authoritative answers for queries related to names in another zone (including the NS, and perhaps A, records at a zone cut) unless it also happens to be a server for the other zone. (§6.1) | SHOULD NOT | 6.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-6.1-2` | Servers should ignore data other than NS records, and the A records necessary to locate the servers listed in those NS records, that may happen to be configured in a zone at a zone cut. (§6.1) | SHOULD | 6.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-6.2-2` | Where a subzone is secure, its KEY and SIG records should also always be present in the parent zone (if secure). (§6.2) | SHOULD | 6.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-6.2-3` | In none of these zone-cut cases should a server for the parent zone, not also being a server for the subzone, set the AA bit in any response for a label at a zone cut. (§6.2) | SHOULD NOT | 6.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-7.2-1` | Implementations should not assume that SOA records will have a TTL of zero. (§7.2) | SHOULD NOT | 7.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-7.3-1` | The MNAME field of the SOA record should contain the name of the primary (master) server for the zone identified by the SOA. (§7.3) | SHOULD | 7.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-7.3-2` | The SOA MNAME field should not contain the name of the zone itself. (§7.3) | SHOULD NOT | 7.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-8-2` | Implementations should treat TTL values received with the most significant bit set as if the entire value received was zero. (§8) | SHOULD | 8 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-9-1` | The TC bit should be set in responses only when an RRSet is required as part of the response but could not be included in its entirety. (§9) | SHOULD | 9 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-9-2` | The TC bit should not be set merely because some extra information (including additional-section processing) could have been included but there was insufficient room. (§9) | SHOULD NOT | 9 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-9-3` | In such cases the entire RRSet that will not fit should be omitted and the reply sent as is, with the TC bit clear. (§9) | SHOULD | 9 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-9-5` | When a client receives a reply with TC set, it should ignore that response and query again using a mechanism, such as a TCP connection, that will permit larger replies. (§9) | SHOULD | 9 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-10.1-2` | The canonical name given by a CNAME record should generally be a name that exists elsewhere in the DNS. (§10.1) | SHOULD | 10.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-10.2-2` | No restriction that only one PTR record is permitted for a name should be inferred. (§10.2) | SHOULD NOT | 10.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-11-5` | Warning about, or refusing to load, a primary zone because of questionable labels should not happen by default. (§11) | SHOULD NOT | 11 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-4.1-2` | If setting the required source address is not permitted for this purpose, the response may be sent from any legal IP address allocated to the server. (§4.1) | MAY | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.3.1-1` | The authority section may contain only those SIG RRs whose "type covered" field equals the type field of an answer being returned. (§5.3.1) | MAY | 5.3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.3.2-1` | Servers are not required to treat two differing NXT RRSets as a special case; they may elect to notice the two NXT RRSets and treat them as they would any two different RRSets (cache one, ignore the other). (§5.3.2) | MAY | 5.3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4-3` | When a received answer contains an RRSet identical to the cached one except for the TTL value, the server may optionally update the TTL in its cache with the TTL of the received answer. (§5.4) | MAY | 5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-7` | Such untrustworthy RRs may be returned as additional information where appropriate. (§5.4.1) | MAY | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.4.1-11` | Where conflicting glue exists, the nameserver may otherwise choose any single set of such data. (§5.4.1) | MAY | 5.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-5.5-2` | An RRSet may occur in any of the Answer, Authority, or Additional Information sections, as required. (§5.5) | MAY | 5.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-6.2-1` | Servers may, but are not required to, retain all differing NXT records they receive, regardless of the rules in section 5.4. (§6.2) | MAY | 6.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-7.1-1` | The authority section of an authoritative answer may contain the SOA record for the zone; SOA records, if added, are to be placed in the authority section. (§7.1) | MAY | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-7.2-2` | Implementations are not required to send SOA records with a TTL of zero (they may send a non-zero SOA TTL). (§7.2) | MAY | 7.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-8-3` | Implementations are always free to place an upper bound on any received TTL and treat any larger values as if they were that upper bound. (§8) | MAY | 8 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-9-4` | Where TC is set, the partial RRSet that would not completely fit may be left in the response. (§9) | MAY | 9 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-10.1-1` | There may be only one canonical name for any one alias. (§10.1) | MAY | 10.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-10.1-3` | An alias (the label of a CNAME record) may, if DNSSEC is in use, have SIG, NXT, and KEY RRs. (§10.1) | MAY | 10.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2181-11-4` | A DNS server may be configurable to issue warnings when loading, or even to refuse to load, a primary zone containing labels that might be considered questionable. (§11) | MAY | 11 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2181-5.1-1`](#rfc2181-5.1-1) The response must be marked "truncated" if the entire RRSet will not fit in the response. (§5.1) | {gap}, no test | geodns and as112 never set the TC bit or call miekg Truncate, and miekg WriteMsg at vendor/github.com/miekg/dns/server.go:747 packs and sends without auto-truncating, so an oversized RRSet would be sent unmarked | | [`RFC2181-5.3.1-2`](#rfc2181-5.3.1-2) Where SIG records are returned in the answer section (a query for SIG records, or type=ANY), the entire SIG RRSet must be included, as for any other RR type. (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no SIG or DNSSEC records -- geodns emits only A/AAAA/SRV at internal/plugins/geodns/record.go:10 and as112 only SOA/NS/TXT, so there is no SIG RRSet to include | | [`RFC2181-5.3.1-3`](#rfc2181-5.3.1-3) A server receiving SIG records in the authority section (or, probably incorrectly, as additional data) must understand that the entire RRSet has almost certainly not been included. (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's stub resolver extracts only answer-section A/AAAA/TXT/PTR/CNAME/MX/NS/SRV at internal/component/resolve/dns/resolver.go:299 and processes no SIG records, so there is no partial SIG RRSet to reason about | | [`RFC2181-5.3.1-4`](#rfc2181-5.3.1-4) Such a server must not cache that SIG record in a way that would permit it to be returned in response to a query for SIG records. (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: the resolver caches only records it extracted from the answer section at internal/component/resolve/dns/resolver.go:299 and never handles SIG, so an authority-section SIG can never be cached or returned | | [`RFC2181-5.4.1-3`](#rfc2181-5.4.1-3) Data trustworthiness shall rank, most to least: primary zone file (non-glue), zone transfer (non-glue), authoritative answer-section data, authority-section data of an authoritative answer, glue, non-authoritative answer data, then additional information and non-authoritative authority-section data. (§5.4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's resolver is a stub forwarder to one configured upstream at internal/component/resolve/dns/resolver.go:263 with a single-source cache keyed by name+type, so it never ranks data from competing trustworthiness sources | | [`RFC2181-5.4.1-8`](#rfc2181-5.4.1-8) When DNS security is in use and an authenticated reply has been received and verified, the authenticated data shall be considered more trustworthy than unauthenticated data of the same type. (§5.4.1) | no test | no test carries this requirement id; annotated {not-applicable}: the stub resolver performs no per-record trust ranking -- it relies on a validating upstream returning SERVFAIL at internal/component/resolve/dns/resolver.go:99 rather than comparing authenticated against unauthenticated data | | [`RFC2181-5.4.1-9`](#rfc2181-5.4.1-9) DNSSEC-aware servers must still correctly set the AA bit in responses, to enable correct operation with servers that are not security aware. (§5.4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers implement no DNSSEC signing so the DNSSEC-aware precondition does not hold; the AA bit is nonetheless always set at internal/core/dnsserver/handler.go:73 | | [`RFC2181-5.5-4`](#rfc2181-5.5-4) Where a duplicate RRSet is required (e.g. the SOA at the first and last record of an AXFR), the TTL transmitted in each case must be the same. (§5.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no AXFR -- the geodns YANG notes this at internal/plugins/geodns/yang/ze-geodns-conf.yang:95 and no plugin emits an SOA twice in one message, so no duplicate RRSet arises | | [`RFC2181-10.1-4`](#rfc2181-10.1-4) An alias (the label of a CNAME record) may have no data other than SIG, NXT, and KEY RRs; a CNAME must not coexist with any other data. (§10.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no CNAME records -- geodns emits only A/AAAA/SRV at internal/plugins/geodns/record.go:10 and as112 only SOA/NS/TXT, so no CNAME can coexist with other data | | [`RFC2181-10.1.1-1`](#rfc2181-10.1.1-1) Care must be taken to be very clear whether the label or the value (the canonical name) of a CNAME resource record is intended. (§10.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze authors no CNAME records at internal/plugins/geodns/record.go:10, so there is no label-versus-canonical-name ambiguity for an implementation to resolve | | [`RFC2181-10.2-1`](#rfc2181-10.2-1) The value of a PTR record must not be an alias; it should be a canonical name. (§10.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze authors no PTR records -- geodns serves A/AAAA/SRV and as112 reverse zones return NODATA with the SOA at internal/plugins/as112/zones.go:273 rather than any PTR, so no PTR value can be an alias | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2181-4.1-1`](#rfc2181-4.1-1) When responding to a query over UDP, a server must send the reply with the IP source address set to the address that was in the destination address field of the query packet. (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_UDPReplySourceAndPort`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L315) | unit/verify | unproven | ### [`RFC2181-4.2-1`](#rfc2181-4.2-1) Replies to all queries must be directed to the port from which they were sent. (§4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_UDPReplySourceAndPort`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L320) | unit/verify | unproven | ### [`RFC2181-4.2-2`](#rfc2181-4.2-2) For queries received by UDP, the server must take note of the source port and use it as the destination port in the response. (§4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_UDPReplySourceAndPort`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L325) | unit/verify | unproven | ### [`RFC2181-5.1-1`](#rfc2181-5.1-1) The response must be marked "truncated" if the entire RRSet will not fit in the response. (§5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-5.1-1, so no unit is bound to it. ### [`RFC2181-5.2-1`](#rfc2181-5.2-1) The TTLs of all RRs in an RRSet must be the same; in no case may a server send an RRSet with TTLs not all equal. (§5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_RRSetEqualTTL`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L367) | unit/verify | unproven | ### [`RFC2181-5.3.1-2`](#rfc2181-5.3.1-2) Where SIG records are returned in the answer section (a query for SIG records, or type=ANY), the entire SIG RRSet must be included, as for any other RR type. (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-5.3.1-2, so no unit is bound to it. ### [`RFC2181-5.3.1-3`](#rfc2181-5.3.1-3) A server receiving SIG records in the authority section (or, probably incorrectly, as additional data) must understand that the entire RRSet has almost certainly not been included. (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-5.3.1-3, so no unit is bound to it. ### [`RFC2181-5.3.1-4`](#rfc2181-5.3.1-4) Such a server must not cache that SIG record in a way that would permit it to be returned in response to a query for SIG records. (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-5.3.1-4, so no unit is bound to it. ### [`RFC2181-5.4-1`](#rfc2181-5.4-1) Servers must never merge RRs from a response with RRs in their cache to form an RRSet. (§5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_CacheReplacesRRSetNoMerge`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/cache_test.go#L226) | unit/verify | unproven | ### [`RFC2181-5.4-2`](#rfc2181-5.4-2) When a response would form an RRSet with cached data, the server must either ignore the response RRs or discard the entire cached RRSet, as appropriate. (§5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_CacheReplacesRRSetNoMerge`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/cache_test.go#L234) | unit/verify | unproven | ### [`RFC2181-5.4.1-3`](#rfc2181-5.4.1-3) Data trustworthiness shall rank, most to least: primary zone file (non-glue), zone transfer (non-glue), authoritative answer-section data, authority-section data of an authoritative answer, glue, non-authoritative answer data, then additional information and non-authoritative authority-section data. (§5.4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-5.4.1-3, so no unit is bound to it. ### [`RFC2181-5.4.1-8`](#rfc2181-5.4.1-8) When DNS security is in use and an authenticated reply has been received and verified, the authenticated data shall be considered more trustworthy than unauthenticated data of the same type. (§5.4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-5.4.1-8, so no unit is bound to it. ### [`RFC2181-5.4.1-9`](#rfc2181-5.4.1-9) DNSSEC-aware servers must still correctly set the AA bit in responses, to enable correct operation with servers that are not security aware. (§5.4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-5.4.1-9, so no unit is bound to it. ### [`RFC2181-5.5-4`](#rfc2181-5.5-4) Where a duplicate RRSet is required (e.g. the SOA at the first and last record of an AXFR), the TTL transmitted in each case must be the same. (§5.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-5.5-4, so no unit is bound to it. ### [`RFC2181-8-1`](#rfc2181-8-1) A TTL is an unsigned number in the range 0..2147483647 (2^31 - 1); when transmitted it shall be encoded in the less significant 31 bits of the 32-bit TTL field, with the most significant (sign) bit set to zero. (§8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2181_TTLSignBitBound`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/config_test.go#L238) | unit/verify | unproven | | positive | [`TestRFC2181_TTLSignBitBound`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/config_test.go#L225) | unit/verify | unproven | ### [`RFC2181-10.1-4`](#rfc2181-10.1-4) An alias (the label of a CNAME record) may have no data other than SIG, NXT, and KEY RRs; a CNAME must not coexist with any other data. (§10.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-10.1-4, so no unit is bound to it. ### [`RFC2181-10.1.1-1`](#rfc2181-10.1.1-1) Care must be taken to be very clear whether the label or the value (the canonical name) of a CNAME resource record is intended. (§10.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-10.1.1-1, so no unit is bound to it. ### [`RFC2181-10.2-1`](#rfc2181-10.2-1) The value of a PTR record must not be an alias; it should be a canonical name. (§10.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2181-10.2-1, so no unit is bound to it. ### [`RFC2181-10.3-1`](#rfc2181-10.3-1) The domain name used as the value of an NS record, or as part of the value of an MX record, must not be an alias, and must never have a CNAME RR. (§10.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_NSCanonicalWithGlue`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L407) | unit/verify | unproven | ### [`RFC2181-10.3-2`](#rfc2181-10.3-2) That domain name must have as its value one or more address records. (§10.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_NSCanonicalWithGlue`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L424) | unit/verify | unproven | ### [`RFC2181-11-1`](#rfc2181-11-1) Any one label is limited to between 1 and 63 octets; a full domain name is limited to 255 octets, including the separators. (§11) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_WireNameLimits`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/server_test.go#L451) | unit/verify | unproven | ### [`RFC2181-11-2`](#rfc2181-11-2) Implementations of the DNS protocols must not place any restrictions on the labels that can be used. (§11) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_LabelsUnrestricted`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/config_test.go#L271) | unit/verify | unproven | ### [`RFC2181-11-3`](#rfc2181-11-3) DNS servers must not refuse to serve a zone because it contains labels that might not be acceptable to some DNS client programs. (§11) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2181_LabelsUnrestricted`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/config_test.go#L260) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 2181, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2181, so its obligations are stated where they were written. --- ### Page: RFC 2205 - Resource ReSerVation Protocol (RSVP) -- Version 1 Functional Specification https://ze-software.net/quality/rfc-compliance/rfc2205/ # RFC 2205 - Resource ReSerVation Protocol (RSVP) -- Version 1 Functional Specification Experimental. Every requirement this repository extracted from RFC 2205, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 33.3% | 2 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 33.3% | 2 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 7 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 10 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 33.3% | 2 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 10 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 0 | | Declared gaps | 2 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 10 | | Tagged units | 7 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2205.md` | | Requirement shard | `rfc/requirements/rfc2205.md` | | RFC text | `rfc/full/rfc2205.txt` | ## Enrolment Enrolled: RSVP version 1 base protocol (codec shared by ze's RSVP-TE plugin): six MUST-level requirements. 3.1-1 (Version MUST be 1) is met with positive+negative tags (encode round-trip and bad-version decode reject, internal/plugins/rsvpte/wire.go). 3.1-2 (reserved octet 0 on send) and 3.1.2-1 (object length multiple of 4) are {single-polarity: positive} with send-side tests (wire.go and the object encoders). 3.10-1 (reject an unknown Class-Num of the form 0bbbbbbb) is met with positive+negative tags: DecodeMessage classifies by the high-order bit (classifyUnknownClass, wire.go) and engine.rejectUnknownObject answers a PATH with Error Code 13. 3.1-3 (verify checksum on receipt) and x-1 (IP Router Alert in PATH) are {gap}: ze's receive path (wire.go DecodeHeader) and raw-socket send (transport_linux.go) omit these. Disclosed in the docs/features/rfc-status.md RFC 2205 row. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** - RSVP base common-header and object codec used by RSVP-TE: Version-1 header enforced on decode, reserved octet zeroed on send, every emitted object length a multiple of 4 - tests bound per requirement in [`rfc/requirements/rfc2205.md`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc2205.md). **What the ledger says remains:** Two MUST gaps gated in [`rfc/short/rfc2205.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2205.md): the receive path does not verify the RSVP checksum or drop bad-checksum messages (3.1); and PATH is sent without the IP Router Alert option. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC2205-3.1-1`](#rfc2205-3.1-1), [`RFC2205-3.10-1`](#rfc2205-3.10-1) **Annotated instead of tested (4):** [`RFC2205-3.1-2`](#rfc2205-3.1-2), [`RFC2205-3.1.2-1`](#rfc2205-3.1.2-1), [`RFC2205-3.1-3`](#rfc2205-3.1-3), [`RFC2205-x-1`](#rfc2205-x-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2205-3.1-1` | Version field MUST be 1 (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRSVPHeaderRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L405). **negative:** `unit/verify` [`TestRSVPDecodeHeaderBadVersion`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L439) | | `RFC2205-3.1-2` | Reserved field in common header MUST be zero (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRSVPReservedByteZeroOnSend`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L581). **negative:** no negative test. **{single-polarity}:** ze sets the reserved byte to 0 on send (internal/plugins/rsvpte/wire.go:177) and the RFC does not require receivers to reject a nonzero reserved field, so no negative case exists | | `RFC2205-3.1.2-1` | Object lengths MUST be a multiple of 4 (§3.1.2) | MUST | 3.1.2 | **positive:** `unit/verify` [`TestRSVPObjectLengthMultipleOfFour`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L602). **negative:** no negative test. **{single-polarity}:** every RSVP object encoder in internal/plugins/rsvpte/wire.go emits a length that is a multiple of 4; the receive path does not enforce %4, so the reject/negative polarity has no code path | | `RFC2205-3.1-3` | Checksum MUST be verified on receipt; drop messages with bad checksum (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze computes the RFC 2205 checksum on send (internal/plugins/rsvpte/build.go:48 internetChecksum) but the receive path (internal/plugins/rsvpte/wire.go:190 DecodeHeader / DecodeMessage) does not verify it or drop bad-checksum messages | | `RFC2205-x-1` | IP Router Alert option MUST be set in PATH messages (Transport) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze sends PATH over a raw protocol-46 socket (internal/plugins/rsvpte/transport_linux.go:35-69) and never sets the IP Router Alert option | | `RFC2205-3.10-1` | Unknown Class-Num of the form 0bbbbbbb: reject the entire message and return an "Unknown Object Class" error (§3.10) | MUST | 3.10 | **positive:** `unit/verify` [`TestDecodeUnknownObjectClass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L718). **negative:** `unit/verify` [`TestDecodeUnknownObjectClass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L725). **negative:** `unit/verify` [`TestEnginePathWithIgnorableObjectAccepted`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L935) | | `RFC2205-3.7-1` | Refresh period SHOULD be jittered by +/- 50% of R to prevent synchronization (§3.7) | SHOULD | 3.7 | **positive:** no positive test. **negative:** no negative test | | `RFC2205-x-2` | Jitter: SHOULD randomize refresh timing (Soft-State Model) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC2205-3.10-2` | Unknown Class-Num of the form 10bbbbbb: ignore the object, neither forwarding it nor sending an error message (§3.10) | MAY | 3.10 | **positive:** no positive test. **negative:** no negative test | | `RFC2205-3.10-3` | Unknown Class-Num of the form 11bbbbbb: ignore the object but forward it unexamined and unmodified in every message resulting from this one (§3.10) | MAY | 3.10 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2205-3.1-3`](#rfc2205-3.1-3) Checksum MUST be verified on receipt; drop messages with bad checksum (§3.1) | {gap}, no test | ze computes the RFC 2205 checksum on send (internal/plugins/rsvpte/build.go:48 internetChecksum) but the receive path (internal/plugins/rsvpte/wire.go:190 DecodeHeader / DecodeMessage) does not verify it or drop bad-checksum messages | | [`RFC2205-x-1`](#rfc2205-x-1) IP Router Alert option MUST be set in PATH messages (Transport) | {gap}, no test | ze sends PATH over a raw protocol-46 socket (internal/plugins/rsvpte/transport_linux.go:35-69) and never sets the IP Router Alert option | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2205-3.1-1`](#rfc2205-3.1-1) Version field MUST be 1 (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRSVPDecodeHeaderBadVersion`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L439) | unit/verify | unproven | | positive | [`TestRSVPHeaderRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L405) | unit/verify | unproven | ### [`RFC2205-3.1-2`](#rfc2205-3.1-2) Reserved field in common header MUST be zero (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRSVPReservedByteZeroOnSend`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L581) | unit/verify | unproven | ### [`RFC2205-3.1.2-1`](#rfc2205-3.1.2-1) Object lengths MUST be a multiple of 4 (§3.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRSVPObjectLengthMultipleOfFour`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L602) | unit/verify | unproven | ### [`RFC2205-3.1-3`](#rfc2205-3.1-3) Checksum MUST be verified on receipt; drop messages with bad checksum (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2205-3.1-3, so no unit is bound to it. ### [`RFC2205-x-1`](#rfc2205-x-1) IP Router Alert option MUST be set in PATH messages (Transport) Audit verdict: not audited: no reader has judged these tests No test carries RFC2205-x-1, so no unit is bound to it. ### [`RFC2205-3.10-1`](#rfc2205-3.10-1) Unknown Class-Num of the form 0bbbbbbb: reject the entire message and return an "Unknown Object Class" error (§3.10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEnginePathWithIgnorableObjectAccepted`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L935) | unit/verify | unproven | | negative | [`TestDecodeUnknownObjectClass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L725) | unit/verify | unproven | | positive | [`TestDecodeUnknownObjectClass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L718) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 2205, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2205, so its obligations are stated where they were written. --- ### Page: RFC 2328 - OSPF Version 2 https://ze-software.net/quality/rfc-compliance/rfc2328/ # RFC 2328 - OSPF Version 2 Partial. Every requirement this repository extracted from RFC 2328, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 96.0% | 24 of 25 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 25 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 25 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 59 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 25 | of 38 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 25 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 25 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 25 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 25 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 4.0% | 1 of 25 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 25 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 38 | | Gated MUST-level | 25 | | Not applicable, so out of scope | 0 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 61 | | Tagged units | 59 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2328.md` | | Requirement shard | `rfc/requirements/rfc2328.md` | | RFC text | `rfc/full/rfc2328.txt` | ## Enrolment Enrolled: OSPF Version 2 ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2328.md). **What the ledger says remains** One MUST gap, annotated in [`rfc/short/rfc2328.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2328.md) and gated by `./le rfc check`: [`RFC2328-13.3-2`](#rfc2328-13.3-2) -- the InfTransDelay increment of LS age is applied on retransmission ([`internal/plugins/ospf/lsdb/flooding.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding.go)) and on a direct database-copy reply ([`internal/plugins/ospf/lsdb/flooding.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding.go)), but the normal flood path copies the LSA with transmit delay 0 (`floodExcept` -> `entry.LSA(d.now())` -> `Raw(now, 0)`, [`internal/plugins/ospf/lsdb/flooding.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding.go) and [`internal/plugins/ospf/lsdb/entry.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/entry.go)), so a first-flooded LSA carries an unincremented age. The feature also remains pre-production pending hardening and deployment evidence. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 24 | one part of the gated population | | Annotated instead of tested | 1 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **25** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (24):** [`RFC2328-A.3.1-1`](#rfc2328-a.3.1-1), [`RFC2328-A.3.1-2`](#rfc2328-a.3.1-2), [`RFC2328-12.1.7-1`](#rfc2328-12.1.7-1), [`RFC2328-13-1`](#rfc2328-13-1), [`RFC2328-13-2`](#rfc2328-13-2), [`RFC2328-13-3`](#rfc2328-13-3), [`RFC2328-13.1-1`](#rfc2328-13.1-1), [`RFC2328-13-4`](#rfc2328-13-4), [`RFC2328-13.3-1`](#rfc2328-13.3-1), [`RFC2328-14-1`](#rfc2328-14-1), [`RFC2328-14-2`](#rfc2328-14-2), [`RFC2328-13.5-1`](#rfc2328-13.5-1), [`RFC2328-13.4-1`](#rfc2328-13.4-1), [`RFC2328-16.1-1`](#rfc2328-16.1-1), [`RFC2328-16.4-1`](#rfc2328-16.4-1), [`RFC2328-16.2-1`](#rfc2328-16.2-1), [`RFC2328-16.2-2`](#rfc2328-16.2-2), [`RFC2328-D.2-1`](#rfc2328-d.2-1), [`RFC2328-D.3-1`](#rfc2328-d.3-1), [`RFC2328-D.3-2`](#rfc2328-d.3-2), [`RFC2328-A.3.3-1`](#rfc2328-a.3.3-1), [`RFC2328-10.1-1`](#rfc2328-10.1-1), [`RFC2328-10.2-1`](#rfc2328-10.2-1), [`RFC2328-C.3-1`](#rfc2328-c.3-1) **Annotated instead of tested (1):** [`RFC2328-13.3-2`](#rfc2328-13.3-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2328-A.3.1-1` | All OSPF packets begin with the standard 24-byte header; Version # MUST be 2 (§A.3.1) | MUST | A.3.1 | **positive:** `unit/verify` [`TestOSPFHeaderRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/header_test.go#L131). **negative:** `unit/verify` [`TestOSPFHeaderRejectsBadVersionAndLength`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/header_test.go#L153) | | `RFC2328-A.3.1-2` | Compute the packet header IP checksum over the whole packet excluding the 64-bit authentication field (§A.3.1, §D.4) | MUST | A.3.1 | **positive:** `unit/verify` [`TestOSPFPacketChecksumExcludesAuth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L32). **negative:** `unit/verify` [`TestPacketVerifyChecksum`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/header_test.go#L100) | | `RFC2328-12.1.7-1` | Compute the LS (Fletcher) checksum over the complete LSA excluding the LS age field; the LS checksum MUST NOT be zero (calculation is not optional) (§12.1.7) | MUST | 12.1.7 | **positive:** `unit/verify` [`TestOSPFLSAChecksum`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L75). **positive:** `unit/verify` [`TestOSPFLSAChecksumExcludesAge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L102). **negative:** `unit/verify` [`TestRFC2328ZeroLSChecksumRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/rfc2328_test.go#L10) | | `RFC2328-13-1` | In the flooding procedure, discard an LSA with an invalid LS checksum and discard an LSA of unknown LS type (only types 1-5 are defined) (§13) | MUST | 13 | **positive:** `unit/verify` [`TestOSPFFloodOutOtherInterfaces`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L30). **negative:** `unit/verify` [`TestDecodeLSReqRejectsMalformed`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/packet_body_test.go#L145). **negative:** `unit/verify` [`TestRFC2328BadLSChecksumDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc2328_test.go#L17) | | `RFC2328-13-2` | Flood AS-external (Type 5) LSAs into or throughout a stub area (§13, §3.6) | MUST NOT | 13 | **positive:** `unit/verify` [`TestOSPFStubFloodFilter`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/area_type_test.go#L15). **negative:** `unit/verify` [`TestOSPFStubAreaDropsType5`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L247) | | `RFC2328-13-3` | Drop a Link State Update / Acknowledgment from a neighbor in a state lesser than Exchange (§13, §13.7) | MUST | 13 | **positive:** `unit/verify` [`TestRFC2328FloodingRequiresExchangeOrHigher`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L17). **negative:** `unit/verify` [`TestRFC2328FloodingRequiresExchangeOrHigher`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L20) | | `RFC2328-13.1-1` | Determine the more recent of two LSA instances using LS sequence number, then larger LS checksum, then MaxAge, then younger LS age beyond MaxAgeDiff (§13.1) | MUST | 13.1 | **positive:** `unit/verify` [`TestOSPFFreshnessCompareMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/lsdb_test.go#L126). **negative:** `unit/verify` [`TestRFC2328OlderInstanceGetsDatabaseCopyBack`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc2328_test.go#L50) | | `RFC2328-13-4` | If the database copy is MaxAge with LS sequence number MaxSequenceNumber, discard a received older instance without acknowledging (§13, step 8) | MUST | 13 | **positive:** `unit/verify` [`TestOSPFMaxSeqMaxAgeSilentDiscard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_edges_test.go#L19). **negative:** `unit/verify` [`TestRFC2328OlderInstanceGetsDatabaseCopyBack`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc2328_test.go#L53) | | `RFC2328-13.3-1` | Add an LSA flooded out an adjacency to that adjacency's Link state retransmission list and retransmit at RxmtInterval until acknowledged (§13.3, §13.6) | MUST | 13.3 | **positive:** `unit/verify` [`TestOSPFFloodOutOtherInterfaces`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L31). **positive:** `unit/verify` [`TestOSPFRetransmitTimer`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L129). **negative:** `unit/verify` [`TestOSPFFloodQueuesExchangeAndLoadingNeighbors`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L57) | | `RFC2328-13.3-2` | Increment an LSA's LS age by InfTransDelay (which MUST be > 0) when copying it into an outgoing Link State Update, capped at MaxAge (§13.3, §13.6, §14) | MUST | 13.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the InfTransDelay bump is applied on the retransmit path (RetransmitTick, lsdb/flooding.go:545) and on a direct database-copy reply (sendDirectLSUpdate, lsdb/flooding.go:745; sendDirectLinkLSUpdate, lsdb/link_scope.go:349), but NOT on the normal flood: floodExcept builds the outgoing copy with `entry.LSA(d.now())` (lsdb/flooding.go:351), and Entry.LSA calls `e.Raw(now, 0)` with transmitDelay 0 (lsdb/entry.go:62-68, 75-85), so the first flooded copy carries the unincremented LS age. The MaxAge cap itself is present wherever the bump is applied (LSAge.Add, types/lsage.go:54-63). Disclosed in docs/features/rfc-status.md RFC 2328 row | | `RFC2328-14-1` | Never increment an LSA's LS age past MaxAge, and exclude MaxAge LSAs from the routing-table calculation (§14) | MUST | 14 | **positive:** `unit/verify` [`TestLSAgeAddSaturates`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/lsage_test.go#L39). **positive:** `unit/verify` [`TestOSPFLSDBAgeToPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/aging_test.go#L34). **negative:** `unit/verify` [`TestOSPFGraphSkipsMaxAge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/graph_test.go#L40) | | `RFC2328-14-2` | Remove a MaxAge LSA from the database only once it is on no neighbor retransmission list and no neighbor is in Exchange or Loading (§14) | MUST | 14 | **positive:** `unit/verify` [`TestOSPFASExternalPurgeRetainedAcrossAreas`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L332). **negative:** `unit/verify` [`TestOSPFPurgeRetainedForExchangeOrLoading`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L383) | | `RFC2328-13.5-1` | Acknowledge every newly received LSA (directly or implicitly per Table 19) (§13.5) | MUST | 13.5 | **positive:** `unit/verify` [`TestOSPFAckDecisionTable`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L93). **positive:** `unit/verify` [`TestOSPFUnknownMaxAgeNoCopyIsAckedAndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L360). **negative:** `unit/verify` [`TestOSPFDRRefloodsBackOutReceivingInterface`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_edges_test.go#L80) | | `RFC2328-13.4-1` | Detect self-originated LSAs by Advertising Router == own Router ID, or network-LSA Link State ID == own interface address, and re-originate or flush via premature aging (§13.4, §14.1) | MUST | 13.4 | **positive:** `unit/verify` [`TestOSPFOriginateSelfReceivedHigherSeq`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/origination_test.go#L426). **negative:** `unit/verify` [`TestOSPFSelfOriginatedNoLocalCopyFlush`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_edges_test.go#L43) | | `RFC2328-16.1-1` | In intra-area SPF, include a transit-vertex link only if the neighbor LSA exists, is not MaxAge, and has a link back to the current vertex (two-way check) (§16.1) | MUST | 16.1 | **positive:** `unit/verify` [`TestOSPFSPFShortestPath`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/spf_test.go#L13). **negative:** `unit/verify` [`TestOSPFTwoWayCheck`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/spf_test.go#L31) | | `RFC2328-16.4-1` | Prefer intra-area and inter-area paths over AS-external paths; prefer Type 1 external over Type 2; among Type 2 prefer the smallest type-2 metric (§16.4) | MUST | 16.4 | **positive:** `unit/verify` [`TestOSPFRouteTablePreference`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/route_test.go#L8). **negative:** `unit/verify` [`TestOSPFExternalE1PreferredOverE2`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_test.go#L83) | | `RFC2328-16.2-1` | As an ABR, examine only backbone summary-LSAs when computing inter-area routes (§16.2) | MUST | 16.2 | **positive:** `unit/verify` [`TestOSPFInterAreaRoute`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/interarea_test.go#L43). **negative:** `unit/verify` [`TestOSPFABRBackboneOnlyAcceptance`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/interarea_test.go#L74) | | `RFC2328-16.2-2` | Skip a summary-LSA or AS-external-LSA whose cost is LSInfinity, whose LS age is MaxAge, or that is self-originated, during the routing calculation (§16.2, §16.4) | MUST | 16.2 | **positive:** `unit/verify` [`TestOSPFInterAreaRoute`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/interarea_test.go#L44). **negative:** `unit/verify` [`TestOSPFExternalLSInfinityDropped`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_test.go#L61). **negative:** `unit/verify` [`TestOSPFInterAreaLSInfinityDropped`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/interarea_test.go#L137). **negative:** `unit/verify` [`TestRFC2328ExternalSkipsMaxAgeAndSelf`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc2328_test.go#L43). **negative:** `unit/verify` [`TestRFC2328InterAreaSkipsMaxAgeAndSelfSummary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc2328_test.go#L20) | | `RFC2328-D.2-1` | Discard a packet whose Simple-password (AuType 1) authentication field does not match the configured 64-bit password (§D.2, §D.5) | MUST | D.2 | **positive:** `unit/verify` [`TestRFC2328SimplePasswordMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc2328_test.go#L94). **negative:** `unit/verify` [`TestRFC2328SimplePasswordMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc2328_test.go#L98) | | `RFC2328-D.3-1` | For Cryptographic auth (AuType 2), set the header checksum to 0, append the message digest (16 bytes for MD5), and exclude the digest from the OSPF header packet length while including it in the IP length (§D.3, §D.4.3) | MUST | D.3 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L46). **negative:** `unit/verify` [`TestOSPFAuthCryptoRejectsExtraTrailerBytes`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L214) | | `RFC2328-D.3-2` | Treat the crypto sequence number as non-decreasing, reset it to 0 when the neighbor goes Down, and set it to a received packet's value when accepted as authentic (§D.3) | MUST | D.3 | **positive:** `unit/verify` [`TestNeighborDownResetsCryptoSeq`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L200). **positive:** `unit/verify` [`TestOSPFAuthReplay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L113). **negative:** `unit/verify` [`TestOSPFAuthReplay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L114) | | `RFC2328-A.3.3-1` | Set Interface MTU to 0 in Database Description packets sent over virtual links (§A.3.3) | MUST | A.3.3 | **positive:** `unit/verify` [`TestRFC2328VirtualInterfaceHasNoMTU`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc2328_test.go#L32). **positive:** `unit/verify` [`TestRFC2328VirtualLinkDBDescCarriesZeroMTU`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L58). **negative:** `unit/verify` [`TestRFC2328VirtualLinkDBDescCarriesZeroMTU`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L63) | | `RFC2328-10.1-1` | Allow only one Database Description packet outstanding per adjacency at a time (§10.1, §10.3) | MUST | 10.1 | **positive:** `unit/verify` [`TestOSPFDDRetransmit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/nsm_test.go#L363). **negative:** `unit/verify` [`TestOSPFDuplicateDD`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/nsm_test.go#L431) | | `RFC2328-10.2-1` | Generate the BadLSReq event and restart the Database Exchange when an LS Request names an LSA not in the database (§10.2, §13) | MUST | 10.2 | **positive:** `unit/verify` [`TestOSPFBadLSReqRestart`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/nsm_test.go#L566). **negative:** `unit/verify` [`TestOSPFValidLSReqSendsLSUpdate`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/nsm_test.go#L721). **negative:** `unit/verify` [`TestRFC2328KnownLSRequestDoesNotRestartExchange`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L121) | | `RFC2328-C.3-1` | Use a positive Interface output cost (greater than 0) (§C.3) | MUST | C.3 | **positive:** `unit/verify` [`TestInterfaceCostAndTransmitDelayBoundary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_interface_validate_test.go#L16). **negative:** `unit/verify` [`TestInterfaceCostAndTransmitDelayBoundary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_interface_validate_test.go#L17) | | `RFC2328-A.2-1` | Reset (clear) unrecognized Options bits when sending Hellos / DD packets and when originating LSAs (§A.2) | SHOULD | A.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-A.2-2` | Ignore unrecognized Options bits on receipt and process the packet/LSA normally (§A.2) | SHOULD | A.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-9.5-1` | Set the E-bit in Hello Options iff the attached area can process AS-external-LSAs (not a stub); a mismatch causes Hello rejection (§9.5, §10.5) | SHOULD | 9.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-C.1-1` | Keep RFC1583Compatibility set identically on all routers; "disabled" (the 16.4.1 rules) when no un-updated routers are present (§C.1, §16.4.1) | SHOULD | C.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-B-1` | Refresh a self-originated LSA when its LS age reaches LSRefreshTime (30 minutes) (§B, §12) | SHOULD | B | **positive:** no positive test. **negative:** no negative test | | `RFC2328-14-3` | Restart the router (at least) on detecting an LS checksum failure during database aging at a CheckAge multiple (§14, §12.1.7) | SHOULD | 14 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-14.1-1` | Flush a self-originated AS-external-LSA via premature aging rather than re-originating with metric LSInfinity when the route becomes unreachable (§14.1) | SHOULD | 14.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-13.5-2` | Keep delayed-acknowledgment intervals shorter than RxmtInterval to avoid needless retransmissions (§13.5) | SHOULD | 13.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-C.3-2` | Make RouterDeadInterval some multiple of HelloInterval (e.g. 4) (§C.3) | SHOULD | C.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-A.4.5-1` | Set the Forwarding address in an AS-external-LSA to 0.0.0.0 to direct traffic to the originating ASBR (§A.4.5, §16.4) | MAY | A.4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-16.1-2` | Use a more efficient SPF algorithm (e.g. incremental SPF) provided it produces an identical shortest-path tree (§16.1) | MAY | 16.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-15-1` | Configure virtual links through non-backbone (non-stub) Transit areas to repair backbone connectivity (§15) | MAY | 15 | **positive:** no positive test. **negative:** no negative test | | `RFC2328-D.3-3` | Configure multiple Cryptographic auth keys per interface with KeyStart/KeyStop time constants for smooth rollover (§D.3) | MAY | D.3 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2328-13.3-2`](#rfc2328-13.3-2) Increment an LSA's LS age by InfTransDelay (which MUST be > 0) when copying it into an outgoing Link State Update, capped at MaxAge (§13.3, §13.6, §14) | {gap}, no test | the InfTransDelay bump is applied on the retransmit path (RetransmitTick, lsdb/flooding.go:545) and on a direct database-copy reply (sendDirectLSUpdate, lsdb/flooding.go:745; sendDirectLinkLSUpdate, lsdb/link_scope.go:349), but NOT on the normal flood: floodExcept builds the outgoing copy with `entry.LSA(d.now())` (lsdb/flooding.go:351), and Entry.LSA calls `e.Raw(now, 0)` with transmitDelay 0 (lsdb/entry.go:62-68, 75-85), so the first flooded copy carries the unincremented LS age. The MaxAge cap itself is present wherever the bump is applied (LSAge.Add, types/lsage.go:54-63). Disclosed in docs/features/rfc-status.md RFC 2328 row | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2328-A.3.1-1`](#rfc2328-a.3.1-1) All OSPF packets begin with the standard 24-byte header; Version # MUST be 2 (§A.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFHeaderRejectsBadVersionAndLength`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/header_test.go#L153) | unit/verify | unproven | | positive | [`TestOSPFHeaderRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/header_test.go#L131) | unit/verify | unproven | ### [`RFC2328-A.3.1-2`](#rfc2328-a.3.1-2) Compute the packet header IP checksum over the whole packet excluding the 64-bit authentication field (§A.3.1, §D.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPacketVerifyChecksum`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/header_test.go#L100) | unit/verify | unproven | | positive | [`TestOSPFPacketChecksumExcludesAuth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L32) | unit/verify | unproven | ### [`RFC2328-12.1.7-1`](#rfc2328-12.1.7-1) Compute the LS (Fletcher) checksum over the complete LSA excluding the LS age field; the LS checksum MUST NOT be zero (calculation is not optional) (§12.1.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2328ZeroLSChecksumRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/rfc2328_test.go#L10) | unit/verify | unproven | | positive | [`TestOSPFLSAChecksum`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L75) | unit/verify | unproven | | positive | [`TestOSPFLSAChecksumExcludesAge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/checksum_test.go#L102) | unit/verify | unproven | ### [`RFC2328-13-1`](#rfc2328-13-1) In the flooding procedure, discard an LSA with an invalid LS checksum and discard an LSA of unknown LS type (only types 1-5 are defined) (§13) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2328BadLSChecksumDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc2328_test.go#L17) | unit/verify | unproven | | negative | [`TestDecodeLSReqRejectsMalformed`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/packet_body_test.go#L145) | unit/verify | unproven | | positive | [`TestOSPFFloodOutOtherInterfaces`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L30) | unit/verify | unproven | ### [`RFC2328-13-2`](#rfc2328-13-2) Flood AS-external (Type 5) LSAs into or throughout a stub area (§13, §3.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFStubAreaDropsType5`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L247) | unit/verify | unproven | | positive | [`TestOSPFStubFloodFilter`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/area_type_test.go#L15) | unit/verify | unproven | ### [`RFC2328-13-3`](#rfc2328-13-3) Drop a Link State Update / Acknowledgment from a neighbor in a state lesser than Exchange (§13, §13.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2328FloodingRequiresExchangeOrHigher`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L20) | unit/verify | unproven | | positive | [`TestRFC2328FloodingRequiresExchangeOrHigher`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L17) | unit/verify | unproven | ### [`RFC2328-13.1-1`](#rfc2328-13.1-1) Determine the more recent of two LSA instances using LS sequence number, then larger LS checksum, then MaxAge, then younger LS age beyond MaxAgeDiff (§13.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2328OlderInstanceGetsDatabaseCopyBack`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc2328_test.go#L50) | unit/verify | unproven | | positive | [`TestOSPFFreshnessCompareMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/lsdb_test.go#L126) | unit/verify | unproven | ### [`RFC2328-13-4`](#rfc2328-13-4) If the database copy is MaxAge with LS sequence number MaxSequenceNumber, discard a received older instance without acknowledging (§13, step 8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2328OlderInstanceGetsDatabaseCopyBack`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc2328_test.go#L53) | unit/verify | unproven | | positive | [`TestOSPFMaxSeqMaxAgeSilentDiscard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_edges_test.go#L19) | unit/verify | unproven | ### [`RFC2328-13.3-1`](#rfc2328-13.3-1) Add an LSA flooded out an adjacency to that adjacency's Link state retransmission list and retransmit at RxmtInterval until acknowledged (§13.3, §13.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFFloodQueuesExchangeAndLoadingNeighbors`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L57) | unit/verify | unproven | | positive | [`TestOSPFFloodOutOtherInterfaces`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L31) | unit/verify | unproven | | positive | [`TestOSPFRetransmitTimer`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L129) | unit/verify | unproven | ### [`RFC2328-13.3-2`](#rfc2328-13.3-2) Increment an LSA's LS age by InfTransDelay (which MUST be > 0) when copying it into an outgoing Link State Update, capped at MaxAge (§13.3, §13.6, §14) Audit verdict: not audited: no reader has judged these tests No test carries RFC2328-13.3-2, so no unit is bound to it. ### [`RFC2328-14-1`](#rfc2328-14-1) Never increment an LSA's LS age past MaxAge, and exclude MaxAge LSAs from the routing-table calculation (§14) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFGraphSkipsMaxAge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/graph_test.go#L40) | unit/verify | unproven | | positive | [`TestOSPFLSDBAgeToPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/aging_test.go#L34) | unit/verify | unproven | | positive | [`TestLSAgeAddSaturates`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/lsage_test.go#L39) | unit/verify | unproven | ### [`RFC2328-14-2`](#rfc2328-14-2) Remove a MaxAge LSA from the database only once it is on no neighbor retransmission list and no neighbor is in Exchange or Loading (§14) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFPurgeRetainedForExchangeOrLoading`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L383) | unit/verify | unproven | | positive | [`TestOSPFASExternalPurgeRetainedAcrossAreas`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L332) | unit/verify | unproven | ### [`RFC2328-13.5-1`](#rfc2328-13.5-1) Acknowledge every newly received LSA (directly or implicitly per Table 19) (§13.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFDRRefloodsBackOutReceivingInterface`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_edges_test.go#L80) | unit/verify | unproven | | positive | [`TestOSPFAckDecisionTable`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L93) | unit/verify | unproven | | positive | [`TestOSPFUnknownMaxAgeNoCopyIsAckedAndDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_test.go#L360) | unit/verify | unproven | ### [`RFC2328-13.4-1`](#rfc2328-13.4-1) Detect self-originated LSAs by Advertising Router == own Router ID, or network-LSA Link State ID == own interface address, and re-originate or flush via premature aging (§13.4, §14.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFSelfOriginatedNoLocalCopyFlush`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/flooding_edges_test.go#L43) | unit/verify | unproven | | positive | [`TestOSPFOriginateSelfReceivedHigherSeq`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/origination_test.go#L426) | unit/verify | unproven | ### [`RFC2328-16.1-1`](#rfc2328-16.1-1) In intra-area SPF, include a transit-vertex link only if the neighbor LSA exists, is not MaxAge, and has a link back to the current vertex (two-way check) (§16.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFTwoWayCheck`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/spf_test.go#L31) | unit/verify | unproven | | positive | [`TestOSPFSPFShortestPath`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/spf_test.go#L13) | unit/verify | unproven | ### [`RFC2328-16.4-1`](#rfc2328-16.4-1) Prefer intra-area and inter-area paths over AS-external paths; prefer Type 1 external over Type 2; among Type 2 prefer the smallest type-2 metric (§16.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFExternalE1PreferredOverE2`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_test.go#L83) | unit/verify | unproven | | positive | [`TestOSPFRouteTablePreference`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/route_test.go#L8) | unit/verify | unproven | ### [`RFC2328-16.2-1`](#rfc2328-16.2-1) As an ABR, examine only backbone summary-LSAs when computing inter-area routes (§16.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFABRBackboneOnlyAcceptance`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/interarea_test.go#L74) | unit/verify | unproven | | positive | [`TestOSPFInterAreaRoute`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/interarea_test.go#L43) | unit/verify | unproven | ### [`RFC2328-16.2-2`](#rfc2328-16.2-2) Skip a summary-LSA or AS-external-LSA whose cost is LSInfinity, whose LS age is MaxAge, or that is self-originated, during the routing calculation (§16.2, §16.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFExternalLSInfinityDropped`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_test.go#L61) | unit/verify | unproven | | negative | [`TestOSPFInterAreaLSInfinityDropped`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/interarea_test.go#L137) | unit/verify | unproven | | negative | [`TestRFC2328ExternalSkipsMaxAgeAndSelf`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc2328_test.go#L43) | unit/verify | unproven | | negative | [`TestRFC2328InterAreaSkipsMaxAgeAndSelfSummary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc2328_test.go#L20) | unit/verify | unproven | | positive | [`TestOSPFInterAreaRoute`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/interarea_test.go#L44) | unit/verify | unproven | ### [`RFC2328-D.2-1`](#rfc2328-d.2-1) Discard a packet whose Simple-password (AuType 1) authentication field does not match the configured 64-bit password (§D.2, §D.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2328SimplePasswordMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc2328_test.go#L98) | unit/verify | unproven | | positive | [`TestRFC2328SimplePasswordMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc2328_test.go#L94) | unit/verify | unproven | ### [`RFC2328-D.3-1`](#rfc2328-d.3-1) For Cryptographic auth (AuType 2), set the header checksum to 0, append the message digest (16 bytes for MD5), and exclude the digest from the OSPF header packet length while including it in the IP length (§D.3, §D.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthCryptoRejectsExtraTrailerBytes`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L214) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L46) | unit/verify | unproven | ### [`RFC2328-D.3-2`](#rfc2328-d.3-2) Treat the crypto sequence number as non-decreasing, reset it to 0 when the neighbor goes Down, and set it to a received packet's value when accepted as authentic (§D.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthReplay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L114) | unit/verify | unproven | | positive | [`TestNeighborDownResetsCryptoSeq`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L200) | unit/verify | unproven | | positive | [`TestOSPFAuthReplay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L113) | unit/verify | unproven | ### [`RFC2328-A.3.3-1`](#rfc2328-a.3.3-1) Set Interface MTU to 0 in Database Description packets sent over virtual links (§A.3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2328VirtualLinkDBDescCarriesZeroMTU`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L63) | unit/verify | unproven | | positive | [`TestRFC2328VirtualLinkDBDescCarriesZeroMTU`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L58) | unit/verify | unproven | | positive | [`TestRFC2328VirtualInterfaceHasNoMTU`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc2328_test.go#L32) | unit/verify | unproven | ### [`RFC2328-10.1-1`](#rfc2328-10.1-1) Allow only one Database Description packet outstanding per adjacency at a time (§10.1, §10.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFDuplicateDD`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/nsm_test.go#L431) | unit/verify | unproven | | positive | [`TestOSPFDDRetransmit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/nsm_test.go#L363) | unit/verify | unproven | ### [`RFC2328-10.2-1`](#rfc2328-10.2-1) Generate the BadLSReq event and restart the Database Exchange when an LS Request names an LSA not in the database (§10.2, §13) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFValidLSReqSendsLSUpdate`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/nsm_test.go#L721) | unit/verify | unproven | | negative | [`TestRFC2328KnownLSRequestDoesNotRestartExchange`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/rfc2328_test.go#L121) | unit/verify | unproven | | positive | [`TestOSPFBadLSReqRestart`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/nsm_test.go#L566) | unit/verify | unproven | ### [`RFC2328-C.3-1`](#rfc2328-c.3-1) Use a positive Interface output cost (greater than 0) (§C.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInterfaceCostAndTransmitDelayBoundary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_interface_validate_test.go#L17) | unit/verify | unproven | | positive | [`TestInterfaceCostAndTransmitDelayBoundary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_interface_validate_test.go#L16) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 2328, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2328, so its obligations are stated where they were written. --- ### Page: RFC 2347 - TFTP Option Extension https://ze-software.net/quality/rfc-compliance/rfc2347/ # RFC 2347 - TFTP Option Extension Supported. Every requirement this repository extracted from RFC 2347, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 50.0% | 2 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 4 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 6 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 2 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 50.0% | 2 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 6 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 2 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 4 | | Tagged units | 4 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2347.md` | | Requirement shard | `rfc/requirements/rfc2347.md` | | RFC text | `rfc/full/rfc2347.txt` | ## Enrolment Enrolled: TFTP Option Extension: four MUST-level requirements. The two server-side requirements are tested with both polarities via loopback OACK round-trips in rfc2347_option_negotiation_test.go (producer: parseRRQ + sendOACKAndWait oackOpts assembly, internal/plugins/tftpserver/handler.go): RFC2347-x-1 (server MUST NOT include in the OACK any option not requested by the client) via TestRFC2347ServerOACKOnlyRequestedOptions (requesting only blksize yields an OACK with blksize but no tsize); RFC2347-x-3 (an option not acknowledged is ignored as if never requested) via TestRFC2347ServerIgnoresUnacknowledgedOption (a requested-but-unsupported windowsize is omitted from the OACK and the server falls back to lockstep, handler.go:276, while the supported blksize is still acknowledged). The two client-side requirements are {not-applicable}: RFC2347-x-2 (client MUST use acknowledged options) and RFC2347-x-4 (client MUST NOT use unacknowledged options) govern a TFTP CLIENT, and Ze ships only a TFTP server with no client. No SHOULD/MAY requirements are gated. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** Option negotiation for TFTP provisioning paths. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 2 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC2347-x-1`](#rfc2347-x-1), [`RFC2347-x-3`](#rfc2347-x-3) **Annotated instead of tested (2):** [`RFC2347-x-2`](#rfc2347-x-2), [`RFC2347-x-4`](#rfc2347-x-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2347-x-1` | Server MUST NOT include in the OACK any option which had not been specifically requested by the client (Negotiation Protocol) | MUST NOT | x | **positive:** `unit/verify` [`TestRFC2347ServerOACKOnlyRequestedOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2347_option_negotiation_test.go#L78). **negative:** `unit/verify` [`TestRFC2347ServerOACKOnlyRequestedOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2347_option_negotiation_test.go#L82) | | `RFC2347-x-2` | If multiple options were requested, the client MUST use those options which were acknowledged by the server (Negotiation Protocol) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** This requirement governs the TFTP CLIENT (it MUST use the options the server acknowledged). Ze ships only a TFTP SERVER (internal/plugins/tftpserver/handler.go) and has no TFTP client, so there is no client-side option-consumption code path to which this applies. | | `RFC2347-x-3` | An option not acknowledged by the server must be ignored by the client and server as if it were never requested (Negotiation Protocol) | MUST | x | **positive:** `unit/verify` [`TestRFC2347ServerIgnoresUnacknowledgedOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2347_option_negotiation_test.go#L103). **negative:** `unit/verify` [`TestRFC2347ServerIgnoresUnacknowledgedOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2347_option_negotiation_test.go#L108) | | `RFC2347-x-4` | If multiple options were requested, the client MUST NOT use those options which were not acknowledged by the server (Negotiation Protocol) | MUST NOT | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** This requirement governs the TFTP CLIENT (it MUST NOT use options the server did not acknowledge). Ze has no TFTP client (only the server in internal/plugins/tftpserver/handler.go), so no client-side code path could use an unacknowledged option. | | `RFC2347-x-5` | Unrecognized options SHOULD be omitted from the OACK, not cause an ERROR packet (Negotiation Protocol) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC2347-x-6` | If client receives an OACK containing an unrequested option, it SHOULD respond with ERROR code 8 and terminate (Negotiation Protocol) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2347-x-2`](#rfc2347-x-2) If multiple options were requested, the client MUST use those options which were acknowledged by the server (Negotiation Protocol) | no test | no test carries this requirement id; annotated {not-applicable}: This requirement governs the TFTP CLIENT (it MUST use the options the server acknowledged). Ze ships only a TFTP SERVER (internal/plugins/tftpserver/handler.go) and has no TFTP client, so there is no client-side option-consumption code path to which this applies. | | [`RFC2347-x-4`](#rfc2347-x-4) If multiple options were requested, the client MUST NOT use those options which were not acknowledged by the server (Negotiation Protocol) | no test | no test carries this requirement id; annotated {not-applicable}: This requirement governs the TFTP CLIENT (it MUST NOT use options the server did not acknowledge). Ze has no TFTP client (only the server in internal/plugins/tftpserver/handler.go), so no client-side code path could use an unacknowledged option. | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2347-x-1`](#rfc2347-x-1) Server MUST NOT include in the OACK any option which had not been specifically requested by the client (Negotiation Protocol) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2347ServerOACKOnlyRequestedOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2347_option_negotiation_test.go#L82) | unit/verify | unproven | | positive | [`TestRFC2347ServerOACKOnlyRequestedOptions`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2347_option_negotiation_test.go#L78) | unit/verify | unproven | ### [`RFC2347-x-2`](#rfc2347-x-2) If multiple options were requested, the client MUST use those options which were acknowledged by the server (Negotiation Protocol) Audit verdict: not audited: no reader has judged these tests No test carries RFC2347-x-2, so no unit is bound to it. ### [`RFC2347-x-3`](#rfc2347-x-3) An option not acknowledged by the server must be ignored by the client and server as if it were never requested (Negotiation Protocol) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2347ServerIgnoresUnacknowledgedOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2347_option_negotiation_test.go#L108) | unit/verify | unproven | | positive | [`TestRFC2347ServerIgnoresUnacknowledgedOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2347_option_negotiation_test.go#L103) | unit/verify | unproven | ### [`RFC2347-x-4`](#rfc2347-x-4) If multiple options were requested, the client MUST NOT use those options which were not acknowledged by the server (Negotiation Protocol) Audit verdict: not audited: no reader has judged these tests No test carries RFC2347-x-4, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6, rfc2347 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc2347.txt | | Source fingerprint | 15ce2b209b116c40 | | Record | rfc/extraction/rfc2347.json | | Mapped sentences | 3 | | Declined as scope | 1 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | The whole document | 4 | walked | The whole document. RFC 2347 carries no numbered section headings, so the derivation returns one section spanning the title block to the Full Copyright Statement, and a skip of it would hide every gated id behind a skip kind. Walked heading by heading. Status of this Memo, the Copyright Notice and the Abstract are indicative. The Introduction says the mechanism is a backward-compatible extension enforcing a request-respond-acknowledge sequence and names RFC 2348 and RFC 2349 as the options it was created to carry; it directs nobody. Packet Formats is the wire-format section, written entirely in the indicative: options are appended to the Read Request or Write Request as NUL-terminated name/value pairs, a new opcode 6 (OACK) acknowledges them, a new error code 8 terminates a transfer over option negotiation, the order of options is not significant, and 'The maximum size of a request packet is 512 octets.' Ze reads those bytes in parseRRQ and writes them in buildOACK (internal/plugins/tftpserver/handler.go), and rfc/short/rfc2347.md carries the two figures and the constants; no gated row is read from this material. Packet Formats also defers three fields to RFC 1350 in the indicative ('as defined in [1]' for opc, filename and mode), which cites the base document rather than restating an obligation of it, so no site here is a cross-document exclusion. Negotiation Protocol is the only normative section and holds three of the four sites, front:1 to front:3, all classified below; its remaining normative sentences are advisory and ungated -- options the server does not support 'should be omitted from the OACK; they should not cause an ERROR packet to be generated' (RFC2347-x-5), and a client receiving an unrequested option in an OACK 'should respond with an ERROR packet, with error code 8' (RFC2347-x-6). Its three-response tables for RRQ and WRQ, the fallback narrative for a server that does not implement options, and the two ways a client confirms an OACK are indicative. The Examples section is two packet traces. Security Considerations says the document adds no security to TFTP and no additional risk, so it states no countermeasure. The References, Authors' Addresses and the Full Copyright Statement bind no TFTP speaker; the copyright statement's one modal sentence is site front:4, excluded below. RFC2347-x-4 is declared unsourced here: it is the second half of site front:3, which the sentence splitter returns whole, so the obligation has no site locator of its own. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `front:4` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Boilerplate the extractor did not strip: the RFC Editor's Full Copyright Statement, which the site scan reaches because the sentence carries 'may not be modified' and 'must be followed'. It binds whoever copies or translates the document, under the Internet Society's copyright terms, and says nothing about TFTP option negotiation or about any byte on the wire. rfc/short/rfc2347.md declares no requirement for it. | However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English. | ## Superseded No document obsoletes RFC 2347, so its obligations are stated where they were written. --- ### Page: RFC 2348 - TFTP Blocksize Option https://ze-software.net/quality/rfc-compliance/rfc2348/ # RFC 2348 - TFTP Blocksize Option No row in the public ledger. Every requirement this repository extracted from RFC 2348, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 80.0% | 4 of 5 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 5 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 5 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 5 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 8 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 5 | of 5 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 5 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 20.0% | 1 of 5 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 5 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 5 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 5 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 5 | | Gated MUST-level | 5 | | Not applicable, so out of scope | 1 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 8 | | Tagged units | 8 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2348.md` | | Requirement shard | `rfc/requirements/rfc2348.md` | | RFC text | `rfc/full/rfc2348.txt` | ## Enrolment Enrolled: TFTP Blocksize Option: five MUST-level requirements, four met and tested, one client-side {not-applicable}. RFC2348-x-1 (acknowledged blksize <= requested) both polarities via TestRFC2348BlksizeAckNotAboveRequest: a request within Ze cap (1200) is acked as 1200, a request above the 1468 cap (60000) is acked as 1468 which never exceeds the request (producer handleRRQ min(opts.blksize, blksizeEthernet), handler.go:271-272). RFC2348-x-3 (valid range 8..65464) both polarities via TestRFC2348BlksizeRangeEnforced: 512 is acked, while 5 and 70000 are ignored and absent from the OACK (producer parseRRQ n >= blksizeMin && n <= blksizeMax, handler.go:122-125). RFC2348-x-4 (a short block ends the transfer) both polarities: TestTFTPReadLargeFile (1500 bytes over 512 ends after a 476-byte short block) and TestTFTPReadExact512 (a full 512-byte block does NOT end, block 2 follows). RFC2348-x-5 (exact multiple -> extra zero-length block) both polarities: TestTFTPReadExact512 (512-byte file -> 512 block + a 0-byte end block) and TestTFTPReadLargeFile (1500 is not a multiple, so no extra zero block). RFC2348-x-2 (client MUST use the OACK size or send ERROR 8) is {not-applicable}: Ze ships only a TFTP server, no client. No SHOULD/MAY requirements are gated. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 2348. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 1 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **5** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`RFC2348-x-1`](#rfc2348-x-1), [`RFC2348-x-3`](#rfc2348-x-3), [`RFC2348-x-4`](#rfc2348-x-4), [`RFC2348-x-5`](#rfc2348-x-5) **Annotated instead of tested (1):** [`RFC2348-x-2`](#rfc2348-x-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2348-x-1` | Server's acknowledged blksize MUST be less than or equal to the client's requested value (Blocksize Option Specification) | MUST | x | **positive:** `unit/verify` [`TestRFC2348BlksizeAckNotAboveRequest`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2348_blksize_test.go#L13). **negative:** `unit/verify` [`TestRFC2348BlksizeAckNotAboveRequest`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2348_blksize_test.go#L17) | | `RFC2348-x-2` | Client MUST use the size specified in the OACK, or send ERROR code 8 to terminate (Blocksize Option Specification) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** This requirement governs the TFTP CLIENT (it MUST use the OACK blocksize or send ERROR 8). Ze ships only a TFTP SERVER (internal/plugins/tftpserver/handler.go) with no TFTP client, so there is no client-side code path that consumes an OACK blocksize or emits ERROR 8. | | `RFC2348-x-3` | Valid blksize range MUST be 8 to 65464 inclusive (Blocksize Option Specification) | MUST | x | **positive:** `unit/verify` [`TestRFC2348BlksizeRangeEnforced`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2348_blksize_test.go#L52). **negative:** `unit/verify` [`TestRFC2348BlksizeRangeEnforced`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2348_blksize_test.go#L55) | | `RFC2348-x-4` | A data block shorter than the negotiated blksize signals end of transfer (Blocksize Option Specification) | MUST | x | **positive:** `unit/verify` [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L383). **negative:** `unit/verify` [`TestTFTPReadExact512`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L468) | | `RFC2348-x-5` | If the transfer size is an exact multiple of the blocksize, an extra zero-length data packet MUST be sent to end the transfer (Blocksize Option Specification) | MUST | x | **positive:** `unit/verify` [`TestTFTPReadExact512`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L464). **negative:** `unit/verify` [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L387) | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2348-x-2`](#rfc2348-x-2) Client MUST use the size specified in the OACK, or send ERROR code 8 to terminate (Blocksize Option Specification) | no test | no test carries this requirement id; annotated {not-applicable}: This requirement governs the TFTP CLIENT (it MUST use the OACK blocksize or send ERROR 8). Ze ships only a TFTP SERVER (internal/plugins/tftpserver/handler.go) with no TFTP client, so there is no client-side code path that consumes an OACK blocksize or emits ERROR 8. | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2348-x-1`](#rfc2348-x-1) Server's acknowledged blksize MUST be less than or equal to the client's requested value (Blocksize Option Specification) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2348BlksizeAckNotAboveRequest`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2348_blksize_test.go#L17) | unit/verify | unproven | | positive | [`TestRFC2348BlksizeAckNotAboveRequest`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2348_blksize_test.go#L13) | unit/verify | unproven | ### [`RFC2348-x-2`](#rfc2348-x-2) Client MUST use the size specified in the OACK, or send ERROR code 8 to terminate (Blocksize Option Specification) Audit verdict: not audited: no reader has judged these tests No test carries RFC2348-x-2, so no unit is bound to it. ### [`RFC2348-x-3`](#rfc2348-x-3) Valid blksize range MUST be 8 to 65464 inclusive (Blocksize Option Specification) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2348BlksizeRangeEnforced`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2348_blksize_test.go#L55) | unit/verify | unproven | | positive | [`TestRFC2348BlksizeRangeEnforced`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2348_blksize_test.go#L52) | unit/verify | unproven | ### [`RFC2348-x-4`](#rfc2348-x-4) A data block shorter than the negotiated blksize signals end of transfer (Blocksize Option Specification) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTFTPReadExact512`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L468) | unit/verify | unproven | | positive | [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L383) | unit/verify | unproven | ### [`RFC2348-x-5`](#rfc2348-x-5) If the transfer size is an exact multiple of the blocksize, an extra zero-length data packet MUST be sent to end the transfer (Blocksize Option Specification) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTFTPReadLargeFile`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L387) | unit/verify | unproven | | positive | [`TestTFTPReadExact512`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/handler_test.go#L464) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 2348, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2348, so its obligations are stated where they were written. --- ### Page: RFC 2349 - TFTP Timeout Interval and Transfer Size Options https://ze-software.net/quality/rfc-compliance/rfc2349/ # RFC 2349 - TFTP Timeout Interval and Transfer Size Options No row in the public ledger. Every requirement this repository extracted from RFC 2349, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 25.0% | 1 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 2 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 6 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 3 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 75.0% | 3 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 6 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 3 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 2 | | Tagged units | 2 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2349.md` | | Requirement shard | `rfc/requirements/rfc2349.md` | | RFC text | `rfc/full/rfc2349.txt` | ## Enrolment Enrolled: TFTP Timeout Interval and Transfer Size Options: four MUST-level requirements. RFC2349-x-3 (tsize in RRQ: client value "0", server returns actual file size in the OACK) is met and tested with both polarities via loopback OACK round-trips in rfc2349_tsize_test.go (producer handleRRQ os.Stat + oackOpts tsize, internal/plugins/tftpserver/handler.go:319-329): a 7-byte file returns tsize "7", a 20-byte file returns "20", and the server never echoes the client placeholder "0". The other three are {not-applicable}: RFC2349-x-1 (timeout acknowledged value matches request) and RFC2349-x-2 (timeout range 1-255) govern the timeout option, which Ze does not implement (parseRRQ recognizes only blksize/tsize/windowsize and ignores timeout per RFC 2347); RFC2349-x-4 (tsize in WRQ echoes the client size) governs write requests, which Ze (a read-only TFTP server) rejects with Illegal-Operation. No SHOULD/MAY requirements are gated. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 2349. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 1 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (1):** [`RFC2349-x-3`](#rfc2349-x-3) **Annotated instead of tested (3):** [`RFC2349-x-1`](#rfc2349-x-1), [`RFC2349-x-2`](#rfc2349-x-2), [`RFC2349-x-4`](#rfc2349-x-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2349-x-1` | Timeout: server's acknowledged value MUST match the client's requested value exactly (Timeout Interval Option Specification) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the RFC 2349 timeout option. Its RRQ parser recognizes only blksize, tsize, and windowsize (internal/plugins/tftpserver/handler.go:121-131 parseRRQ); a requested timeout option falls through and is ignored per RFC 2347 (an unacknowledged option is treated as never requested, gated as RFC2347-x-3). Ze never places timeout in an OACK, so it has no code path that could acknowledge a mismatched timeout value. | | `RFC2349-x-2` | Timeout valid range MUST be 1 to 255 seconds inclusive (Timeout Interval Option Specification) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the timeout option (parseRRQ recognizes only blksize/tsize/windowsize, internal/plugins/tftpserver/handler.go:121-131), so it has no timeout value to range-check against 1-255 seconds. | | `RFC2349-x-3` | Tsize in RRQ: client's value MUST be "0"; server returns actual file size in OACK (Transfer Size Option Specification) | MUST | x | **positive:** `unit/verify` [`TestRFC2349TsizeRRQReturnsActualSize`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2349_tsize_test.go#L32). **negative:** `unit/verify` [`TestRFC2349TsizeRRQReturnsActualSize`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2349_tsize_test.go#L37) | | `RFC2349-x-4` | Tsize in WRQ: server's OACK value MUST echo the client's specified file size (Transfer Size Option Specification) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze is a read-only TFTP server: it rejects every write request (WRQ) with an Illegal-Operation error (internal/plugins/tftpserver/handler.go:226-227 "write not supported"; TestTFTPWriteRejected). With no WRQ transfer ever accepted, Ze has no code path that would echo a client-specified file size in a WRQ OACK. | | `RFC2349-x-5` | If the file is too large for the client (RRQ), it MAY abort with ERROR code 3 (Transfer Size Option Specification) | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC2349-x-6` | If the file is too large for the server (WRQ), it MAY abort with ERROR code 3 (Transfer Size Option Specification) | MAY | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2349-x-1`](#rfc2349-x-1) Timeout: server's acknowledged value MUST match the client's requested value exactly (Timeout Interval Option Specification) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the RFC 2349 timeout option. Its RRQ parser recognizes only blksize, tsize, and windowsize (internal/plugins/tftpserver/handler.go:121-131 parseRRQ); a requested timeout option falls through and is ignored per RFC 2347 (an unacknowledged option is treated as never requested, gated as RFC2347-x-3). Ze never places timeout in an OACK, so it has no code path that could acknowledge a mismatched timeout value. | | [`RFC2349-x-2`](#rfc2349-x-2) Timeout valid range MUST be 1 to 255 seconds inclusive (Timeout Interval Option Specification) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the timeout option (parseRRQ recognizes only blksize/tsize/windowsize, internal/plugins/tftpserver/handler.go:121-131), so it has no timeout value to range-check against 1-255 seconds. | | [`RFC2349-x-4`](#rfc2349-x-4) Tsize in WRQ: server's OACK value MUST echo the client's specified file size (Transfer Size Option Specification) | no test | no test carries this requirement id; annotated {not-applicable}: Ze is a read-only TFTP server: it rejects every write request (WRQ) with an Illegal-Operation error (internal/plugins/tftpserver/handler.go:226-227 "write not supported"; TestTFTPWriteRejected). With no WRQ transfer ever accepted, Ze has no code path that would echo a client-specified file size in a WRQ OACK. | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2349-x-1`](#rfc2349-x-1) Timeout: server's acknowledged value MUST match the client's requested value exactly (Timeout Interval Option Specification) Audit verdict: not audited: no reader has judged these tests No test carries RFC2349-x-1, so no unit is bound to it. ### [`RFC2349-x-2`](#rfc2349-x-2) Timeout valid range MUST be 1 to 255 seconds inclusive (Timeout Interval Option Specification) Audit verdict: not audited: no reader has judged these tests No test carries RFC2349-x-2, so no unit is bound to it. ### [`RFC2349-x-3`](#rfc2349-x-3) Tsize in RRQ: client's value MUST be "0"; server returns actual file size in OACK (Transfer Size Option Specification) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2349TsizeRRQReturnsActualSize`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2349_tsize_test.go#L37) | unit/verify | unproven | | positive | [`TestRFC2349TsizeRRQReturnsActualSize`](https://github.com/ze-software/ze/blob/main/internal/plugins/tftpserver/rfc2349_tsize_test.go#L32) | unit/verify | unproven | ### [`RFC2349-x-4`](#rfc2349-x-4) Tsize in WRQ: server's OACK value MUST echo the client's specified file size (Transfer Size Option Specification) Audit verdict: not audited: no reader has judged these tests No test carries RFC2349-x-4, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2349, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2349, so its obligations are stated where they were written. --- ### Page: RFC 2385 - Protection of BGP Sessions via the TCP MD5 Signature Option https://ze-software.net/quality/rfc-compliance/rfc2385/ # RFC 2385 - Protection of BGP Sessions via the TCP MD5 Signature Option Supported on Linux; FreeBSD needs a `setkey(8)` SAD entry. Every requirement this repository extracted from RFC 2385, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 66.7% | 6 of 9 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 33.3% | 3 of 9 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 9 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 9 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 93.3% | 14 of 15 tagged units, 0 escaped and 1 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 9 | of 10 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 9 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 9 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 9 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 9 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 9 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported on Linux; FreeBSD needs a `setkey(8)` SAD entry | | Enrolment | Enrolled | | Requirements | 10 | | Gated MUST-level | 9 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 15 | | Tagged units | 15 | | Recorded audit verdicts | 0 | | Discrimination records | 15 | | Summary | `rfc/short/rfc2385.md` | | Requirement shard | `rfc/requirements/rfc2385.md` | | RFC text | `rfc/full/rfc2385.txt` | ## Enrolment Enrolled: Protection of BGP Sessions via the TCP MD5 Signature Option: nine gated requirements, every one written in lowercase by a document that predates RFC 2119, so the extraction sign-off derives register 'prose' (rfc/extraction/rfc2385.json, 8 sites, 1 excluded). Ze computes no digest: it installs the operator's key with TCP_MD5SIG and the kernel signs and validates every segment, which is the whole-stack ruling in ai/rules/rfc-compliance.md. The proof runs at the boundary ze owns and reads what the stack answers on loopback (internal/core/network/md5_rfc2385_linux_test.go): one key installed on both sockets carries a 256 KiB payload (RFC2385-2.0-1, -2.0-2, -2.0-6, -3.0-1, -4.3-1 and -4.3-2 positive, RFC2385-2.0-3 negative); a mismatched key is dropped with no answer at all, so the dial times out instead of being refused or reset (RFC2385-2.0-2, -2.0-6 and -3.0-1 negative, RFC2385-2.0-3 positive); and a key held by only one end carries no session (RFC2385-2.0-1 negative). The application-control rows are proven over the config path (internal/component/bgp/reactor/rfc2385_test.go): a peer carrying md5 { password } gets that key on the dialer and in the listener's key set (RFC2385-2.0-5 positive), a peer without one gets none on either (RFC2385-2.0-5 negative), and a failed connection attempt leaves the key in place (RFC2385-2.0-4 positive). Three rows are single-polarity positive because ze holds no rejecting branch for them: RFC2385-2.0-4, and the two section 4.3 rows, whose MSS and option list are assembled by the kernel. RFC2385-4.5-1 is SHOULD-level and met exactly at its floor: setTCPMD5Sig refuses a key above 80 octets (internal/core/network/md5_linux.go), which is Linux's TCP_MD5SIG_MAXKEYLEN. Enrolled 2026-09-01. ## What the public ledger says **Status:** Supported on Linux; FreeBSD needs a `setkey(8)` SAD entry **What the ledger says is covered** - Per-peer TCP MD5 through `connection { md5 { password; ip; } }`. `parsePeer` ([`internal/component/bgp/reactor/config.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/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 [`rfc/short/rfc2385.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2385.md) 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`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/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`. **What the ledger says remains** No conformance gap is tracked. - **One platform limit:** on FreeBSD `setTCPMD5Sig` ([`internal/core/network/md5_freebsd.go`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_freebsd.go)) enables the socket flag only and takes the key from the Security Association Database, so a configured password installs nothing there until `setkey(8)` carries it ([`plan/journal/silent-fall-through.md`](https://github.com/ze-software/ze/blob/main/plan/journal/silent-fall-through.md), 2026-09-01). macOS and every other platform refuse the socket option, so the connection fails rather than falling back to an unsigned session. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 6 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **9** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (6):** [`RFC2385-2.0-1`](#rfc2385-2.0-1), [`RFC2385-2.0-2`](#rfc2385-2.0-2), [`RFC2385-2.0-3`](#rfc2385-2.0-3), [`RFC2385-2.0-5`](#rfc2385-2.0-5), [`RFC2385-2.0-6`](#rfc2385-2.0-6), [`RFC2385-3.0-1`](#rfc2385-3.0-1) **Annotated instead of tested (3):** [`RFC2385-2.0-4`](#rfc2385-2.0-4), [`RFC2385-4.3-1`](#rfc2385-4.3-1), [`RFC2385-4.3-2`](#rfc2385-4.3-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2385-2.0-1` | The key must be known by both ends of the connection, so the same configured key is installed for the peer on the outbound socket and on the listening socket (§2.0) | MUST | 2.0 - Proposal | **positive:** `unit/verify` [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L76). **negative:** `unit/verify` [`TestRFC2385KeyOnOneEndOnlyCarriesNoSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L192) | | `RFC2385-2.0-2` | Upon receiving a signed segment, the receiver must validate it by calculating its own digest from the same data using its own key and comparing the two digests (§2.0) | MUST | 2.0 - Proposal | **positive:** `unit/verify` [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L79). **negative:** `unit/verify` [`TestRFC2385MismatchedKeyIsDroppedWithNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L153) | | `RFC2385-2.0-3` | A failing comparison must result in the segment being dropped, and must not produce any response back to the sender (§2.0) | MUST | 2.0 - Proposal | **positive:** `unit/verify` [`TestRFC2385MismatchedKeyIsDroppedWithNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L156). **negative:** `unit/verify` [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L82) | | `RFC2385-2.0-4` | The absence of the option in the SYN,ACK segment must not cause the sender to disable its sending of signatures (§2.0) | MUST NOT | 2.0 - Proposal | **positive:** `unit/verify` [`TestRFC2385FailedConnectDoesNotDisableSigning`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2385_test.go#L134). **negative:** no negative test. **{single-polarity}:** ze holds no code path that stops signing: the key is installed from the peer's settings on every dial attempt and is never cleared by anything the remote host does or fails to do, so there is no rejecting branch a negative test could reach | | `RFC2385-2.0-5` | The sending of signatures must be under the complete control of the application, not at the mercy of the remote host not understanding the option (§2.0) | MUST | 2.0 - Proposal | **positive:** `unit/verify` [`TestRFC2385ConfiguredKeyReachesBothSockets`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2385_test.go#L40). **negative:** `unit/verify` [`TestRFC2385NoKeyWithoutConfiguration`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2385_test.go#L90) | | `RFC2385-2.0-6` | Every segment sent on a protected connection carries the 16-byte MD5 digest of the TCP pseudo-header, the TCP header with a zero checksum, the segment data and the key, in that order (§2.0) | MUST | 2.0 - Proposal | **positive:** `unit/verify` [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L84). **negative:** `unit/verify` [`TestRFC2385MismatchedKeyIsDroppedWithNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L159) | | `RFC2385-3.0-1` | The option is Kind 19, Length 18, carrying a 16-byte digest, and it appears in every segment of the connection (§3.0) | MUST | 3.0 - Syntax | **positive:** `unit/verify` [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L87). **negative:** `unit/verify` [`TestRFC2385MismatchedKeyIsDroppedWithNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L162) | | `RFC2385-4.3-1` | The size of the MD5 option must be factored into the MSS offered to the other side during connection negotiation (§4.3) | MUST | 4.3 - TCP Header Size | **positive:** `unit/verify` [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L90). **negative:** no negative test. **{single-polarity}:** the MSS is chosen by the kernel that signs the segments, and ze holds no code that lowers, rejects or recomputes an MSS, so there is no rejecting branch a negative test could reach | | `RFC2385-4.3-2` | The total size of the TCP header plus its options must be less than or equal to 60 bytes, leaving 40 bytes for options (§4.3) | MUST | 4.3 - TCP Header Size | **positive:** `unit/verify` [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L93). **negative:** no negative test. **{single-polarity}:** the option list is assembled by the kernel and ze contributes no TCP option of its own, so there is no rejecting branch a negative test could reach | | `RFC2385-4.5-1` | It is strongly recommended that an implementation support at minimum a key composed of a string of printable ASCII of 80 bytes or less (§4.5) | SHOULD | 4.5 - Key configuration | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 2385 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2385-2.0-1`](#rfc2385-2.0-1) The key must be known by both ends of the connection, so the same configured key is installed for the peer on the outbound socket and on the listening socket (§2.0) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2385KeyOnOneEndOnlyCarriesNoSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L192) | unit/verify | revert, verified | | positive | [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L76) | unit/verify | revert, verified | ### [`RFC2385-2.0-2`](#rfc2385-2.0-2) Upon receiving a signed segment, the receiver must validate it by calculating its own digest from the same data using its own key and comparing the two digests (§2.0) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2385MismatchedKeyIsDroppedWithNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L153) | unit/verify | revert, verified | | positive | [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L79) | unit/verify | revert, verified | ### [`RFC2385-2.0-3`](#rfc2385-2.0-3) A failing comparison must result in the segment being dropped, and must not produce any response back to the sender (§2.0) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L82) | unit/verify | revert, verified | | positive | [`TestRFC2385MismatchedKeyIsDroppedWithNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L156) | unit/verify | revert, verified | ### [`RFC2385-2.0-4`](#rfc2385-2.0-4) The absence of the option in the SYN,ACK segment must not cause the sender to disable its sending of signatures (§2.0) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2385FailedConnectDoesNotDisableSigning`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2385_test.go#L134) | unit/verify | revert, unit-changed (the tagged unit's behavior changed since the red was observed) | ### [`RFC2385-2.0-5`](#rfc2385-2.0-5) The sending of signatures must be under the complete control of the application, not at the mercy of the remote host not understanding the option (§2.0) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2385NoKeyWithoutConfiguration`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2385_test.go#L90) | unit/verify | revert, verified | | positive | [`TestRFC2385ConfiguredKeyReachesBothSockets`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2385_test.go#L40) | unit/verify | revert, verified | ### [`RFC2385-2.0-6`](#rfc2385-2.0-6) Every segment sent on a protected connection carries the 16-byte MD5 digest of the TCP pseudo-header, the TCP header with a zero checksum, the segment data and the key, in that order (§2.0) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2385MismatchedKeyIsDroppedWithNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L159) | unit/verify | revert, verified | | positive | [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L84) | unit/verify | revert, verified | ### [`RFC2385-3.0-1`](#rfc2385-3.0-1) The option is Kind 19, Length 18, carrying a 16-byte digest, and it appears in every segment of the connection (§3.0) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2385MismatchedKeyIsDroppedWithNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L162) | unit/verify | revert, verified | | positive | [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L87) | unit/verify | revert, verified | ### [`RFC2385-4.3-1`](#rfc2385-4.3-1) The size of the MD5 option must be factored into the MSS offered to the other side during connection negotiation (§4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L90) | unit/verify | revert, verified | ### [`RFC2385-4.3-2`](#rfc2385-4.3-2) The total size of the TCP header plus its options must be less than or equal to 60 bytes, leaving 40 bytes for options (§4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2385MatchingKeysCarryASignedSession`](https://github.com/ze-software/ze/blob/main/internal/core/network/md5_rfc2385_linux_test.go#L93) | unit/verify | revert, verified | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement walk agent, spec-rfcgate-6-supported-extraction-signoff phase 7 (Class B, rfc2385) | | Signed off | 2026-09-01 | | Register | prose | | Source | rfc/full/rfc2385.txt | | Source fingerprint | d4667cc1ab9fd8ab | | Record | rfc/extraction/rfc2385.json | | Mapped sentences | 7 | | Declined as scope | 1 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, Copyright Notice, IESG Note and Abstract. The IESG Note records that the mechanism is weak against a concerted attack and the Abstract restates section 1; neither states an obligation. | | `1.0` | Introduction | 0 | walked | Introduction. Indicative throughout: what an attacker would have to guess, that the password never appears in the connection stream, that its form is up to the application, and that there is no negotiation for the option because its use is site policy. The last of these is the ground the section 2 sentence at site 2.0:5 states normatively, so nothing is left unmapped here. | | `2.0` | Proposal | 5 | walked | Proposal. The core of the document: what is signed, in what order, what a receiver does with the digest, and who controls signing. Five derived sites, all mapped. Its first sentence, 'Every segment sent on a TCP connection to be protected against spoofing will contain the 16-byte MD5 digest produced by applying the MD5 algorithm to these items in the following order', carries the whole computation and no modal, so the site scan cannot see it; it is captured as RFC2385-2.0-6. | | `3.0` | Syntax | 0 | walked | Syntax. The option diagram and the sentence 'The MD5 digest is always 16 bytes in length, and the option would appear in every segment of a connection'. Both are written in the indicative, so the scan derives no site, and the encoding they fix is captured as RFC2385-3.0-1. | | `4.0` | not stated | 0 | walked | 'Some Implications' is a heading with no body of its own; its four subsections follow. | | `4.1` | Connectionless Resets | 0 | walked | Connectionless Resets. A consequence, not an obligation: a reset from a party without the key is ignored, so a connect to a port with no listener times out instead of being refused and a stale-connection reset no longer clears the session quickly. | | `4.2` | Performance | 0 | walked | Performance. Two measured digest timings on a 100 MHz R4600 and the note that the cost is paid on both paths. No obligation. | | `4.3` | TCP Header Size | 2 | walked | TCP Header Size. Two derived sites, both mapped: the option's 18 octets have to be allowed for in the MSS offered at setup, and the whole header including options has to fit the 60 bytes the data-offset field can express. The 4.4BSD worked example that follows is arithmetic over those two, and states nothing further. | | `4.4` | MD5 as a Hashing Algorithm | 0 | walked | MD5 as a Hashing Algorithm. Why the memo keeps MD5 despite the collision-search result: the option is already deployed and carries no algorithm-type field. It states what a FUTURE document could do and binds nobody here. | | `4.5` | Key configuration | 0 | walked | Key configuration. One obligation, written as 'It is strongly recommended that an implementation be able to support at minimum a key composed of a string of printable ASCII of 80 bytes or less, as this is current practice'. 'strongly recommended' is SHOULD-level and carries no modal the scan counts, so it is captured as RFC2385-4.5-1. | | `5.0` | Security Considerations | 0 | walked | Security Considerations. States that this is a weak but currently practiced mechanism and that stronger ones are expected later. No obligation. | | `6.0` | not stated | 1 | skipped (references) | References, the author's address and the Full Copyright Statement, which the section parser attached to the last numbered heading. Its one derived site is the copyright licence sentence. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `6.0:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Boilerplate the extractor did not strip: the Full Copyright Statement's licence condition on modifying and republishing THIS DOCUMENT. It binds a party redistributing the memo, not a TCP implementation, and states nothing about a segment, a digest or a key. | However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English. | ## Superseded No document obsoletes RFC 2385, so its obligations are stated where they were written. --- ### Page: RFC 2473 - Generic Packet Tunneling in IPv6 Specification https://ze-software.net/quality/rfc-compliance/rfc2473/ # RFC 2473 - Generic Packet Tunneling in IPv6 Specification No row in the public ledger. Every requirement this repository extracted from RFC 2473, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 11 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 11 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 11 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 11 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 11 | of 21 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 11 | of 11 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 11 of 11 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 11 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 11 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 11 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 21 | | Gated MUST-level | 11 | | Not applicable, so out of scope | 11 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2473.md` | | Requirement shard | `rfc/requirements/rfc2473.md` | | RFC text | `rfc/full/rfc2473.txt` | ## Enrolment Enrolled: Generic Packet Tunneling in IPv6 (tunnel datapath delegated to kernel ip6_tunnel / VPP; ze configures netdevs only) ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 2473. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 11 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **11** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (11):** [`RFC2473-4.1.1-1`](#rfc2473-4.1.1-1), [`RFC2473-4.1.1-2`](#rfc2473-4.1.1-2), [`RFC2473-4.1.1-3`](#rfc2473-4.1.1-3), [`RFC2473-4.1.1-4`](#rfc2473-4.1.1-4), [`RFC2473-4.1.2-1`](#rfc2473-4.1.2-1), [`RFC2473-7.1-1`](#rfc2473-7.1-1), [`RFC2473-7.1-2`](#rfc2473-7.1-2), [`RFC2473-7.1-3`](#rfc2473-7.1-3), [`RFC2473-7.1-4`](#rfc2473-7.1-4), [`RFC2473-7.2-1`](#rfc2473-7.2-1), [`RFC2473-8-1`](#rfc2473-8-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2473-4.1.1-1` | When Tunnel Encapsulation Limit option value reaches zero, discard packet and send ICMPv6 Parameter Problem (code 0, pointer to limit octet) (§4.1.1) | MUST | 4.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the tunnel-node datapath that processes the encapsulation-limit option and emits ICMPv6 Parameter Problem is the kernel ip6_tunnel module; ze only creates and configures the tunnel netdev (internal/plugins/iface/netlink/tunnel_linux.go:44) | | `RFC2473-4.1.1-2` | When Tunnel Encapsulation Limit option is found with non-zero value, include a new Tunnel Encapsulation Limit option in the encapsulating headers with value decremented by one (§4.1.1) | MUST | 4.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** decrementing and re-emitting the encapsulation-limit option during encapsulation is a kernel/VPP datapath action; ze supplies only the configured limit via netlink (internal/plugins/iface/netlink/tunnel_linux.go:266) | | `RFC2473-4.1.1-3` | When no Tunnel Encapsulation Limit option is found but a limit is configured, include a Tunnel Encapsulation Limit option with the configured value (§4.1.1) | MUST | 4.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** inserting the configured encapsulation-limit option is done by the kernel ip6_tunnel datapath; ze passes IFLA_IPTUN_ENCAP_LIMIT at internal/plugins/iface/netlink/tunnel_linux.go:266-267 | | `RFC2473-4.1.1-4` | Examine headers following the IPv6 header in strict left-to-right order when checking for Tunnel Encapsulation Limit option (§4.1.1) | MUST | 4.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** left-to-right extension-header parsing at packet time is kernel datapath parsing; ze carries no per-packet header path, only tunnel netdev creation (internal/plugins/iface/netlink/tunnel_linux.go:44) | | `RFC2473-4.1.2-1` | Loopback encapsulation (entry-point and exit-point are the same node) must be avoided (§4.1.2) | MUST | 4.1.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** detecting loopback encapsulation at packet time binds the kernel ip6_tunnel datapath; ze only creates the tunnel netdev (internal/plugins/iface/netlink/tunnel_linux.go:44) | | `RFC2473-7.1-1` | Tunnel entry-point node must support fragmentation of tunnel IPv6 packets (§7.1) | MUST | 7.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** fragmenting outbound tunnel packets is a kernel/VPP datapath capability; ze holds no packet-forwarding code, only tunnel config (internal/plugins/iface/netlink/tunnel_linux.go:248) | | `RFC2473-7.1-2` | Tunnel intermediate node must not fragment a packet undergoing forwarding (§7.1) | MUST NOT | 7.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is a routing control plane with no IPv6 forwarding datapath; the intermediate-node no-fragment rule is enforced by the kernel/VPP | | `RFC2473-7.1-3` | If original IPv6 packet exceeds tunnel MTU and is larger than IPv6 minimum link MTU, discard and send ICMPv6 Packet Too Big with MTU = max(tunnel MTU, IPv6 minimum link MTU) (§7.1) | MUST | 7.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** tunnel-MTU comparison, discard, and ICMPv6 Packet Too Big generation are kernel ip6_tunnel datapath actions ze does not perform | | `RFC2473-7.1-4` | If original IPv6 packet exceeds tunnel MTU but is equal or smaller than IPv6 minimum link MTU, encapsulate then fragment the tunnel packet (§7.1) | MUST | 7.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** encapsulate-then-fragment is a kernel/VPP datapath operation; ze only configures the tunnel interface (internal/plugins/iface/netlink/tunnel_linux.go:248) | | `RFC2473-7.2-1` | If original IPv4 packet has DF set and exceeds tunnel MTU, discard and send ICMP unreachable/packet-too-big with tunnel MTU (§7.2) | MUST | 7.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** DF handling and ICMP unreachable/too-big generation for encapsulated IPv4 are kernel datapath behavior; ze only sets PMtuDisc via netlink (internal/plugins/iface/netlink/tunnel_linux.go:210-214) | | `RFC2473-8-1` | Tunnel entry-point node must relay ICMP messages from inside the tunnel to the source of the original packet (§8) | MUST | 8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** relaying tunnel-internal ICMP errors to the original source is a kernel ip6_tunnel datapath function ze does not perform | | `RFC2473-3.1-1` | Tunnel extension headers should appear in the order recommended by IPv6 specifications (§3.1, §5.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-6.3-1` | The "single-hop" mechanism should be implemented by setting tunnel hop limit independently of the original header (§6.3) | SHOULD | 6.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-4.1.2-2` | Implementation should check and reject configuration of a tunnel where entry-point and exit-point addresses belong to the same node (§4.1.2) | SHOULD | 4.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-4.1.2-3` | Encapsulating engine should check for and reject encapsulation where tunnel endpoint addresses match original packet source/destination addresses (§4.1.2) | SHOULD | 4.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-4.1.3-1` | Maximum hops on a path with tunnels should be controlled by both original packet hop limit and tunnel encapsulation limit (§4.1.3) | SHOULD | 4.1.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-6.3-2` | Tunnel hop limit should be configured to ensure packets reach exit-point and expire quickly on routing loops (§6.3) | SHOULD | 6.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-6.1-1` | Tunnel entry-point node address should be validated at tunnel configuration time (§6.1) | SHOULD | 6.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-6.4-1` | Traffic Class in tunnel header MAY be inherited from inner packet or set per-tunnel (§6.4) | MAY | 6.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-4.1.1-5` | Implementations MAY allow per-tunnel override of the encapsulation limit (§4.1.1, §6.6) | MAY | 4.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2473-5.1-1` | Tunnel entry-point node may append IPv6 extension headers (Hop-by-Hop, Routing, etc.) to the tunnel header (§5.1) | MAY | 5.1 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2473-4.1.1-1`](#rfc2473-4.1.1-1) When Tunnel Encapsulation Limit option value reaches zero, discard packet and send ICMPv6 Parameter Problem (code 0, pointer to limit octet) (§4.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: the tunnel-node datapath that processes the encapsulation-limit option and emits ICMPv6 Parameter Problem is the kernel ip6_tunnel module; ze only creates and configures the tunnel netdev (internal/plugins/iface/netlink/tunnel_linux.go:44) | | [`RFC2473-4.1.1-2`](#rfc2473-4.1.1-2) When Tunnel Encapsulation Limit option is found with non-zero value, include a new Tunnel Encapsulation Limit option in the encapsulating headers with value decremented by one (§4.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: decrementing and re-emitting the encapsulation-limit option during encapsulation is a kernel/VPP datapath action; ze supplies only the configured limit via netlink (internal/plugins/iface/netlink/tunnel_linux.go:266) | | [`RFC2473-4.1.1-3`](#rfc2473-4.1.1-3) When no Tunnel Encapsulation Limit option is found but a limit is configured, include a Tunnel Encapsulation Limit option with the configured value (§4.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: inserting the configured encapsulation-limit option is done by the kernel ip6_tunnel datapath; ze passes IFLA_IPTUN_ENCAP_LIMIT at internal/plugins/iface/netlink/tunnel_linux.go:266-267 | | [`RFC2473-4.1.1-4`](#rfc2473-4.1.1-4) Examine headers following the IPv6 header in strict left-to-right order when checking for Tunnel Encapsulation Limit option (§4.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: left-to-right extension-header parsing at packet time is kernel datapath parsing; ze carries no per-packet header path, only tunnel netdev creation (internal/plugins/iface/netlink/tunnel_linux.go:44) | | [`RFC2473-4.1.2-1`](#rfc2473-4.1.2-1) Loopback encapsulation (entry-point and exit-point are the same node) must be avoided (§4.1.2) | no test | no test carries this requirement id; annotated {not-applicable}: detecting loopback encapsulation at packet time binds the kernel ip6_tunnel datapath; ze only creates the tunnel netdev (internal/plugins/iface/netlink/tunnel_linux.go:44) | | [`RFC2473-7.1-1`](#rfc2473-7.1-1) Tunnel entry-point node must support fragmentation of tunnel IPv6 packets (§7.1) | no test | no test carries this requirement id; annotated {not-applicable}: fragmenting outbound tunnel packets is a kernel/VPP datapath capability; ze holds no packet-forwarding code, only tunnel config (internal/plugins/iface/netlink/tunnel_linux.go:248) | | [`RFC2473-7.1-2`](#rfc2473-7.1-2) Tunnel intermediate node must not fragment a packet undergoing forwarding (§7.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze is a routing control plane with no IPv6 forwarding datapath; the intermediate-node no-fragment rule is enforced by the kernel/VPP | | [`RFC2473-7.1-3`](#rfc2473-7.1-3) If original IPv6 packet exceeds tunnel MTU and is larger than IPv6 minimum link MTU, discard and send ICMPv6 Packet Too Big with MTU = max(tunnel MTU, IPv6 minimum link MTU) (§7.1) | no test | no test carries this requirement id; annotated {not-applicable}: tunnel-MTU comparison, discard, and ICMPv6 Packet Too Big generation are kernel ip6_tunnel datapath actions ze does not perform | | [`RFC2473-7.1-4`](#rfc2473-7.1-4) If original IPv6 packet exceeds tunnel MTU but is equal or smaller than IPv6 minimum link MTU, encapsulate then fragment the tunnel packet (§7.1) | no test | no test carries this requirement id; annotated {not-applicable}: encapsulate-then-fragment is a kernel/VPP datapath operation; ze only configures the tunnel interface (internal/plugins/iface/netlink/tunnel_linux.go:248) | | [`RFC2473-7.2-1`](#rfc2473-7.2-1) If original IPv4 packet has DF set and exceeds tunnel MTU, discard and send ICMP unreachable/packet-too-big with tunnel MTU (§7.2) | no test | no test carries this requirement id; annotated {not-applicable}: DF handling and ICMP unreachable/too-big generation for encapsulated IPv4 are kernel datapath behavior; ze only sets PMtuDisc via netlink (internal/plugins/iface/netlink/tunnel_linux.go:210-214) | | [`RFC2473-8-1`](#rfc2473-8-1) Tunnel entry-point node must relay ICMP messages from inside the tunnel to the source of the original packet (§8) | no test | no test carries this requirement id; annotated {not-applicable}: relaying tunnel-internal ICMP errors to the original source is a kernel ip6_tunnel datapath function ze does not perform | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2473-4.1.1-1`](#rfc2473-4.1.1-1) When Tunnel Encapsulation Limit option value reaches zero, discard packet and send ICMPv6 Parameter Problem (code 0, pointer to limit octet) (§4.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-4.1.1-1, so no unit is bound to it. ### [`RFC2473-4.1.1-2`](#rfc2473-4.1.1-2) When Tunnel Encapsulation Limit option is found with non-zero value, include a new Tunnel Encapsulation Limit option in the encapsulating headers with value decremented by one (§4.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-4.1.1-2, so no unit is bound to it. ### [`RFC2473-4.1.1-3`](#rfc2473-4.1.1-3) When no Tunnel Encapsulation Limit option is found but a limit is configured, include a Tunnel Encapsulation Limit option with the configured value (§4.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-4.1.1-3, so no unit is bound to it. ### [`RFC2473-4.1.1-4`](#rfc2473-4.1.1-4) Examine headers following the IPv6 header in strict left-to-right order when checking for Tunnel Encapsulation Limit option (§4.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-4.1.1-4, so no unit is bound to it. ### [`RFC2473-4.1.2-1`](#rfc2473-4.1.2-1) Loopback encapsulation (entry-point and exit-point are the same node) must be avoided (§4.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-4.1.2-1, so no unit is bound to it. ### [`RFC2473-7.1-1`](#rfc2473-7.1-1) Tunnel entry-point node must support fragmentation of tunnel IPv6 packets (§7.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-7.1-1, so no unit is bound to it. ### [`RFC2473-7.1-2`](#rfc2473-7.1-2) Tunnel intermediate node must not fragment a packet undergoing forwarding (§7.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-7.1-2, so no unit is bound to it. ### [`RFC2473-7.1-3`](#rfc2473-7.1-3) If original IPv6 packet exceeds tunnel MTU and is larger than IPv6 minimum link MTU, discard and send ICMPv6 Packet Too Big with MTU = max(tunnel MTU, IPv6 minimum link MTU) (§7.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-7.1-3, so no unit is bound to it. ### [`RFC2473-7.1-4`](#rfc2473-7.1-4) If original IPv6 packet exceeds tunnel MTU but is equal or smaller than IPv6 minimum link MTU, encapsulate then fragment the tunnel packet (§7.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-7.1-4, so no unit is bound to it. ### [`RFC2473-7.2-1`](#rfc2473-7.2-1) If original IPv4 packet has DF set and exceeds tunnel MTU, discard and send ICMP unreachable/packet-too-big with tunnel MTU (§7.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-7.2-1, so no unit is bound to it. ### [`RFC2473-8-1`](#rfc2473-8-1) Tunnel entry-point node must relay ICMP messages from inside the tunnel to the source of the original packet (§8) Audit verdict: not audited: no reader has judged these tests No test carries RFC2473-8-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2473, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2473, so its obligations are stated where they were written. --- ### Page: RFC 2516 - A Method for Transmitting PPP Over Ethernet (PPPoE) https://ze-software.net/quality/rfc-compliance/rfc2516/ # RFC 2516 - A Method for Transmitting PPP Over Ethernet (PPPoE) Partial. Every requirement this repository extracted from RFC 2516, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 68.2% | 15 of 22 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 31.8% | 7 of 22 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 22 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 22 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 10.3% | 4 of 39 tagged units, 0 escaped and 1 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 22 | of 26 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 22 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 22 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 22 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 22 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 22 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 26 | | Gated MUST-level | 22 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 39 | | Tagged units | 39 | | Recorded audit verdicts | 0 | | Discrimination records | 5 | | Summary | `rfc/short/rfc2516.md` | | Requirement shard | `rfc/requirements/rfc2516.md` | | RFC text | `rfc/full/rfc2516.txt` | ## Enrolment Enrolled: A Method for Transmitting PPP Over Ethernet / PPPoE (RFC 2516): AC + client, both roles. 15 MET (VER/TYPE nibble=1 and discard ver/type!=0x11, reject broadcast/multicast source, Relay-Session-Id echo, Host-Uniq echo in PADO/PADS, AC-Cookie + Host-Uniq echo in PADR, no PADO for unservable Service-Name, PADT verified on SESSION_ID+source MAC, PADO and PADS always carry exactly one Service-Name tag, a PADR the AC will not serve is refused with a Service-Name-Error tag and SESSION_ID 0x0000) + 7 single-polarity positive (no non-zero End-Of-List, unicast destinations, unknown tags tolerated, client LCP MRU 1492, PADI one Service-Name to broadcast, PADR one Service-Name with presence checked but not count on receipt) + 0 gap ## What the public ledger says **Status:** Partial **What the ledger says is covered:** Access concentrator (PADI/PADO/PADR/PADS/PADT discovery, AC-Cookie, session tables, AF_PPPOX kernel sessions) and PPPoE client/Host dialer, over the shared PPP driver. Tests bound per requirement in [`rfc/requirements/rfc2516.md`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc2516.md). **What the ledger says remains** No MUST gap remains gated in [`rfc/short/rfc2516.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2516.md). [`RFC2516-5.2-2`](#rfc2516-5.2-2) (BuildPADO omitting Service-Name under the accept-any config) closed 2026-09-09: BuildPADO and BuildPADS now write the Service-Name tag through AddTagString unconditionally, so an absent or zero-length source value still yields exactly one tag. The receive-side tolerance that remains -- a PADI or PADR with no Service-Name tag is served/admitted rather than refused for count, and a duplicate tag resolves first-wins -- is a deliberate robustness choice recorded in [`docs/architecture/l2tp/bng-5-pppoe.md`](https://github.com/ze-software/ze/blob/main/docs/architecture/l2tp/bng-5-pppoe.md), not an unmet MUST: Sections 5.1 and 5.3 bind the sending host, and no reference AC (accel-ppp, FreeBSD) enforces those counts on receipt either. Status stays `Partial` rather than `Supported` because no [`rfc/extraction/rfc2516.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/rfc2516.json) sign-off exists yet to bound what this summary's extraction may have missed. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 15 | one part of the gated population | | Annotated instead of tested | 7 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **22** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (15):** [`RFC2516-x-1`](#rfc2516-x-1), [`RFC2516-x-2`](#rfc2516-x-2), [`RFC2516-x-3`](#rfc2516-x-3), [`RFC2516-5.2-1`](#rfc2516-5.2-1), [`RFC2516-5.3-1`](#rfc2516-5.3-1), [`RFC2516-x-5`](#rfc2516-x-5), [`RFC2516-5.2-2`](#rfc2516-5.2-2), [`RFC2516-5.2-3`](#rfc2516-5.2-3), [`RFC2516-5.2-4`](#rfc2516-5.2-4), [`RFC2516-5.3-3`](#rfc2516-5.3-3), [`RFC2516-5.3-4`](#rfc2516-5.3-4), [`RFC2516-5.4-1`](#rfc2516-5.4-1), [`RFC2516-5.4-2`](#rfc2516-5.4-2), [`RFC2516-x-7`](#rfc2516-x-7), [`RFC2516-7-1`](#rfc2516-7-1) **Annotated instead of tested (7):** [`RFC2516-x-4`](#rfc2516-x-4), [`RFC2516-5.1-1`](#rfc2516-5.1-1), [`RFC2516-5.1-2`](#rfc2516-5.1-2), [`RFC2516-5.3-2`](#rfc2516-5.3-2), [`RFC2516-x-6`](#rfc2516-x-6), [`RFC2516-x-8`](#rfc2516-x-8), [`RFC2516-x-9`](#rfc2516-x-9) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2516-x-1` | VER field MUST be 0x1 (Wire Format) | MUST | x | **positive:** `unit/verify` [`TestBuildFrameVerType`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L452). **negative:** `unit/verify` [`TestParseBadVersion`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L303) | | `RFC2516-x-2` | TYPE field MUST be 0x1 (Wire Format) | MUST | x | **positive:** `unit/verify` [`TestBuildFrameVerType`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L453). **negative:** `unit/verify` [`TestParseBadType`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L472) | | `RFC2516-x-3` | Packets with VER or TYPE other than 0x1 MUST be silently discarded (Encoding Rules) | MUST | x | **positive:** `unit/verify` [`TestParsePADI`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L46). **negative:** `unit/verify` [`TestParseBadVersion`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L304) | | `RFC2516-x-4` | End-Of-List tag TAG_LENGTH MUST be 0 (Tag Catalog) | MUST | x | **positive:** `unit/verify` [`TestParseEndOfListTag`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L363). **negative:** no negative test. **{single-polarity}:** ze never constructs an End-Of-List tag (builders set the payload length instead, discovery.go:291), and parseTags treats a zero-length End-Of-List as the list terminator (discovery.go:161), so no non-zero-length End-Of-List is ever produced and no negative case exists | | `RFC2516-5.2-1` | AC MUST echo Host-Uniq unchanged in PADO and PADS if Host included it in PADI/PADR (Encoding Rules, §5.2, §5.3) | MUST | 5.2 | **positive:** `unit/verify` [`TestBuildPADO`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L125). **positive:** `unit/verify` [`TestBuildPADS`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L175). **negative:** `unit/verify` [`TestBuildNoHostUniqEcho`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L580) | | `RFC2516-5.3-1` | Host MUST echo AC-Cookie unchanged in PADR if AC included it in PADO (Encoding Rules, §5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestBuildPADREchoesTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L506). **negative:** `unit/verify` [`TestBuildPADRNoOptionalTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L549) | | `RFC2516-x-5` | Relay-Session-Id, if present, MUST be included unchanged in all subsequent Discovery packets for the exchange (Encoding Rules) | MUST | x | **positive:** `unit/verify` [`TestRelaySessionIDEcho`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L618). **negative:** `unit/verify` [`TestRelaySessionIDNoEcho`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L661) | | `RFC2516-5.1-1` | PADI MUST contain exactly one Service-Name tag (§5.1) | MUST | 5.1 | **positive:** `unit/verify` [`TestBuildPADIDiscovery`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L483). **negative:** no negative test. **{single-polarity}:** emit-side count: BuildPADI always writes exactly one Service-Name tag (discovery.go:307). The AC does not enforce the count on receive (MatchServiceName tolerates zero or many, discovery.go:220): this is a deliberate choice, not an omission, per the owner decision of 2026-09-08 recorded in docs/architecture/l2tp/bng-5-pppoe.md ("Ze refuses a PADR with no Service-Name tag and serves a PADI with none"), because Section 5.1 binds the sending host and neither accel-ppp nor FreeBSD enforces this count on receipt either | | `RFC2516-5.1-2` | PADI destination MUST be broadcast; non-broadcast PADI is silently discarded (§5.1, Encoding Rules) | MUST | 5.1 | **positive:** `unit/verify` [`TestBuildPADIDiscovery`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L484). **negative:** no negative test. **{single-polarity}:** emit-side: BuildPADI addresses the PADI to the Ethernet broadcast MAC (discovery.go:305); the receive-side non-broadcast-PADI discard is not enforced (handlePADI checks only SID, server.go:53) | | `RFC2516-5.2-2` | PADO MUST contain one AC-Name tag and one or more Service-Name tags (§5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestPADOAlwaysCarriesServiceName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L728). **negative:** `unit/verify` [`TestPADOAlwaysCarriesServiceName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L729) | | `RFC2516-5.2-3` | PADO MUST echo Host-Uniq if present in PADI (§5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestBuildPADO`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L126). **negative:** `unit/verify` [`TestBuildNoHostUniqEcho`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L581) | | `RFC2516-5.2-4` | PADO MUST NOT be sent if AC cannot serve the requested Service-Name (§5.2) | MUST NOT | 5.2 | **positive:** `unit/verify` [`TestServiceNameFilter`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L243). **negative:** `unit/verify` [`TestServiceNameFilter`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L244) | | `RFC2516-5.3-2` | PADR MUST contain exactly one Service-Name tag from the selected PADO (§5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestBuildPADREchoesTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L509). **negative:** no negative test. **{single-polarity}:** emit-side count: BuildPADR writes exactly one Service-Name tag (discovery.go:322). The AC's requireServiceNameTag (server.go) now refuses a PADR carrying zero Service-Name tags on receipt, but that enforces PRESENCE, not COUNT: a PADR carrying two Service-Name tags is accepted, first-wins (TestDuplicateServiceNameTagTakesTheFirst), so there is still no receive-side enforcement of "exactly one, not more" to give a negative for this requirement | | `RFC2516-5.3-3` | PADR MUST echo AC-Cookie if one was in the PADO (§5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestBuildPADREchoesTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L507). **negative:** `unit/verify` [`TestBuildPADRNoOptionalTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L550) | | `RFC2516-5.3-4` | PADR MUST echo Host-Uniq if one was in the PADI (§5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestBuildPADREchoesTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L508). **negative:** `unit/verify` [`TestBuildPADRNoOptionalTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L551) | | `RFC2516-5.4-1` | PADS MUST contain exactly one Service-Name tag (§5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestBuildPADS`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L176). **negative:** `unit/verify` [`TestPADSAlwaysCarriesServiceName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L785) | | `RFC2516-5.4-2` | AC that does not like the PADR's Service-Name MUST reply with a PADS carrying a Service-Name-Error tag, and MUST set SESSION_ID to 0x0000 in that reply (§5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestPADRWithoutServiceNameGetsServiceNameError`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/server_test.go#L156). **negative:** `unit/verify` [`TestBuildPADS`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L177) | | `RFC2516-x-6` | PADO/PADR/PADS/PADT destination MUST be unicast; broadcast destination is silently discarded (Encoding Rules) | MUST | x | **positive:** `unit/verify` [`TestBuildPADT`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L220). **negative:** no negative test. **{single-polarity}:** emit-side: every builder addresses PADO/PADR/PADS/PADT to the peer unicast MAC derived from its unicast source (discovery.go:320, 336, 362, 387); the receive-side broadcast-destination discard is not enforced (ParseDiscovery validates only the source, discovery.go:108) | | `RFC2516-x-7` | Source address MUST NOT be broadcast or multicast (Encoding Rules) | MUST NOT | x | **positive:** `unit/verify` [`TestParsePADI`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L47). **negative:** `unit/verify` [`TestParseBroadcastSource`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L330). **negative:** `unit/verify` [`TestParseMulticastSource`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L340) | | `RFC2516-x-8` | Unknown tags MUST NOT cause an error; silently ignore (Encoding Rules) | MUST | x | **positive:** `unit/verify` [`TestUnknownTagIgnored`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L681). **negative:** no negative test. **{single-polarity}:** tolerate requirement: parseTags stores unknown tag types generically and never errors on them (discovery.go:154); rejecting an unknown tag would itself violate the MUST NOT, so no negative exists | | `RFC2516-x-9` | LCP MUST negotiate MRU to 1492 or lower unless both sides support RFC 4638 and the Ethernet path supports larger frames (MTU) | MUST | x | **positive:** `unit/verify` [`TestLCPConfigRequestMRU`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/session_test.go#L117). **negative:** no negative test. **{single-polarity}:** ceiling requirement: the client proposes MRU 1492 by default in its LCP Configure-Request (dialer.go:114, session.go:487) and the AC caps MaxMRU at PPPoEMaxMTU 1492 (server.go:203); 1492 is the maximum so there is no negative | | `RFC2516-7-1` | AC MUST verify SESSION_ID + source MAC pair on every Session and PADT packet (Security, §7) | MUST | 7 | **positive:** `unit/verify` [`TestHandlePADTVerifiesMACAndSID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/server_test.go#L12). **negative:** `unit/verify` [`TestHandlePADTVerifiesMACAndSID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/server_test.go#L13) | | `RFC2516-5.2-5` | PADO SHOULD contain AC-Cookie for DoS mitigation (§5.2) | SHOULD | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2516-5.1-3` | PADI MAY contain Host-Uniq for client-side demux of PADO replies (§5.1) | MAY | 5.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2516-5.5-1` | PADT MAY be sent by either side at any time after PADS (§5.5) | MAY | 5.5 | **positive:** no positive test. **negative:** no negative test | | `RFC2516-5.5-2` | PADT MAY contain Generic-Error tag (§5.5) | MAY | 5.5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 2516 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2516-x-1`](#rfc2516-x-1) VER field MUST be 0x1 (Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseBadVersion`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L303) | unit/verify | unproven | | positive | [`TestBuildFrameVerType`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L452) | unit/verify | unproven | ### [`RFC2516-x-2`](#rfc2516-x-2) TYPE field MUST be 0x1 (Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseBadType`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L472) | unit/verify | unproven | | positive | [`TestBuildFrameVerType`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L453) | unit/verify | unproven | ### [`RFC2516-x-3`](#rfc2516-x-3) Packets with VER or TYPE other than 0x1 MUST be silently discarded (Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseBadVersion`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L304) | unit/verify | unproven | | positive | [`TestParsePADI`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L46) | unit/verify | unproven | ### [`RFC2516-x-4`](#rfc2516-x-4) End-Of-List tag TAG_LENGTH MUST be 0 (Tag Catalog) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestParseEndOfListTag`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L363) | unit/verify | unproven | ### [`RFC2516-5.2-1`](#rfc2516-5.2-1) AC MUST echo Host-Uniq unchanged in PADO and PADS if Host included it in PADI/PADR (Encoding Rules, §5.2, §5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildNoHostUniqEcho`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L580) | unit/verify | unproven | | positive | [`TestBuildPADO`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L125) | unit/verify | unproven | | positive | [`TestBuildPADS`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L175) | unit/verify | unproven | ### [`RFC2516-5.3-1`](#rfc2516-5.3-1) Host MUST echo AC-Cookie unchanged in PADR if AC included it in PADO (Encoding Rules, §5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildPADRNoOptionalTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L549) | unit/verify | unproven | | positive | [`TestBuildPADREchoesTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L506) | unit/verify | unproven | ### [`RFC2516-x-5`](#rfc2516-x-5) Relay-Session-Id, if present, MUST be included unchanged in all subsequent Discovery packets for the exchange (Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRelaySessionIDNoEcho`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L661) | unit/verify | unproven | | positive | [`TestRelaySessionIDEcho`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L618) | unit/verify | unproven | ### [`RFC2516-5.1-1`](#rfc2516-5.1-1) PADI MUST contain exactly one Service-Name tag (§5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestBuildPADIDiscovery`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L483) | unit/verify | unproven | ### [`RFC2516-5.1-2`](#rfc2516-5.1-2) PADI destination MUST be broadcast; non-broadcast PADI is silently discarded (§5.1, Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestBuildPADIDiscovery`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L484) | unit/verify | unproven | ### [`RFC2516-5.2-2`](#rfc2516-5.2-2) PADO MUST contain one AC-Name tag and one or more Service-Name tags (§5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPADOAlwaysCarriesServiceName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L729) | unit/verify | revert, verified | | positive | [`TestPADOAlwaysCarriesServiceName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L728) | unit/verify | revert, verified | ### [`RFC2516-5.2-3`](#rfc2516-5.2-3) PADO MUST echo Host-Uniq if present in PADI (§5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildNoHostUniqEcho`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L581) | unit/verify | unproven | | positive | [`TestBuildPADO`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L126) | unit/verify | unproven | ### [`RFC2516-5.2-4`](#rfc2516-5.2-4) PADO MUST NOT be sent if AC cannot serve the requested Service-Name (§5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestServiceNameFilter`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L244) | unit/verify | unproven | | positive | [`TestServiceNameFilter`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L243) | unit/verify | unproven | ### [`RFC2516-5.3-2`](#rfc2516-5.3-2) PADR MUST contain exactly one Service-Name tag from the selected PADO (§5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestBuildPADREchoesTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L509) | unit/verify | unproven | ### [`RFC2516-5.3-3`](#rfc2516-5.3-3) PADR MUST echo AC-Cookie if one was in the PADO (§5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildPADRNoOptionalTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L550) | unit/verify | unproven | | positive | [`TestBuildPADREchoesTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L507) | unit/verify | unproven | ### [`RFC2516-5.3-4`](#rfc2516-5.3-4) PADR MUST echo Host-Uniq if one was in the PADI (§5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildPADRNoOptionalTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L551) | unit/verify | unproven | | positive | [`TestBuildPADREchoesTags`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L508) | unit/verify | unproven | ### [`RFC2516-5.4-1`](#rfc2516-5.4-1) PADS MUST contain exactly one Service-Name tag (§5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPADSAlwaysCarriesServiceName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L785) | unit/verify | revert, verified | | positive | [`TestBuildPADS`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L176) | unit/verify | unproven | ### [`RFC2516-5.4-2`](#rfc2516-5.4-2) AC that does not like the PADR's Service-Name MUST reply with a PADS carrying a Service-Name-Error tag, and MUST set SESSION_ID to 0x0000 in that reply (§5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildPADS`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L177) | unit/verify | revert, verified | | positive | [`TestPADRWithoutServiceNameGetsServiceNameError`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/server_test.go#L156) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | ### [`RFC2516-x-6`](#rfc2516-x-6) PADO/PADR/PADS/PADT destination MUST be unicast; broadcast destination is silently discarded (Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestBuildPADT`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L220) | unit/verify | unproven | ### [`RFC2516-x-7`](#rfc2516-x-7) Source address MUST NOT be broadcast or multicast (Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseBroadcastSource`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L330) | unit/verify | unproven | | negative | [`TestParseMulticastSource`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L340) | unit/verify | unproven | | positive | [`TestParsePADI`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L47) | unit/verify | unproven | ### [`RFC2516-x-8`](#rfc2516-x-8) Unknown tags MUST NOT cause an error; silently ignore (Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestUnknownTagIgnored`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/discovery_test.go#L681) | unit/verify | unproven | ### [`RFC2516-x-9`](#rfc2516-x-9) LCP MUST negotiate MRU to 1492 or lower unless both sides support RFC 4638 and the Ethernet path supports larger frames (MTU) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestLCPConfigRequestMRU`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoeclient/session_test.go#L117) | unit/verify | unproven | ### [`RFC2516-7-1`](#rfc2516-7-1) AC MUST verify SESSION_ID + source MAC pair on every Session and PADT packet (Security, §7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestHandlePADTVerifiesMACAndSID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/server_test.go#L13) | unit/verify | unproven | | positive | [`TestHandlePADTVerifiesMACAndSID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/pppoe/server_test.go#L12) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 2516, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2516, so its obligations are stated where they were written. --- ### Page: RFC 2545 - Use of BGP-4 Multiprotocol Extensions for IPv6 Inter-Domain Routing https://ze-software.net/quality/rfc-compliance/rfc2545/ # RFC 2545 - Use of BGP-4 Multiprotocol Extensions for IPv6 Inter-Domain Routing Supported. Every requirement this repository extracted from RFC 2545, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 4 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 28 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 6 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 6 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 28 | | Tagged units | 28 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2545.md` | | Requirement shard | `rfc/requirements/rfc2545.md` | | RFC text | `rfc/full/rfc2545.txt` | ## Enrolment Enrolled: Use of BGP-4 Multiprotocol Extensions for IPv6 Inter-Domain Routing: four SHALL-level requirements, all in Section 3 and all about the MP_REACH_NLRI Network Address of Next Hop field. Each is proven in both polarities by the exabgp-compat encoding suite (verify tier, ./le functional exabgp-test): conf-llnh-update.ci pins the 32-octet form (global ::1 followed by link-local fe80::1, Length octet 0x20) and conf-llnh-lla-only.ci pins the 16-octet form (one address, Length octet 0x10). The two configs differ in ONE variable, the route's next hop: both declare the same local-link-local fe80::1, both enable the link-local-nexthop capability, and both peer with ::1. So the pair binds inclusion to the shared-subnet condition of Section 3 rather than to the leaf being set: ::1 shares the loopback subnet with the speaker, 2001:db8::ffff shares none. The pair discriminates: an encoder that always appended a second address would fail the second test, and one that never appended would fail the first. Section 2 binds network administrators and restates [ND]/[RIP], Section 4 restates the [BGP-4] BGP Identifier; those sites are classified in rfc/extraction/rfc2545.json. Enrolled 2026-08-07. ## What the public ledger says **Status:** Supported **What the ledger says is covered** - 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 [`rfc/short/rfc2545.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2545.md) and listed in [`rfc/requirements/rfc2545.md`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc2545.md). Send side: ze evaluates the Section 3 condition itself. `linkScope.linkLocalNextHop` ([`internal/component/bgp/reactor/link_scope.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/link_scope.go)) 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` ([`internal/core/network/connected.go`](https://github.com/ze-software/ze/blob/main/internal/core/network/connected.go)), 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` ([`internal/component/bgp/reactor/reactor_iface.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_iface.go)), 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`, [`internal/component/bgp/reactor/config_nexthop_form.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/config_nexthop_form.go)) all call `ValidateGlobalNextHop` ([`internal/core/bgp/attribute/nexthop_form.go`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/nexthop_form.go)), 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` ([`internal/core/bgp/attribute/mpnlri.go`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri.go)) accepts lengths 16 and 32 for an IPv6 next hop and rejects every other length for AFI 2. **What the ledger says remains** No tracked gap in current source anchors. Sections 2 and 4 bind a network administrator, an IPv6 router's neighbor-discovery behavior, and the operator who configures a session; those sites are classified in [`rfc/extraction/rfc2545.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/rfc2545.json) rather than gated here. The BGP Identifier uniqueness that Section 4 restates is gated under RFC 6286. Capability code 77 is draft-based; RFC 2545 covers the next-hop wire behavior. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`RFC2545-3-1`](#rfc2545-3-1), [`RFC2545-3-2`](#rfc2545-3-2), [`RFC2545-3-3`](#rfc2545-3-3), [`RFC2545-3-4`](#rfc2545-3-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2545-3-1` | "A BGP speaker shall advertise to its peer in the Network Address of Next Hop field the global IPv6 address of the next hop, potentially followed by the link-local IPv6 address of the next hop" (§3) | SHALL | 3 - Constructing the Next Hop field | **positive:** `unit/verify` [`TestDefaultOriginateAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L666). **positive:** `unit/verify` [`TestSendAnnounceAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L136). **positive:** `unit/verify` [`TestSessionSendAnnounceAcceptsLinkLocalSecondAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L133). **negative:** `unit/verify` [`TestSendAnnounceRefusesUnusableIPv6NextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L67). **negative:** `unit/verify` [`TestSessionSendAnnounceRefusesNonLinkLocalSecondAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L105). **positive:** `functional/verify` [`adj-rib-in-replay-rfc2545-next-hop.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/adj-rib-in-replay-rfc2545-next-hop.ci#L17). **positive:** `functional/verify` [`conf-llnh-update.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-update.ci#L7). **negative:** `functional/verify` [`new-v6.ci`](https://github.com/ze-software/ze/blob/main/test/encode/new-v6.ci#L1) | | `RFC2545-3-2` | "The value of the Length of Next Hop Network Address field on a MP_REACH_NLRI attribute shall be set to 16, when only a global address is present, or 32 if a link-local address is also included in the Next Hop field" (§3) | SHALL | 3 - Constructing the Next Hop field | **positive:** `unit/verify` [`TestDefaultOriginateAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L670). **positive:** `unit/verify` [`TestSendAnnounceAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L140). **positive:** `unit/verify` [`TestSessionSendAnnounceAcceptsLinkLocalSecondAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L136). **negative:** `unit/verify` [`TestSessionSendAnnounceRefusesNonLinkLocalSecondAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L108). **positive:** `functional/verify` [`adj-rib-in-replay-rfc2545-next-hop.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/adj-rib-in-replay-rfc2545-next-hop.ci#L21). **positive:** `functional/verify` [`conf-llnh-update.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-update.ci#L13). **negative:** `functional/verify` [`new-v6.ci`](https://github.com/ze-software/ze/blob/main/test/encode/new-v6.ci#L8). **positive:** `interop/nightly` [`checkRFC2545NextHops`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L519) | | `RFC2545-3-3` | "The link-local address shall be included in the Next Hop field if and only if the BGP speaker shares a common subnet with the entity identified by the global IPv6 address carried in the Network Address of Next Hop field and the peer the route is being advertised to" (§3) | SHALL | 3 - Constructing the Next Hop field | **positive:** `unit/verify` [`TestDefaultOriginateAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L673). **positive:** `unit/verify` [`TestSendAnnounceAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L143). **negative:** `unit/verify` [`TestDefaultOriginateOmitsLinkLocalWhenPeerOffLink`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L698). **negative:** `unit/verify` [`TestSendAnnounceOmitsLinkLocalWhenPeerOffLink`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L176). **positive:** `functional/verify` [`conf-llnh-update.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-update.ci#L16). **negative:** `functional/verify` [`conf-llnh-lla-only.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-lla-only.ci#L7). **positive:** `interop/nightly` [`checkRFC2545NextHops`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L525). **negative:** `interop/nightly` [`checkRFC2545NextHops`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L531) | | `RFC2545-3-4` | "In all other cases a BGP speaker shall advertise to its peer in the Network Address field only the global IPv6 address of the next hop (the value of the Length of Network Address of Next Hop field shall be set to 16)" (§3) | SHALL | 3 - Constructing the Next Hop field | **positive:** `unit/verify` [`TestDefaultOriginateOmitsLinkLocalWhenPeerOffLink`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L702). **positive:** `unit/verify` [`TestSendAnnounceOmitsLinkLocalWhenPeerOffLink`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L180). **positive:** `functional/verify` [`new-v6.ci`](https://github.com/ze-software/ze/blob/main/test/encode/new-v6.ci#L11). **negative:** `functional/verify` [`conf-llnh-update.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-update.ci#L22) | | `RFC2545-4-1` | The BGP Identifier "should be derived from an IPv4 address regardless of the network protocol(s) a particular BGP-4 instance is configured to convey at a given moment" (§4) | SHOULD | 4 - Transport | **positive:** no positive test. **negative:** no negative test | | `RFC2545-3-5` | "a BGP speaker that advertises a route to an internal peer may modify the Network Address of Next Hop field by removing the link-local IPv6 address of the next hop" (§3) | MAY | 3 - Constructing the Next Hop field | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 2545 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2545-3-1`](#rfc2545-3-1) "A BGP speaker shall advertise to its peer in the Network Address of Next Hop field the global IPv6 address of the next hop, potentially followed by the link-local IPv6 address of the next hop" (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSendAnnounceRefusesUnusableIPv6NextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L67) | unit/verify | unproven | | negative | [`TestSessionSendAnnounceRefusesNonLinkLocalSecondAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L105) | unit/verify | unproven | | negative | [`new-v6.ci`](https://github.com/ze-software/ze/blob/main/test/encode/new-v6.ci#L1) | functional/verify | unproven | | positive | [`TestSessionSendAnnounceAcceptsLinkLocalSecondAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L133) | unit/verify | unproven | | positive | [`TestDefaultOriginateAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L666) | unit/verify | unproven | | positive | [`TestSendAnnounceAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L136) | unit/verify | unproven | | positive | [`conf-llnh-update.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-update.ci#L7) | functional/verify | unproven | | positive | [`adj-rib-in-replay-rfc2545-next-hop.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/adj-rib-in-replay-rfc2545-next-hop.ci#L17) | functional/verify | unproven | ### [`RFC2545-3-2`](#rfc2545-3-2) "The value of the Length of Next Hop Network Address field on a MP_REACH_NLRI attribute shall be set to 16, when only a global address is present, or 32 if a link-local address is also included in the Next Hop field" (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSessionSendAnnounceRefusesNonLinkLocalSecondAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L108) | unit/verify | unproven | | negative | [`new-v6.ci`](https://github.com/ze-software/ze/blob/main/test/encode/new-v6.ci#L8) | functional/verify | unproven | | positive | [`TestSessionSendAnnounceAcceptsLinkLocalSecondAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/announce_nexthop_guard_test.go#L136) | unit/verify | unproven | | positive | [`TestDefaultOriginateAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L670) | unit/verify | unproven | | positive | [`TestSendAnnounceAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L140) | unit/verify | unproven | | positive | [`checkRFC2545NextHops`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L519) | interop/nightly | unproven | | positive | [`conf-llnh-update.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-update.ci#L13) | functional/verify | unproven | | positive | [`adj-rib-in-replay-rfc2545-next-hop.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/adj-rib-in-replay-rfc2545-next-hop.ci#L21) | functional/verify | unproven | ### [`RFC2545-3-3`](#rfc2545-3-3) "The link-local address shall be included in the Next Hop field if and only if the BGP speaker shares a common subnet with the entity identified by the global IPv6 address carried in the Network Address of Next Hop field and the peer the route is being advertised to" (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDefaultOriginateOmitsLinkLocalWhenPeerOffLink`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L698) | unit/verify | unproven | | negative | [`TestSendAnnounceOmitsLinkLocalWhenPeerOffLink`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L176) | unit/verify | unproven | | negative | [`checkRFC2545NextHops`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L531) | interop/nightly | unproven | | negative | [`conf-llnh-lla-only.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-lla-only.ci#L7) | functional/verify | unproven | | positive | [`TestDefaultOriginateAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L673) | unit/verify | unproven | | positive | [`TestSendAnnounceAppendsLinkLocalWhenSection3Holds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L143) | unit/verify | unproven | | positive | [`checkRFC2545NextHops`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L525) | interop/nightly | unproven | | positive | [`conf-llnh-update.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-update.ci#L16) | functional/verify | unproven | ### [`RFC2545-3-4`](#rfc2545-3-4) "In all other cases a BGP speaker shall advertise to its peer in the Network Address field only the global IPv6 address of the next hop (the value of the Length of Network Address of Next Hop field shall be set to 16)" (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`conf-llnh-update.ci`](https://github.com/ze-software/ze/blob/main/test/exabgp-compat/encoding/conf-llnh-update.ci#L22) | functional/verify | unproven | | positive | [`TestDefaultOriginateOmitsLinkLocalWhenPeerOffLink`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L702) | unit/verify | unproven | | positive | [`TestSendAnnounceOmitsLinkLocalWhenPeerOffLink`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_send_test.go#L180) | unit/verify | unproven | | positive | [`new-v6.ci`](https://github.com/ze-software/ze/blob/main/test/encode/new-v6.ci#L11) | functional/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-rfc phase agent, deferral fixit-stored-route-relay-hardening (rfc2545 enrolment) | | Signed off | 2026-08-07 | | Register | prose | | Source | rfc/full/rfc2545.txt | | Source fingerprint | 80e7f63fb7ad995a | | Record | rfc/extraction/rfc2545.json | | Mapped sentences | 5 | | Declined as scope | 5 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, copyright notice and Abstract. The Abstract states what the document defines and binds no speaker. | | `1` | Introduction | 1 | walked | Introduction. States that the BGP-4 procedures apply unchanged unless this document says otherwise, and that the document concerns itself with IPv6 address scope. Its one site describes what the IPv6 addressing architecture defines and is excluded below. | | `2` | IPv6 Address Scopes | 2 | walked | IPv6 Address Scopes. Defines the terms 'global' and 'non-link-local' for this document, and explains why a Next Hop field sometimes carries two addresses. It states no rule for building that field; every such rule is in section 3. Its two sites bind a network administrator and an IPv6 router's neighbour-discovery behaviour, and both are excluded below. | | `3` | Constructing the Next Hop field | 4 | walked | Constructing the Next Hop field. The only section that binds a BGP speaker. All four sites are mapped, one per requirement, to RFC2545-3-1 through RFC2545-3-4. The section's fifth sentence is the MAY captured as RFC2545-3-5; 'may' is not a derived site under either scan, which is why the site count is four rather than five. | | `4` | Transport | 2 | walked | Transport. Explains that BGP-4 runs over IPv4 or IPv6 and reads implicit configuration from the session address, then separates that address from the BGP Identifier. Site 4:1 binds the operator who configures the session. Site 4:2 is the source of RFC2545-4-1 and is mapped to it. | | `5` | Security Considerations | 0 | walked | Security Considerations. One sentence: carrying IPv6 reachability raises no new security issue beyond those of BGP-4 with IPv4. No countermeasure is directed at a speaker. | | `6` | Acknowledgments | 0 | skipped (acknowledgements) | Acknowledgments. Credits the authors of the BGP-4 Multiprotocol Extensions work. | | `7` | not stated | 0 | skipped (references) | References: RFC 2373, RFC 1771, RFC 2283, RFC 2460, RFC 2461, RFC 2080. | | `8` | Author Information | 0 | walked | Author Information. Postal addresses, telephone numbers and e-mail addresses for the two authors. No obligation. | | `9` | Full Copyright Statement | 1 | walked | Full Copyright Statement. The Internet Society boilerplate governing copying and translation of the document. Its one site is a condition on republishing the text and is excluded below. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A description of another document, in the Introduction. The subject of 'must' is IPv6 itself: the sentence reports that the IPv6 addressing architecture [ADDR-ARCH] defines situations calling for a given scope. It directs no BGP speaker and states no rule this document adds. The rules this document does add begin at section 3. | In terms of routing information, the most significant difference between IPv6 and IPv4 (for which BGP was originally designed) is the fact that IPv6 introduces scoped unicast addresses and defines particular situations when a particular address scope must be used. | | `2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the network administrator, named as the subject of the sentence. The obligation is to respect address scope restrictions when planning a deployment, and the awareness clause is about how a person reasons about routing domains against sites. Neither is an act a BGP implementation performs; the sentence exists because section 2 has just declared that the document itself makes no distinction between global and site-local addresses, and it hands the distinction back to the operator. The role is the AS operator, so no producer could act as it. Ze CONSUMES the operator's decision: the capability is negotiated per session in the reactor (`internal/component/bgp/reactor`), which advertises what it is configured to and decides no AS-wide policy. | Network administrators must however respect address scope restrictions and should be aware that the concepts of a BGP-4 routing domain and "site" are orthogonal notions and that they may or may not coincide in a given situation. | | `2:2` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The sentence opens with 'This restrictions does imply', referring to the restrictions of the two documents cited immediately above it: [ND] (RFC 2461), which permits only a link-local address when generating ICMP Redirect messages, and [RIP] (RFC 2080), which permits only a link-local address as a RIPng next hop. The obligation to hold a link-local next hop for a directly connected route is theirs, and it binds the IPv6 neighbour-discovery and interior-routing layers rather than the BGP-4 Next Hop field. RFC 2545 draws the consequence to motivate section 3, and adds no obligation of its own here. | This restrictions does imply that an IPv6 router must have a link- local next hop address for all directly connected routes (routes for which the given router and the next hop router share a common subnet prefix). | | `4:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the operator who configures the session. The sentence draws a consequence of the two before it: BGP-4 reads the peer's network address implicitly from the address that established the session, and the route dissemination procedure uses it, so an IPv4 transport carrying IPv6 reachability leaves that address undetermined and somebody has to supply it. What is required is a configuration act, not a wire behaviour or a decision procedure, and the sentence names no message, field or timer for a speaker to get right. The role is the AS operator, so no producer could act as it. Ze CONSUMES the operator's decision: the capability is negotiated per session in the reactor (`internal/component/bgp/reactor`), which advertises what it is configured to and decides no AS-wide policy. | Thus, when using TCP over IPv4 as a transport for IPv6 reachability information, additional explicit configuration of the peer's network address is required. | | `9:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Internet Society copyright boilerplate that the site scanner did not strip. The 'must' governs the procedures for republishing or translating the document text, and binds a publisher of the document rather than an implementation of the protocol it specifies. | However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English. | ## Superseded No document obsoletes RFC 2545, so its obligations are stated where they were written. --- ### Page: RFC 2661 - Layer Two Tunneling Protocol "L2TP" https://ze-software.net/quality/rfc-compliance/rfc2661/ # RFC 2661 - Layer Two Tunneling Protocol "L2TP" Partial. Every requirement this repository extracted from RFC 2661, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 80.0% | 16 of 20 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 15.0% | 3 of 20 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 20 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 20.4% | 10 of 49 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 20 | of 27 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 20 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 20 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 20 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 20 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 5.0% | 1 of 20 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 20 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 27 | | Gated MUST-level | 20 | | Not applicable, so out of scope | 0 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 49 | | Tagged units | 49 | | Recorded audit verdicts | 0 | | Discrimination records | 10 | | Summary | `rfc/short/rfc2661.md` | | Requirement shard | `rfc/requirements/rfc2661.md` | | RFC text | `rfc/full/rfc2661.txt` | ## Enrolment Enrolled: Layer Two Tunneling Protocol / L2TP (RFC 2661): LAC+LNS control plane. 16 MET (AVP format+reserved bits, control-message framing, ICRQ/ICRP session setup, SCCRQ/SCCRP/SCCCN tunnel handshake, SCCRQ and SCCRP mandatory-AVP refusal, unknown-mandatory-AVP teardown, reliable transport max-attempts/retransmit/duplicate-detect/window/post-teardown-ack retention, StopCCN cascade, CDN drop, SID boundary) + 3 single-polarity positive (retransmit backoff schedule, backoff cap >= 8s, control-header reserved bits zero) + 1 gap (hidden-AVP MD5 codec present but not wired: no Random Vector emitted, no H=1 encoder) ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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). <!-- source: [`internal/component/l2tp/tunnel_initiator.go`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator.go) -- initiate/handleSCCRP - [`internal/component/l2tp/session_initiator.go`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_initiator.go) -- placeOutgoingCall/handleOCRP --> **What the ledger says remains** Feature remains Partial; see L2TP guide for operational limits. One MUST gap gated in [`rfc/short/rfc2661.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2661.md): [`RFC2661-4.3-1`](#rfc2661-4.3-1) -- the hidden-AVP MD5 cipher ([`internal/component/l2tp/hidden.go`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/hidden.go)) is implemented and unit-tested but not wired into any message encoder/decoder, so no Random Vector precedes a hidden AVP on send and precedence is not enforced on receive. Initiator tunnel interop proven vs xl2tpd (test/interop-l2tp/scenarios/03). LAC data-plane bridge (A-4) is QEMU/CAP_NET_ADMIN-gated. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 16 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **20** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (16):** [`RFC2661-4.1-1`](#rfc2661-4.1-1), [`RFC2661-4.1-2`](#rfc2661-4.1-2), [`RFC2661-4.1-3`](#rfc2661-4.1-3), [`RFC2661-4.1-4`](#rfc2661-4.1-4), [`RFC2661-5.8-3`](#rfc2661-5.8-3), [`RFC2661-5.8-4`](#rfc2661-5.8-4), [`RFC2661-5.8-5`](#rfc2661-5.8-5), [`RFC2661-5.8-6`](#rfc2661-5.8-6), [`RFC2661-5.8-7`](#rfc2661-5.8-7), [`RFC2661-6.1-1`](#rfc2661-6.1-1), [`RFC2661-6.2-1`](#rfc2661-6.2-1), [`RFC2661-24.10-1`](#rfc2661-24.10-1), [`RFC2661-24.12-1`](#rfc2661-24.12-1), [`RFC2661-10-1`](#rfc2661-10-1), [`RFC2661-9-1`](#rfc2661-9-1), [`RFC2661-10-2`](#rfc2661-10-2) **Annotated instead of tested (4):** [`RFC2661-x-1`](#rfc2661-x-1), [`RFC2661-5.8-1`](#rfc2661-5.8-1), [`RFC2661-5.8-2`](#rfc2661-5.8-2), [`RFC2661-4.3-1`](#rfc2661-4.3-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2661-x-1` | Reserved bits 8-11 in L2TP header MUST be 0 (Wire Format) | MUST | x | **positive:** `unit/verify` [`TestWriteControlHeader`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/header_test.go#L230). **negative:** no negative test. **positive:** `functional/verify` [`rfc2661-emitted-control-shape.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-emitted-control-shape.ci#L27). **{single-polarity}:** every control header is the fixed constant 0xC802 and every data header is built from verL2TP\|flags, so reserved bits are always emitted zero, and ze never rejects non-zero on receive (internal/component/l2tp/header.go:26, :148-155, :172-207) | | `RFC2661-4.1-1` | AVP reserved bits 2-5 MUST be zero on send; non-zero on receive means treat AVP as unrecognized (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestAVPCatalogRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/avp_test.go#L180). **negative:** `unit/verify` [`TestAVPIteratorReservedBits`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/avp_test.go#L107). **positive:** `functional/verify` [`rfc2661-emitted-control-shape.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-emitted-control-shape.ci#L29) | | `RFC2661-4.1-2` | Message Type AVP (type 0) MUST be the first AVP in every control message. RFC 2661 Section 4.4.1: "The Message Type AVP MUST be the first AVP in a message, immediately following the control message header". The id anchor below is frozen and does NOT name Section 4.1, which is AVP Format (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestWriteICRPBody`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L940). **negative:** `unit/verify` [`TestReactor_MalformedSCCRQCreatesNoTunnel`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_test.go#L256). **positive:** `functional/verify` [`rfc2661-emitted-control-shape.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-emitted-control-shape.ci#L24) | | `RFC2661-4.1-3` | If M=1 and AVP is unrecognized and session-scoped, send CDN and tear down session (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestSession_IncomingLNS_ICRQ`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L121). **negative:** `unit/verify` [`TestSession_UnknownMandatoryAVP`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L554) | | `RFC2661-4.1-4` | If M=1 and AVP is unrecognized and tunnel-scoped, send StopCCN and tear down tunnel (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestTunnelInitiatorHandshake`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_test.go#L132). **negative:** `unit/verify` [`TestTunnelSCCCNUnknownMandatoryAVP_StopCCN`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_test.go#L251) | | `RFC2661-5.8-1` | Retransmission of control messages MUST use exponential backoff (§5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestTickBackoffSchedule`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L374). **negative:** no negative test. **{single-polarity}:** the engine doubles the retransmit timeout on each expiry, and exponential growth is a positive behavior with no meaningful negation on a correct implementation (internal/component/l2tp/reliable.go:591-595) | | `RFC2661-5.8-2` | Backoff cap MUST be at least 8 seconds (§5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestBackoffCapAtLeast8Seconds`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_seq_test.go#L94). **negative:** no negative test. **{single-polarity}:** the default backoff cap is the 16s constant and the reactor always constructs engines with RTimeoutCap unset, so the cap is always at least 8s with no config path or floor guard to test negatively (internal/component/l2tp/reliable_seq.go:19, reliable.go:291-292) | | `RFC2661-5.8-3` | After exhausting retransmissions without response, tunnel and all sessions MUST be cleared (§5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestPeerTeardownWithdrawsSubscriberRoute`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_test.go#L1204). **positive:** `unit/verify` [`TestTickMaxAttempts`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L414). **negative:** `unit/verify` [`TestTickMaxAttempts`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L417) | | `RFC2661-5.8-4` | On each retransmit, Ns stays the same but Nr MUST be updated to current next-expected value (§5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestTickRetransmit`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L339). **negative:** `unit/verify` [`TestTickRetransmit`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L342) | | `RFC2661-5.8-5` | Duplicate control messages MUST be acknowledged (via ZLB or piggyback) even though not processed by upper layer (§5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestOnReceiveDuplicate`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L146). **negative:** `unit/verify` [`TestOnReceiveDuplicate`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L148) | | `RFC2661-5.8-6` | Implementations MUST accept a peer Receive Window Size of up to 4 (§5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestWindowAvailable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_window_test.go#L183). **negative:** `unit/verify` [`TestWindowPeerRWSZero`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_window_test.go#L112) | | `RFC2661-5.8-7` | State and reliable delivery mechanisms MUST be maintained for the full retransmission interval after the final message exchange (§5.8) | MUST | 5.8 | **positive:** `unit/verify` [`TestExpired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L560). **positive:** `unit/verify` [`TestPostTeardownAckRetention`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_integration_test.go#L191). **negative:** `unit/verify` [`TestExpired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L563). **negative:** `unit/verify` [`TestPostTeardownAckRetention`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_integration_test.go#L195) | | `RFC2661-4.3-1` | A Random Vector AVP (type 36) MUST precede any hidden AVP (H=1) in the same message (§4.3) | MUST | 4.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the hidden-AVP MD5 cipher is implemented and unit-tested but is not wired into any control-message path -- no encoder sets H=1 or emits a Random Vector, and decoders skip hidden AVPs without decrypting or checking precedence (internal/component/l2tp/hidden.go:38 has no production caller; avp.go:156-168 skips hidden AVPs; AVPRandomVector avp.go:56 never emitted) | | `RFC2661-6.1-1` | Every AVP RFC 2661 Section 6.1 makes mandatory in an SCCRQ is required, and an SCCRQ missing one is answered with StopCCN rather than dropped in silence. RFC 2661 Section 6.1: "The following AVPs MUST be present in the SCCRQ: Message Type AVP, Protocol Version, Host Name, Framing Capabilities, Assigned Tunnel ID"; RFC 2661 Section 7.1: "Examples of a malformed control message include ... a message that is missing a required AVP", and receipt of one "should be logged appropriately and the control connection cleared to ensure recovery to a known state" (§6.1) | MUST | 6.1 | **positive:** `unit/verify` [`TestSCCRQWithEveryMandatoryAVPEstablishes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_mandatory_avp_test.go#L108). **negative:** `unit/verify` [`TestSCCRQMissingMandatoryAVPIsAnswered`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_mandatory_avp_test.go#L45). **negative:** `unit/verify` [`TestSCCRQWithShortFramingCapabilitiesIsAnswered`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_mandatory_avp_test.go#L149). **positive:** `functional/verify` [`rfc2661-sccrq-mandatory-avp.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-sccrq-mandatory-avp.ci#L28). **negative:** `functional/verify` [`rfc2661-sccrq-mandatory-avp.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-sccrq-mandatory-avp.ci#L24) | | `RFC2661-6.2-1` | Every AVP RFC 2661 Section 6.2 makes mandatory in an SCCRP is required, and an SCCRP missing one tears the dialed tunnel down with StopCCN rather than establishing it. RFC 2661 Section 6.2: "The following AVPs MUST be present in the SCCRP: Message Type, Protocol Version, Framing Capabilities, Host Name, Assigned Tunnel ID"; RFC 2661 Section 7.2.1 gives wait-ctl-reply the row "Receive SCCRP, not acceptable \| Send StopCCN, Clean up \| idle" (§6.2) | MUST | 6.2 | **positive:** `unit/verify` [`TestSCCRPWithEveryMandatoryAVPEstablishes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_sccrp_mandatory_avp_test.go#L107). **negative:** `unit/verify` [`TestSCCRPMissingMandatoryAVPTearsTheTunnelDown`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_sccrp_mandatory_avp_test.go#L58) | | `RFC2661-24.10-1` | Assigned Tunnel ID of 0 in SCCRQ/SCCRP is a protocol error; reject with StopCCN. RFC 2661 Section 4.4.3: "The Assigned Tunnel ID is a 2 octet non-zero unsigned integer"; RFC 2661 Section 5.3: the value 0 "MUST NOT be used as an Assigned Session ID or Assigned Tunnel ID". The id anchor below numbers no section of RFC 2661 and is frozen (§24.10) | MUST | 24.10 | **positive:** `unit/verify` [`TestSCCRQWithNonZeroAssignedTunnelIDEstablishes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_zero_tid_test.go#L117). **positive:** `unit/verify` [`TestTunnelInitiatorHandshake`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_test.go#L135). **negative:** `unit/verify` [`TestParseSCCRP_Rejects`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_test.go#L107). **negative:** `unit/verify` [`TestSCCRQWithZeroAssignedTunnelIDIsAnswered`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_zero_tid_test.go#L75). **positive:** `functional/verify` [`rfc2661-sccrq-tunnel-id-zero.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-sccrq-tunnel-id-zero.ci#L24). **negative:** `functional/verify` [`rfc2661-sccrq-tunnel-id-zero.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-sccrq-tunnel-id-zero.ci#L21) | | `RFC2661-24.12-1` | Unknown M=1 vendor AVP in a session context tears down the session with CDN, not the tunnel with StopCCN. RFC 2661 Section 4.1 states the session/tunnel split and RFC 2661 Section 4.2 states the consequence. The id anchor below numbers no section of RFC 2661 and is frozen (§24.12) | MUST | 24.12 | **positive:** `unit/verify` [`TestSession_UnknownMandatoryAVP`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L556). **negative:** `unit/verify` [`TestSession_UnknownMandatoryAVP`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L558) | | `RFC2661-10-1` | CDN is valid in any non-idle session state; receiving CDN destroys the session. RFC 2661 Section 5.6 states session teardown by CDN and RFC 2661 Section 7.4.2 gives the state table. The id anchor below is frozen and does NOT name Section 10, which is IANA Considerations (§10) | MUST | 10 | **positive:** `unit/verify` [`TestSession_CDN_AnyState`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L337). **positive:** `unit/verify` [`TestSession_CDN_EstablishedSession`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L308). **negative:** `unit/verify` [`TestSession_CDN_UnknownSessionDropped`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L361) | | `RFC2661-9-1` | StopCCN cascades: all sessions in a tunnel are cleared when StopCCN is received. RFC 2661 Section 5.7: an implementation "may shut down an entire tunnel and all sessions on the tunnel by sending the StopCCN". The id anchor below is frozen and does NOT name Section 9, which is Security Considerations (§9) | MUST | 9 | **positive:** `unit/verify` [`TestSession_StopCCN_CascadeSessions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L398). **negative:** `unit/verify` [`TestStopCCNQueuesAllTeardowns`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L809) | | `RFC2661-10-2` | Session ID 0 is reserved and never assigned. RFC 2661 Section 5.3: "The value of 0 for Session ID and Tunnel ID is special and MUST NOT be used as an Assigned Session ID or Assigned Tunnel ID". The id anchor below is frozen and does NOT name Section 10, which is IANA Considerations (§10) | MUST | 10 | **positive:** `unit/verify` [`TestSession_SIDBoundary_MaxUint16`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L1036). **negative:** `unit/verify` [`TestSession_SIDBoundary_Zero`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L1050) | | `RFC2661-5.8-8` | Retransmission count SHOULD be configurable (recommended 5) (§5.8) | SHOULD | 5.8 | **positive:** no positive test. **negative:** no negative test | | `RFC2661-x-2` | Slow start and congestion avoidance SHOULD be implemented (CWND/SSTHRESH per Appendix A) (Appendix A) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC2661-15-1` | HELLO keepalive SHOULD be sent when no control messages received for a configurable period (recommended 60 seconds). RFC 2661 Section 5.5 states the keepalive and RFC 2661 Section 6.5 the message. The id anchor below numbers no section of RFC 2661 and is frozen (§15) | SHOULD | 15 | **positive:** no positive test. **negative:** no negative test | | `RFC2661-4.2-1` | Both peers MAY independently challenge each other during tunnel establishment. RFC 2661 Section 5.1.1 states tunnel authentication and RFC 2661 Section 4.4.3 defines the AVPs. The id anchor below is frozen and does NOT name Section 4.2, which is Mandatory AVPs (§4.2) | MAY | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2661-4.3-2` | Multiple hidden AVPs MAY share a single Random Vector AVP (§4.3) | MAY | 4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2661-5.8-9` | Out-of-order control messages MAY be queued or discarded (§5.8) | MAY | 5.8 | **positive:** no positive test. **negative:** no negative test | | `RFC2661-9.5-1` | Tie Breaker AVP MAY be included in SCCRQ for simultaneous-open resolution. RFC 2661 Section 4.4.3 defines the AVP and its resolution rule; RFC 2661 Section 7.2 names the collision. The id anchor below is frozen and does NOT name Section 9.5, which is Proxy PPP Authentication (§9.5) | MAY | 9.5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2661-4.3-1`](#rfc2661-4.3-1) A Random Vector AVP (type 36) MUST precede any hidden AVP (H=1) in the same message (§4.3) | {gap}, no test | the hidden-AVP MD5 cipher is implemented and unit-tested but is not wired into any control-message path -- no encoder sets H=1 or emits a Random Vector, and decoders skip hidden AVPs without decrypting or checking precedence (internal/component/l2tp/hidden.go:38 has no production caller; avp.go:156-168 skips hidden AVPs; AVPRandomVector avp.go:56 never emitted) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2661-x-1`](#rfc2661-x-1) Reserved bits 8-11 in L2TP header MUST be 0 (Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestWriteControlHeader`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/header_test.go#L230) | unit/verify | unproven | | positive | [`rfc2661-emitted-control-shape.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-emitted-control-shape.ci#L27) | functional/verify | revert, verified | ### [`RFC2661-4.1-1`](#rfc2661-4.1-1) AVP reserved bits 2-5 MUST be zero on send; non-zero on receive means treat AVP as unrecognized (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAVPIteratorReservedBits`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/avp_test.go#L107) | unit/verify | unproven | | positive | [`TestAVPCatalogRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/avp_test.go#L180) | unit/verify | unproven | | positive | [`rfc2661-emitted-control-shape.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-emitted-control-shape.ci#L29) | functional/verify | revert, verified | ### [`RFC2661-4.1-2`](#rfc2661-4.1-2) Message Type AVP (type 0) MUST be the first AVP in every control message. RFC 2661 Section 4.4.1: "The Message Type AVP MUST be the first AVP in a message, immediately following the control message header". The id anchor below is frozen and does NOT name Section 4.1, which is AVP Format (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReactor_MalformedSCCRQCreatesNoTunnel`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_test.go#L256) | unit/verify | unproven | | positive | [`TestWriteICRPBody`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L940) | unit/verify | unproven | | positive | [`rfc2661-emitted-control-shape.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-emitted-control-shape.ci#L24) | functional/verify | revert, verified | ### [`RFC2661-4.1-3`](#rfc2661-4.1-3) If M=1 and AVP is unrecognized and session-scoped, send CDN and tear down session (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSession_UnknownMandatoryAVP`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L554) | unit/verify | unproven | | positive | [`TestSession_IncomingLNS_ICRQ`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L121) | unit/verify | unproven | ### [`RFC2661-4.1-4`](#rfc2661-4.1-4) If M=1 and AVP is unrecognized and tunnel-scoped, send StopCCN and tear down tunnel (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTunnelSCCCNUnknownMandatoryAVP_StopCCN`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_test.go#L251) | unit/verify | unproven | | positive | [`TestTunnelInitiatorHandshake`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_test.go#L132) | unit/verify | unproven | ### [`RFC2661-5.8-1`](#rfc2661-5.8-1) Retransmission of control messages MUST use exponential backoff (§5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestTickBackoffSchedule`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L374) | unit/verify | unproven | ### [`RFC2661-5.8-2`](#rfc2661-5.8-2) Backoff cap MUST be at least 8 seconds (§5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestBackoffCapAtLeast8Seconds`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_seq_test.go#L94) | unit/verify | unproven | ### [`RFC2661-5.8-3`](#rfc2661-5.8-3) After exhausting retransmissions without response, tunnel and all sessions MUST be cleared (§5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTickMaxAttempts`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L417) | unit/verify | unproven | | positive | [`TestPeerTeardownWithdrawsSubscriberRoute`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_test.go#L1204) | unit/verify | unproven | | positive | [`TestTickMaxAttempts`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L414) | unit/verify | unproven | ### [`RFC2661-5.8-4`](#rfc2661-5.8-4) On each retransmit, Ns stays the same but Nr MUST be updated to current next-expected value (§5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTickRetransmit`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L342) | unit/verify | unproven | | positive | [`TestTickRetransmit`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L339) | unit/verify | unproven | ### [`RFC2661-5.8-5`](#rfc2661-5.8-5) Duplicate control messages MUST be acknowledged (via ZLB or piggyback) even though not processed by upper layer (§5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOnReceiveDuplicate`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L148) | unit/verify | unproven | | positive | [`TestOnReceiveDuplicate`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L146) | unit/verify | unproven | ### [`RFC2661-5.8-6`](#rfc2661-5.8-6) Implementations MUST accept a peer Receive Window Size of up to 4 (§5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestWindowPeerRWSZero`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_window_test.go#L112) | unit/verify | unproven | | positive | [`TestWindowAvailable`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_window_test.go#L183) | unit/verify | unproven | ### [`RFC2661-5.8-7`](#rfc2661-5.8-7) State and reliable delivery mechanisms MUST be maintained for the full retransmission interval after the final message exchange (§5.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPostTeardownAckRetention`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_integration_test.go#L195) | unit/verify | unproven | | negative | [`TestExpired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L563) | unit/verify | unproven | | positive | [`TestPostTeardownAckRetention`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_integration_test.go#L191) | unit/verify | unproven | | positive | [`TestExpired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reliable_test.go#L560) | unit/verify | unproven | ### [`RFC2661-4.3-1`](#rfc2661-4.3-1) A Random Vector AVP (type 36) MUST precede any hidden AVP (H=1) in the same message (§4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2661-4.3-1, so no unit is bound to it. ### [`RFC2661-6.1-1`](#rfc2661-6.1-1) Every AVP RFC 2661 Section 6.1 makes mandatory in an SCCRQ is required, and an SCCRQ missing one is answered with StopCCN rather than dropped in silence. RFC 2661 Section 6.1: "The following AVPs MUST be present in the SCCRQ: Message Type AVP, Protocol Version, Host Name, Framing Capabilities, Assigned Tunnel ID"; RFC 2661 Section 7.1: "Examples of a malformed control message include ... a message that is missing a required AVP", and receipt of one "should be logged appropriately and the control connection cleared to ensure recovery to a known state" (§6.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSCCRQMissingMandatoryAVPIsAnswered`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_mandatory_avp_test.go#L45) | unit/verify | revert, verified | | negative | [`TestSCCRQWithShortFramingCapabilitiesIsAnswered`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_mandatory_avp_test.go#L149) | unit/verify | revert, verified | | negative | [`rfc2661-sccrq-mandatory-avp.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-sccrq-mandatory-avp.ci#L24) | functional/verify | revert, verified | | positive | [`TestSCCRQWithEveryMandatoryAVPEstablishes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_mandatory_avp_test.go#L108) | unit/verify | revert, verified | | positive | [`rfc2661-sccrq-mandatory-avp.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-sccrq-mandatory-avp.ci#L28) | functional/verify | revert, verified | ### [`RFC2661-6.2-1`](#rfc2661-6.2-1) Every AVP RFC 2661 Section 6.2 makes mandatory in an SCCRP is required, and an SCCRP missing one tears the dialed tunnel down with StopCCN rather than establishing it. RFC 2661 Section 6.2: "The following AVPs MUST be present in the SCCRP: Message Type, Protocol Version, Framing Capabilities, Host Name, Assigned Tunnel ID"; RFC 2661 Section 7.2.1 gives wait-ctl-reply the row "Receive SCCRP, not acceptable | Send StopCCN, Clean up | idle" (§6.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSCCRPMissingMandatoryAVPTearsTheTunnelDown`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_sccrp_mandatory_avp_test.go#L58) | unit/verify | revert, verified | | positive | [`TestSCCRPWithEveryMandatoryAVPEstablishes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_sccrp_mandatory_avp_test.go#L107) | unit/verify | revert, verified | ### [`RFC2661-24.10-1`](#rfc2661-24.10-1) Assigned Tunnel ID of 0 in SCCRQ/SCCRP is a protocol error; reject with StopCCN. RFC 2661 Section 4.4.3: "The Assigned Tunnel ID is a 2 octet non-zero unsigned integer"; RFC 2661 Section 5.3: the value 0 "MUST NOT be used as an Assigned Session ID or Assigned Tunnel ID". The id anchor below numbers no section of RFC 2661 and is frozen (§24.10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSCCRQWithZeroAssignedTunnelIDIsAnswered`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_zero_tid_test.go#L75) | unit/verify | unproven | | negative | [`TestParseSCCRP_Rejects`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_test.go#L107) | unit/verify | unproven | | negative | [`rfc2661-sccrq-tunnel-id-zero.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-sccrq-tunnel-id-zero.ci#L21) | functional/verify | unproven | | positive | [`TestSCCRQWithNonZeroAssignedTunnelIDEstablishes`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/reactor_sccrq_zero_tid_test.go#L117) | unit/verify | unproven | | positive | [`TestTunnelInitiatorHandshake`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/tunnel_initiator_test.go#L135) | unit/verify | unproven | | positive | [`rfc2661-sccrq-tunnel-id-zero.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/rfc2661-sccrq-tunnel-id-zero.ci#L24) | functional/verify | unproven | ### [`RFC2661-24.12-1`](#rfc2661-24.12-1) Unknown M=1 vendor AVP in a session context tears down the session with CDN, not the tunnel with StopCCN. RFC 2661 Section 4.1 states the session/tunnel split and RFC 2661 Section 4.2 states the consequence. The id anchor below numbers no section of RFC 2661 and is frozen (§24.12) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSession_UnknownMandatoryAVP`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L558) | unit/verify | unproven | | positive | [`TestSession_UnknownMandatoryAVP`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L556) | unit/verify | unproven | ### [`RFC2661-10-1`](#rfc2661-10-1) CDN is valid in any non-idle session state; receiving CDN destroys the session. RFC 2661 Section 5.6 states session teardown by CDN and RFC 2661 Section 7.4.2 gives the state table. The id anchor below is frozen and does NOT name Section 10, which is IANA Considerations (§10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSession_CDN_UnknownSessionDropped`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L361) | unit/verify | unproven | | positive | [`TestSession_CDN_AnyState`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L337) | unit/verify | unproven | | positive | [`TestSession_CDN_EstablishedSession`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L308) | unit/verify | unproven | ### [`RFC2661-9-1`](#rfc2661-9-1) StopCCN cascades: all sessions in a tunnel are cleared when StopCCN is received. RFC 2661 Section 5.7: an implementation "may shut down an entire tunnel and all sessions on the tunnel by sending the StopCCN". The id anchor below is frozen and does NOT name Section 9, which is Security Considerations (§9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestStopCCNQueuesAllTeardowns`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L809) | unit/verify | unproven | | positive | [`TestSession_StopCCN_CascadeSessions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L398) | unit/verify | unproven | ### [`RFC2661-10-2`](#rfc2661-10-2) Session ID 0 is reserved and never assigned. RFC 2661 Section 5.3: "The value of 0 for Session ID and Tunnel ID is special and MUST NOT be used as an Assigned Session ID or Assigned Tunnel ID". The id anchor below is frozen and does NOT name Section 10, which is IANA Considerations (§10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSession_SIDBoundary_Zero`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L1050) | unit/verify | unproven | | positive | [`TestSession_SIDBoundary_MaxUint16`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/session_fsm_test.go#L1036) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 2661, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2661, so its obligations are stated where they were written. --- ### Page: RFC 2759 - Microsoft PPP CHAP Extensions, Version 2 https://ze-software.net/quality/rfc-compliance/rfc2759/ # RFC 2759 - Microsoft PPP CHAP Extensions, Version 2 Supported. Every requirement this repository extracted from RFC 2759, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 75.0% | 9 of 12 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 25.0% | 3 of 12 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 12 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 12 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 22 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 12 | of 14 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 12 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 12 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 12 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 12 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 12 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 14 | | Gated MUST-level | 12 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 23 | | Tagged units | 22 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2759.md` | | Requirement shard | `rfc/requirements/rfc2759.md` | | RFC text | `rfc/full/rfc2759.txt` | ## Enrolment Enrolled: MS-CHAPv2 (EAP inside IKEv2): 6 MET (Response field validation, DOMAIN-strip, UTF-16LE hash) + 3 single-polarity positive (uppercase S=, 16-octet random challenges) + 3 gap (no MS-CHAPv2 Failure/C= packet, peer skips authenticator-response check, no E=691) ## What the public ledger says **Status:** Supported **What the ledger says is covered** 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). **What the ledger says remains:** No MUST gap remains gated in [`rfc/short/rfc2759.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc2759.md). The three that stood on 2026-08-30 are closed: x-6 the Failure packet and its C= field, x-7 the peer-side Authenticator Response check, and x-12 the E=691 error code. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 9 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **12** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (9):** [`RFC2759-x-1`](#rfc2759-x-1), [`RFC2759-x-2`](#rfc2759-x-2), [`RFC2759-x-3`](#rfc2759-x-3), [`RFC2759-x-4`](#rfc2759-x-4), [`RFC2759-x-6`](#rfc2759-x-6), [`RFC2759-x-7`](#rfc2759-x-7), [`RFC2759-x-10`](#rfc2759-x-10), [`RFC2759-x-11`](#rfc2759-x-11), [`RFC2759-x-12`](#rfc2759-x-12) **Annotated instead of tested (3):** [`RFC2759-x-5`](#rfc2759-x-5), [`RFC2759-x-8`](#rfc2759-x-8), [`RFC2759-x-9`](#rfc2759-x-9) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2759-x-1` | Response Reserved octets (8 octets) MUST be zero (Wire Format, Validation) | MUST | x | **positive:** `unit/verify` [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L43). **negative:** `unit/verify` [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L65) | | `RFC2759-x-2` | Response Flags octet MUST be zero (Wire Format, Validation) | MUST | x | **positive:** `unit/verify` [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L45). **negative:** `unit/verify` [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L80) | | `RFC2759-x-3` | Response Value-Size MUST be 49; any other value MUST be rejected as malformed (Wire Format, Validation) | MUST | x | **positive:** `unit/verify` [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L47). **negative:** `unit/verify` [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L95) | | `RFC2759-x-4` | ChallengeHash UserName input MUST exclude any `DOMAIN\\` prefix (Crypto Operations) | MUST | x | **positive:** `unit/verify` [`TestChallengeHashExcludesDomainPrefix`](https://github.com/ze-software/ze/blob/main/internal/core/eap/mschapv2_test.go#L164). **negative:** `unit/verify` [`TestChallengeHashExcludesDomainPrefix`](https://github.com/ze-software/ze/blob/main/internal/core/eap/mschapv2_test.go#L175) | | `RFC2759-x-5` | `S=` hex digits MUST be uppercase A-F (Wire Format, Pitfalls) | MUST | x | **positive:** `unit/verify` [`TestMSCHAPv2SuccessUppercaseHex`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L114). **negative:** no negative test. **{single-polarity}:** the authenticator only emits S= and forces uppercase via strings.ToUpper, and no code path can emit lowercase, so only the positive assertion is reachable (internal/core/eap/eap_mschapv2.go:148) | | `RFC2759-x-6` | Failure packet MUST contain `C=` field with fresh 16-octet challenge as 32 uppercase hex digits (Wire Format) | MUST | x | **positive:** `unit/verify` [`TestRFC2759FailureCarriesFreshChallenge`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_failure_packet_test.go#L197). **negative:** `unit/verify` [`TestRFC2759PeerRefusesFailureWithoutConformantChallenge`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_failure_packet_test.go#L277) | | `RFC2759-x-7` | Peer MUST disconnect if Authenticator Response (`S=` value) does not match expected value (Validation, Mutual Authentication) | MUST | x | **positive:** `unit/verify` [`TestRFC2759PeerAcceptsCorrectAuthenticatorResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_authenticator_response_test.go#L93). **negative:** `unit/verify` [`TestRFC2759PeerEndsSessionOnBadAuthenticatorResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_authenticator_response_test.go#L122) | | `RFC2759-x-8` | Authenticator Challenge MUST be 16 octets of cryptographic random (Wire Format, Validation) | MUST | x | **positive:** `unit/verify` [`TestMSCHAPv2AuthChallengeRandom16`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L146). **negative:** no negative test. **{single-polarity}:** the authenticator fills a [16]byte from crypto/rand, so length and source are assertable but randomness quality has no falsifying negative test (internal/core/eap/eap_mschapv2.go:49) | | `RFC2759-x-9` | Peer-Challenge MUST be 16 octets of cryptographic random (Wire Format) | MUST | x | **positive:** `unit/verify` [`TestMSCHAPv2PeerChallengeRandom16`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L330). **positive:** `unit/verify` [`TestRFC2759PeerChallengeComesFromCryptoRand`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_peer_challenge_test.go#L37). **negative:** no negative test. **{single-polarity}:** the peer fills a [16]byte from crypto/rand, assertable for length and source but not falsifiable for randomness quality (internal/core/eap/peer.go:195) | | `RFC2759-x-10` | NT password hash MUST use UTF-16LE encoding of the password, not UTF-8 (Crypto Operations, Pitfalls) | MUST | x | **positive:** `unit/verify` [`TestNtPasswordHash`](https://github.com/ze-software/ze/blob/main/internal/core/eap/mschapv2_test.go#L29). **negative:** `unit/verify` [`TestNtPasswordHash`](https://github.com/ze-software/ze/blob/main/internal/core/eap/mschapv2_test.go#L37) | | `RFC2759-x-11` | Non-zero Reserved or Flags octets in Response MUST be rejected (Validation) | MUST | x | **positive:** `unit/verify` [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L49). **negative:** `unit/verify` [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L67) | | `RFC2759-x-12` | NT-Response mismatch MUST result in Failure with E=691 and session termination (Validation) | MUST | x | **positive:** `unit/verify` [`TestRFC2759AuthenticatorRefusesWithErrorCode691`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_failure_packet_test.go#L104). **negative:** `unit/verify` [`TestRFC2759AuthenticatorAcceptsMatchingNTResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_failure_packet_test.go#L171) | | `RFC2759-x-13` | Failure packet version field (`V=`) SHOULD be 3 for MS-CHAPv2 (Wire Format) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC2759-x-14` | Authenticator SHOULD limit retry count to mitigate brute-force attacks (Security Considerations) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 2759 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2759-x-1`](#rfc2759-x-1) Response Reserved octets (8 octets) MUST be zero (Wire Format, Validation) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L65) | unit/verify | unproven | | positive | [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L43) | unit/verify | unproven | ### [`RFC2759-x-2`](#rfc2759-x-2) Response Flags octet MUST be zero (Wire Format, Validation) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L80) | unit/verify | unproven | | positive | [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L45) | unit/verify | unproven | ### [`RFC2759-x-3`](#rfc2759-x-3) Response Value-Size MUST be 49; any other value MUST be rejected as malformed (Wire Format, Validation) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L95) | unit/verify | unproven | | positive | [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L47) | unit/verify | unproven | ### [`RFC2759-x-4`](#rfc2759-x-4) ChallengeHash UserName input MUST exclude any `DOMAIN\` prefix (Crypto Operations) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestChallengeHashExcludesDomainPrefix`](https://github.com/ze-software/ze/blob/main/internal/core/eap/mschapv2_test.go#L175) | unit/verify | unproven | | positive | [`TestChallengeHashExcludesDomainPrefix`](https://github.com/ze-software/ze/blob/main/internal/core/eap/mschapv2_test.go#L164) | unit/verify | unproven | ### [`RFC2759-x-5`](#rfc2759-x-5) `S=` hex digits MUST be uppercase A-F (Wire Format, Pitfalls) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestMSCHAPv2SuccessUppercaseHex`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L114) | unit/verify | unproven | ### [`RFC2759-x-6`](#rfc2759-x-6) Failure packet MUST contain `C=` field with fresh 16-octet challenge as 32 uppercase hex digits (Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2759PeerRefusesFailureWithoutConformantChallenge`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_failure_packet_test.go#L277) | unit/verify | unproven | | positive | [`TestRFC2759FailureCarriesFreshChallenge`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_failure_packet_test.go#L197) | unit/verify | unproven | ### [`RFC2759-x-7`](#rfc2759-x-7) Peer MUST disconnect if Authenticator Response (`S=` value) does not match expected value (Validation, Mutual Authentication) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2759PeerEndsSessionOnBadAuthenticatorResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_authenticator_response_test.go#L122) | unit/verify | unproven | | positive | [`TestRFC2759PeerAcceptsCorrectAuthenticatorResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_authenticator_response_test.go#L93) | unit/verify | unproven | ### [`RFC2759-x-8`](#rfc2759-x-8) Authenticator Challenge MUST be 16 octets of cryptographic random (Wire Format, Validation) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestMSCHAPv2AuthChallengeRandom16`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L146) | unit/verify | unproven | ### [`RFC2759-x-9`](#rfc2759-x-9) Peer-Challenge MUST be 16 octets of cryptographic random (Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestMSCHAPv2PeerChallengeRandom16`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L330) | unit/verify | unproven | | positive | [`TestRFC2759PeerChallengeComesFromCryptoRand`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_peer_challenge_test.go#L37) | unit/verify | unproven | ### [`RFC2759-x-10`](#rfc2759-x-10) NT password hash MUST use UTF-16LE encoding of the password, not UTF-8 (Crypto Operations, Pitfalls) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNtPasswordHash`](https://github.com/ze-software/ze/blob/main/internal/core/eap/mschapv2_test.go#L37) | unit/verify | unproven | | positive | [`TestNtPasswordHash`](https://github.com/ze-software/ze/blob/main/internal/core/eap/mschapv2_test.go#L29) | unit/verify | unproven | ### [`RFC2759-x-11`](#rfc2759-x-11) Non-zero Reserved or Flags octets in Response MUST be rejected (Validation) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L67) | unit/verify | unproven | | positive | [`TestMSCHAPv2ResponseFieldValidation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_mschapv2_test.go#L49) | unit/verify | unproven | ### [`RFC2759-x-12`](#rfc2759-x-12) NT-Response mismatch MUST result in Failure with E=691 and session termination (Validation) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2759AuthenticatorAcceptsMatchingNTResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_failure_packet_test.go#L171) | unit/verify | unproven | | positive | [`TestRFC2759AuthenticatorRefusesWithErrorCode691`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc2759_failure_packet_test.go#L104) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement walk agent, spec-rfcgate-6-supported-extraction-signoff phase 2 (Tier 1) | | Signed off | 2026-08-30 | | Register | prose | | Source | rfc/full/rfc2759.txt | | Source fingerprint | 0b123d1db323bc28 | | Record | rfc/extraction/rfc2759.json | | Mapped sentences | 5 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, Copyright Notice, Abstract and Table of Contents. The Abstract names the document as a description of MS-CHAP-V2 and states no obligation. | | `1` | Introduction | 0 | walked | Introduction. A bulleted list of the six differences from MS-CHAP-V1: CHAP Algorithm 0x81, mutual authentication by piggybacking a peer challenge and an authenticator response, the changed NT-Response calculation, the Peer-Challenge replacing the LAN Manager response, the changed Failure Message format, and the single Change-Password packet. Every sentence is indicative, and each obligation it previews is stated normatively in sections 3 to 7. | | `2` | LCP Configuration | 0 | walked | LCP Configuration. Assigns the CHAP Algorithm field the value 0x81 and observes that a PPP implementation which answers with LCP Config-Rej has no problem. A value assignment is not a directive, which is the reading the RFC 4486 sign-off took of its own subcode table. The obligation to USE the value would belong to a PPP speaker, and the ledger scopes this summary to MS-CHAPv2 carried in EAP inside IKEv2, where no LCP option is negotiated at all. | | `3` | Challenge Packet | 0 | walked | Challenge Packet. Two sentences carry content: 'MS-CHAP-V2 authenticators send an 16-octet challenge Value field', which is indicative, and 'the standard guidelines on randomness [1,2,7] SHOULD be observed', which is advisory. The site scan reads no site from either, and RFC2759-x-8 is read from the pair. | | `4` | Response Packet | 2 | walked | Response Packet. The Value sub-format list is site 4:1 and the Flag rule is site 4:2. Three further ids are read from prose here. RFC2759-x-3 states the Value-Size as 49, which the RFC never writes as a number: it is the sum of the sub-format list, 16 + 8 + 24 + 1. RFC2759-x-9 is read from 'The Peer-Challenge field is a 16-octet random number' together with the same randomness advisory as section 3. RFC2759-x-11 is the receiver-side counterpart of the two sender rules the sites carry: the RFC states that Reserved and Flags must be zero and never states the duty to reject a packet in which they are not. The domain-stripping rule is stated here too, in 'only the user name is used, without any associated Windows NT domain name'; it is attributed to section 8.2, where the pseudocode states it as a constraint on the hash input. | | `5` | Success Packet | 3 | walked | Success Packet. Three sites: the uppercase-hex rule (5:1), the peer's duty to verify the authenticator response (5:2), and the peer's duty to end the session when that response is missing or incorrect (5:3). The summary carries one row for the verify-and-disconnect duty, RFC2759-x-7, so site 5:3 maps it and site 5:2 is excluded as its duplicate. The method the section points at is section 8.8. | | `6` | Failure Packet | 1 | walked | Failure Packet. The C= field rule is site 6:1. The error-code table, the retry flag and the <msg> text are indicative. One lowercase advisory sits here that the summary does not declare, 'implementations should deal with codes not on this list gracefully', and it binds a reader of a Failure packet; ze's IKEv2 EAP path never parses one. RFC2759-x-12 is read from the 691 ERROR_AUTHENTICATION_FAILURE entry together with the flow in section 9.1.3, and RFC2759-x-13 from 'For MS-CHAP-V2, this value SHOULD always be 3'. | | `7` | Change-Password Packet | 1 | walked | Change-Password Packet. The packet is 586 octets and is sent by a peer whose password the authenticator reported expired. Its Reserved field rule is site 7:1, excluded below: ze implements the password-change exchange in neither direction. The Peer-Challenge and NT-Response fields are defined by reference to the Response packet and add no obligation of their own, and the Flags field is 'Reserved, always clear (0)', an indicative bit-field description. | | `8` | Pseudocode | 0 | walked | Pseudocode. One sentence naming what the subsections describe. No obligation. | | `8.1` | GenerateNTResponse() | 0 | walked | GenerateNTResponse(). Pseudocode composing ChallengeHash, NtPasswordHash and ChallengeResponse into the 24-octet NT-Response. Indicative throughout. GenerateNTResponse (internal/core/eap/mschapv2.go) is the same composition in the same order. | | `8.2` | ChallengeHash() | 0 | walked | ChallengeHash(). SHA-1 over PeerChallenge, AuthenticatorChallenge and UserName, truncated to 8 octets. Its comment states the constraint RFC2759-x-4 renders: 'Only the user name (as presented by the peer and excluding any prepended domain name) is used as input to SHAUpdate()'. A pseudocode comment carries no capitalised keyword, so the scan reads no site from it. | | `8.3` | NtPasswordHash() | 0 | walked | NtPasswordHash(). MD4 over the password, with the comment 'Only the password is hashed without including any terminating 0'. RFC2759-x-4's sibling RFC2759-x-10 is read here: the UTF-16LE encoding is stated by the declared input type '0-to-256-unicode-char Password' and pinned by the worked vector in section 9.2, where the password 'clientPass' appears as 63 00 6C 00 69 00 ... with no terminator. Neither statement is a keyword site. | | `8.4` | HashNtPasswordHash() | 0 | walked | HashNtPasswordHash(). MD4 over the 16-octet PasswordHash. Indicative. | | `8.5` | ChallengeResponse() | 0 | walked | ChallengeResponse(). Zero-pads the PasswordHash to 21 octets, splits it into three 7-octet DES keys and encrypts the 8-octet Challenge under each. Indicative; the summary renders it in its Crypto Operations table. | | `8.6` | DesEncrypt() | 0 | walked | DesEncrypt(). DES in ECB mode, with the note that the caller inserts the parity bits itself because the algorithm ignores them. Indicative. | | `8.7` | GenerateAuthenticatorResponse() | 0 | walked | GenerateAuthenticatorResponse(). The Magic1 and Magic2 constants and the two SHA-1 passes that produce the 20-octet value the S= field carries. Indicative. GenerateAuthenticatorResponse (internal/core/eap/mschapv2.go) implements it and the authenticator calls it in handleResponse. | | `8.8` | CheckAuthenticatorResponse() | 0 | walked | CheckAuthenticatorResponse(). The procedure section 5 points at: recompute the authenticator response from the peer's own inputs and compare. Pseudocode, so no site; the obligation to RUN it is stated in section 5 and mapped there as RFC2759-x-7. Ze has no implementation of this routine: the peer's handleMSCHAPv2Success (internal/core/eap/peer.go) hex-decodes the S= field and never calls GenerateAuthenticatorResponse. | | `8.9` | NewPasswordEncryptedWithOldNtPasswordHash() | 0 | walked | NewPasswordEncryptedWithOldNtPasswordHash(). Change-Password crypto, building the PWBLOCK. Indicative pseudocode for the exchange section 7 defines and ze does not implement. | | `8.10` | EncryptPwBlockWithPasswordHash() | 0 | walked | EncryptPwBlockWithPasswordHash(). Change-Password crypto. Indicative pseudocode for the exchange ze does not implement. | | `8.11` | Rc4Encrypt() | 0 | walked | Rc4Encrypt(). Change-Password crypto, naming RC4 as a licensed proprietary algorithm. Indicative pseudocode for the exchange ze does not implement. | | `8.12` | OldNtPasswordHashEncryptedWithNewNtPasswordHash() | 0 | walked | OldNtPasswordHashEncryptedWithNewNtPasswordHash(). Change-Password crypto. Indicative pseudocode for the exchange ze does not implement. | | `8.13` | NtPasswordHashEncryptedWithBlock() | 0 | walked | NtPasswordHashEncryptedWithBlock(). Change-Password crypto, two DES blocks over the password hash. Indicative pseudocode for the exchange ze does not implement. | | `9` | Examples | 0 | walked | Examples. One sentence naming what the subsections show. No obligation. | | `9.1` | Negotiation Examples | 0 | walked | Negotiation Examples. States indicatively that the packet sequence ID increments on each retry response and on the change-password response, that retry is never allowed after a password change, and that a password change may follow a retry. Descriptions of the flows below, not directives. | | `9.1.1` | Successful authentication | 0 | walked | Successful authentication. A three-message flow diagram. Non-normative. | | `9.1.2` | Authenticator authentication failure | 0 | walked | Authenticator authentication failure. The flow diagram for the case RFC2759-x-7 governs: 'Authenticator Response verification fails, peer disconnects'. A worked example of the section 5 obligation, not a second statement of it. | | `9.1.3` | Failed authentication with no retry allowed | 0 | walked | Failed authentication with no retry allowed. The flow diagram showing Failure (E=691 R=0) followed by an authenticator disconnect. RFC2759-x-12 is read from it together with the section 6 error table. | | `9.1.4` | Successful authentication after retry | 0 | walked | Successful authentication after retry. A flow diagram. Non-normative. | | `9.1.5` | Failed hack attack with 3 attempts allowed | 0 | walked | Failed hack attack with 3 attempts allowed. A flow diagram illustrating the retry limit that section 10 states as an advisory. Non-normative. | | `9.1.6` | Successful authentication with password change | 0 | walked | Successful authentication with password change. A flow diagram for the exchange section 7 defines. Non-normative. | | `9.1.7` | Successful authentication with retry and password change | 0 | walked | Successful authentication with retry and password change. A flow diagram. Non-normative. | | `9.2` | Hash Example | 0 | walked | Hash Example. The known-answer vectors for user name 'User' and password 'clientPass', from the UTF-16LE password bytes through to 'S=407A5589115FD0D6209F510FE9C04566932CDA56'. No obligation; these are the vectors the RFC2759-x-10 and RFC2759-x-4 tests drive (internal/core/eap/mschapv2_test.go). Its column-0 lines are what the section splitter reads as the seven numeric pseudo-sections below. | | `55` | not stated | 0 | walked | Not a section of RFC 2759. The section splitter reads a column-0 line beginning with digits as a heading, and section 9.2 prints its vectors at column 0. This id comes from '55 73 65 72', the UserName vector. No text and no obligation. | | `63` | not stated | 0 | walked | Not a section of RFC 2759: the splitter artifact of '63 00 6C 00 69 00 65 00 6E 00 74 00 50 00 61 00 73 00 73 00', the UTF-16LE Password vector in section 9.2. No text and no obligation. | | `21` | not stated | 0 | walked | Not a section of RFC 2759: the splitter artifact of '21 40 23 24 25 5E 26 2A 28 29 5F 2B 3A 33 7C 7E', the PeerChallenge vector in section 9.2. No text and no obligation. | | `44` | not stated | 0 | walked | Not a section of RFC 2759: the splitter artifact of '44 EB BA 8D 53 12 B8 D6 11 47 44 11 F5 69 89 AE', the PasswordHash vector in section 9.2. No text and no obligation. | | `24` | not stated | 0 | walked | Not a section of RFC 2759: the splitter artifact of the label line '24 octet NT-Response:' in section 9.2. No text and no obligation. | | `82` | not stated | 0 | walked | Not a section of RFC 2759: the splitter artifact of '82 30 9E CD 8D 70 8B 5E A0 8F AA 39 81 CD 83 54 42 33 11 4A 3D 85 D6 DF', the NT-Response vector in section 9.2. No text and no obligation. | | `41` | not stated | 0 | walked | Not a section of RFC 2759: the splitter artifact of '41 C0 0C 58 4B D2 D9 1C 40 17 A2 A1 2F A5 9F 3F', the PasswordHashHash vector in section 9.2. No text and no obligation. | | `9.3` | Example of DES Key Generation | 0 | walked | Example of DES Key Generation. Shows the two parity-corrected DES keys derived from the password 'MyPw', and notes that many DES engines strip the parity bits rather than check them. A worked example of section 8.6, with no obligation of its own. | | `10` | Security Considerations | 0 | walked | Security Considerations. One sentence, an advisory: 'As an implementation detail, the authenticator SHOULD limit the number of password retries allowed to make brute-force password guessing attacks more difficult.' RFC2759-x-14 renders it. The scan reads no site because the sentence is SHOULD-level. | | `11` | References | 0 | skipped (references) | References. Twelve citations, including RFC 2119 at [3]. No obligation of this document. | | `12` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. Credits the reviewers of the document. | | `13` | Author's Address | 0 | skipped (front-matter) | Author's Address. Document furniture: postal address, telephone number and e-mail address for the author. | | `14` | Full Copyright Statement | 1 | walked | Full Copyright Statement. Walked rather than skipped because the prose scan attributes its one site here; that site is the Internet Society boilerplate and is excluded below. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `5:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The Success-packet verification duty is stated in two sentences: this one names the act and site 5:3 names the consequence. rfc/short/rfc2759.md carries them as one row, RFC2759-x-7, whose declared text is the consequence sentence and whose annotation covers both halves ('never computes the expected Authenticator Response to compare or disconnect on mismatch'). Site 5:3 maps that row. Ze meets neither half: handleMSCHAPv2Success (internal/core/eap/peer.go) hex-decodes the S= field and never calls GenerateAuthenticatorResponse. Raised as an ask under AC-8 of plan/pre-release/spec-rfcgate-6-supported-extraction-signoff.md, not annotated here. | The authenticating peer MUST verify the authenticator response when a Success packet is received. | | `7:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the peer that performs the MS-CHAPv2 password change, a role ze plays in neither direction. Section 7 makes the role optional in its own text: 'This packet type is supported by recent versions of Windows NT 4.0, Windows 95 and Windows 98. It is not supported by Windows NT 3.5, Windows NT 3.51, or early versions', and the packet 'should be sent only if the authenticator reports ERROR_PASSWD_EXPIRED (E=648)'. Ze never reports E=648, because sendFailure (internal/core/eap/eap_mschapv2.go) ends the method with ErrMethodFailed and builds no MS-CHAPv2 Failure packet. Ze never sends or accepts Code 7 either: the authenticator's Process and the peer's handleMSCHAPv2Request (internal/core/eap/peer.go) each switch on Challenge, Response and Success alone and refuse every other opcode, and the PPP path states the same scope at internal/component/l2tp/ppp/mschapv2.go:25. Nothing in ze builds or parses the packet whose Reserved field this sentence constrains. | Reserved 8 octets, must be zero. | | `14:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Boilerplate the site scan does not strip: the Internet Society Full Copyright Statement. Its 'must be followed' governs whoever republishes or translates the document under the Internet Standards process, not a speaker of MS-CHAPv2. | However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English. | ## Superseded No document obsoletes RFC 2759, so its obligations are stated where they were written. --- ### Page: RFC 2782 - A DNS RR for specifying the location of services (DNS SRV) https://ze-software.net/quality/rfc-compliance/rfc2782/ # RFC 2782 - A DNS RR for specifying the location of services (DNS SRV) No row in the public ledger. Every requirement this repository extracted from RFC 2782, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 7 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 7 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 7 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 7 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 7 | of 14 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 7 | of 7 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 7 of 7 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 7 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 7 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 7 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 14 | | Gated MUST-level | 7 | | Not applicable, so out of scope | 7 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2782.md` | | Requirement shard | `rfc/requirements/rfc2782.md` | | RFC text | `rfc/full/rfc2782.txt` | ## Enrolment Enrolled: DNS SRV resource records: seven MUST-level requirements, all {not-applicable}. ze serves SRV records verbatim as an authoritative server (internal/plugins/geodns: config.go parseSRV, server.go recordRR emitting dns.SRV) and implements no SRV-cognizant connecting client that performs priority/weight selection or Additional-section address resolution (internal/component/resolve/dns surfaces target:port strings without contacting or ordering; no production code resolves TypeSRV). The client-selection MUSTs (Priority-1, Notes-2 parse-all-RRs, Notes-3 Additional-section lookup), the protocol-specification-author MUSTs (Applicability-1 Service token, Applicability-2 security considerations), and the Target MUSTs (Target-1 address records present, Target-2 not a CNAME) all govern roles or data ze does not own. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 2782. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 7 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **7** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (7):** [`RFC2782-Applicability-1`](#rfc2782-applicability-1), [`RFC2782-Applicability-2`](#rfc2782-applicability-2), [`RFC2782-Priority-1`](#rfc2782-priority-1), [`RFC2782-Target-1`](#rfc2782-target-1), [`RFC2782-Target-2`](#rfc2782-target-2), [`RFC2782-Notes-2`](#rfc2782-notes-2), [`RFC2782-Notes-3`](#rfc2782-notes-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2782-Applicability-1` | A protocol specification indicating SRV use MUST define the symbolic name to be used in the Service field of the SRV record (§Applicability) | MUST | Applicability | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this MUST binds the author of a protocol specification that adopts SRV to define the Service token; ze authors no such specification -- the _Service._Proto owner name is operator config data served verbatim (internal/plugins/geodns/config.go:319, server.go:111), not a name ze defines | | `RFC2782-Applicability-2` | Such a specification MUST also include security considerations (§Applicability) | MUST | Applicability | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this is a security-considerations obligation on the author of an SRV-adopting protocol specification; ze publishes no such specification | | `RFC2782-Priority-1` | A client MUST attempt to contact the target host with the lowest-numbered priority it can reach (§Priority) | MUST | Priority | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze implements no SRV-cognizant connecting client; geodns is authoritative and serves SRV verbatim without selection (internal/plugins/geodns/server.go:111), and internal/component/resolve/dns returns target:port strings without contacting or ordering by priority (resolver.go:321) -- no production code resolves TypeSRV | | `RFC2782-Target-1` | There MUST be one or more address records for the Target name (§Target) | MUST | Target | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** an SRV Target is an arbitrary FQDN that commonly lives outside any zone geodns is authoritative for, so geodns cannot require in-zone address records for it (internal/plugins/geodns/config.go:319); the address-record obligation belongs to the target's own authoritative zone | | `RFC2782-Target-2` | The Target name MUST NOT be an alias, in the sense of RFC 1034 or RFC 2181 (§Target) | MUST NOT | Target | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** geodns serves no CNAME record type (internal/plugins/geodns/record.go:10-14), so a geodns SRV Target can never resolve to a geodns-served alias; whether an external target name is a CNAME is outside geodns's authority | | `RFC2782-Notes-2` | A client MUST parse all of the RRs in the reply (§Notes) | MUST | Notes | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this parse-all-RRs MUST governs the SRV-cognizant client that locates and connects to servers; ze has no such client. internal/component/resolve/dns iterates every answer RR (resolver.go:299) but is a generic stub resolver surfacing data, not a connecting SRV client, and no production code drives it with TypeSRV | | `RFC2782-Notes-3` | If the Additional Data section lacks address records for all the SRV RRs, the client MUST look up the missing address records before connecting (§Notes) | MUST | Notes | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Additional-section address follow-up before connecting is a connecting-SRV-client obligation ze does not implement; internal/component/resolve/dns never reads the Additional section and never connects (resolver.go:299), and geodns as a server emits no target address glue for SRV answers (server.go:182-188) | | `RFC2782-Applicability-3` | Service SRV records SHOULD NOT be used in the absence of such a protocol specification (§Applicability) | SHOULD NOT | Applicability | **positive:** no positive test. **negative:** no negative test | | `RFC2782-Priority-2` | Target hosts with the same priority SHOULD be tried in an order defined by the weight field (§Priority) | SHOULD | Priority | **positive:** no positive test. **negative:** no negative test | | `RFC2782-Weight-1` | Larger weights SHOULD be given a proportionately higher probability of being selected (§Weight) | SHOULD | Weight | **positive:** no positive test. **negative:** no negative test | | `RFC2782-Weight-2` | Domain administrators SHOULD use Weight 0 when there is no server selection to do (§Weight) | SHOULD | Weight | **positive:** no positive test. **negative:** no negative test | | `RFC2782-Weight-3` | The specified ordering algorithm SHOULD be used to order the SRV RRs of the same priority (§Weight) | SHOULD | Weight | **positive:** no positive test. **negative:** no negative test | | `RFC2782-Usage-1` | A SRV-cognizant client SHOULD use the specified procedure to locate servers and connect to the preferred one (§Usage) | SHOULD | Usage | **positive:** no positive test. **negative:** no negative test | | `RFC2782-Notes-1` | Port numbers SHOULD NOT be used in place of the symbolic service or protocol names (§Notes) | SHOULD NOT | Notes | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2782-Applicability-1`](#rfc2782-applicability-1) A protocol specification indicating SRV use MUST define the symbolic name to be used in the Service field of the SRV record (§Applicability) | no test | no test carries this requirement id; annotated {not-applicable}: this MUST binds the author of a protocol specification that adopts SRV to define the Service token; ze authors no such specification -- the _Service._Proto owner name is operator config data served verbatim (internal/plugins/geodns/config.go:319, server.go:111), not a name ze defines | | [`RFC2782-Applicability-2`](#rfc2782-applicability-2) Such a specification MUST also include security considerations (§Applicability) | no test | no test carries this requirement id; annotated {not-applicable}: this is a security-considerations obligation on the author of an SRV-adopting protocol specification; ze publishes no such specification | | [`RFC2782-Priority-1`](#rfc2782-priority-1) A client MUST attempt to contact the target host with the lowest-numbered priority it can reach (§Priority) | no test | no test carries this requirement id; annotated {not-applicable}: ze implements no SRV-cognizant connecting client; geodns is authoritative and serves SRV verbatim without selection (internal/plugins/geodns/server.go:111), and internal/component/resolve/dns returns target:port strings without contacting or ordering by priority (resolver.go:321) -- no production code resolves TypeSRV | | [`RFC2782-Target-1`](#rfc2782-target-1) There MUST be one or more address records for the Target name (§Target) | no test | no test carries this requirement id; annotated {not-applicable}: an SRV Target is an arbitrary FQDN that commonly lives outside any zone geodns is authoritative for, so geodns cannot require in-zone address records for it (internal/plugins/geodns/config.go:319); the address-record obligation belongs to the target's own authoritative zone | | [`RFC2782-Target-2`](#rfc2782-target-2) The Target name MUST NOT be an alias, in the sense of RFC 1034 or RFC 2181 (§Target) | no test | no test carries this requirement id; annotated {not-applicable}: geodns serves no CNAME record type (internal/plugins/geodns/record.go:10-14), so a geodns SRV Target can never resolve to a geodns-served alias; whether an external target name is a CNAME is outside geodns's authority | | [`RFC2782-Notes-2`](#rfc2782-notes-2) A client MUST parse all of the RRs in the reply (§Notes) | no test | no test carries this requirement id; annotated {not-applicable}: this parse-all-RRs MUST governs the SRV-cognizant client that locates and connects to servers; ze has no such client. internal/component/resolve/dns iterates every answer RR (resolver.go:299) but is a generic stub resolver surfacing data, not a connecting SRV client, and no production code drives it with TypeSRV | | [`RFC2782-Notes-3`](#rfc2782-notes-3) If the Additional Data section lacks address records for all the SRV RRs, the client MUST look up the missing address records before connecting (§Notes) | no test | no test carries this requirement id; annotated {not-applicable}: Additional-section address follow-up before connecting is a connecting-SRV-client obligation ze does not implement; internal/component/resolve/dns never reads the Additional section and never connects (resolver.go:299), and geodns as a server emits no target address glue for SRV answers (server.go:182-188) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2782-Applicability-1`](#rfc2782-applicability-1) A protocol specification indicating SRV use MUST define the symbolic name to be used in the Service field of the SRV record (§Applicability) Audit verdict: not audited: no reader has judged these tests No test carries RFC2782-Applicability-1, so no unit is bound to it. ### [`RFC2782-Applicability-2`](#rfc2782-applicability-2) Such a specification MUST also include security considerations (§Applicability) Audit verdict: not audited: no reader has judged these tests No test carries RFC2782-Applicability-2, so no unit is bound to it. ### [`RFC2782-Priority-1`](#rfc2782-priority-1) A client MUST attempt to contact the target host with the lowest-numbered priority it can reach (§Priority) Audit verdict: not audited: no reader has judged these tests No test carries RFC2782-Priority-1, so no unit is bound to it. ### [`RFC2782-Target-1`](#rfc2782-target-1) There MUST be one or more address records for the Target name (§Target) Audit verdict: not audited: no reader has judged these tests No test carries RFC2782-Target-1, so no unit is bound to it. ### [`RFC2782-Target-2`](#rfc2782-target-2) The Target name MUST NOT be an alias, in the sense of RFC 1034 or RFC 2181 (§Target) Audit verdict: not audited: no reader has judged these tests No test carries RFC2782-Target-2, so no unit is bound to it. ### [`RFC2782-Notes-2`](#rfc2782-notes-2) A client MUST parse all of the RRs in the reply (§Notes) Audit verdict: not audited: no reader has judged these tests No test carries RFC2782-Notes-2, so no unit is bound to it. ### [`RFC2782-Notes-3`](#rfc2782-notes-3) If the Additional Data section lacks address records for all the SRV RRs, the client MUST look up the missing address records before connecting (§Notes) Audit verdict: not audited: no reader has judged these tests No test carries RFC2782-Notes-3, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2782, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2782, so its obligations are stated where they were written. --- ### Page: RFC 2784 - Generic Routing Encapsulation (GRE) https://ze-software.net/quality/rfc-compliance/rfc2784/ # RFC 2784 - Generic Routing Encapsulation (GRE) No row in the public ledger. Every requirement this repository extracted from RFC 2784, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 11 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 11 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 11 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 11 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 11 | of 14 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 11 | of 11 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 11 of 11 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 11 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 11 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 11 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 14 | | Gated MUST-level | 11 | | Not applicable, so out of scope | 11 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2784.md` | | Requirement shard | `rfc/requirements/rfc2784.md` | | RFC text | `rfc/full/rfc2784.txt` | ## Enrolment Enrolled: Generic Routing Encapsulation (GRE) base header: eleven MUST-level requirements, all {not-applicable}. ze builds and parses no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go buildGretun sets only the netlink link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go), delegating all header construction (C bit, Reserved0/Reserved1 zeroing, version 0, protocol type 0x0800), checksum handling, reserved-bit discard, and decapsulation/forwarding (destination lookup, TTL decrement, loop discard) to the kernel ip_gre module and the VPP dataplane. This is the same delegation rationale as the enrolled RFC 2890. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 2784. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 11 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **11** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (11):** [`RFC2784-2.2-1`](#rfc2784-2.2-1), [`RFC2784-2.3-1`](#rfc2784-2.3-1), [`RFC2784-2.3-2`](#rfc2784-2.3-2), [`RFC2784-2.3-3`](#rfc2784-2.3-3), [`RFC2784-2.3.1-1`](#rfc2784-2.3.1-1), [`RFC2784-2.6-1`](#rfc2784-2.6-1), [`RFC2784-3-1`](#rfc2784-3-1), [`RFC2784-3.1-1`](#rfc2784-3.1-1), [`RFC2784-3.1-2`](#rfc2784-3.1-2), [`RFC2784-3.1-3`](#rfc2784-3.1-3), [`RFC2784-5.2-1`](#rfc2784-5.2-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2784-2.2-1` | A compliant implementation MUST accept and process the Checksum field when present (C bit set) (§2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this receive-side obligation has no ze code path | | `RFC2784-2.3-1` | Receiver MUST discard a packet where any of bits 1-5 of Reserved0 are non-zero, unless the receiver implements RFC 1701 (§2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this receive-side obligation has no ze code path | | `RFC2784-2.3-2` | Reserved0 bits 6-12 MUST be sent as zero (§2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this header-construction obligation has no ze code path | | `RFC2784-2.3-3` | Reserved0 bits 6-12 MUST be ignored on receipt (§2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this receive-side obligation has no ze code path | | `RFC2784-2.3.1-1` | Version Number field MUST contain the value zero (§2.3.1) | MUST | 2.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this header-construction obligation has no ze code path | | `RFC2784-2.6-1` | Reserved1 field, if present, MUST be transmitted as zero (§2.6) | MUST | 2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this header-construction obligation has no ze code path | | `RFC2784-3-1` | When IPv4 is the payload, Protocol Type field MUST be set to 0x0800 (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this header-construction obligation has no ze code path | | `RFC2784-3.1-1` | When decapsulating IPv4 payload, the destination address in the IPv4 payload header MUST be used to forward the packet (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this packet-forwarding/decapsulation obligation has no ze code path | | `RFC2784-3.1-2` | TTL of the decapsulated IPv4 payload packet MUST be decremented (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this packet-forwarding/decapsulation obligation has no ze code path | | `RFC2784-3.1-3` | If decapsulated IPv4 payload destination address is the encapsulator (loop detected), the packet MUST be discarded (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this packet-forwarding/decapsulation obligation has no ze code path | | `RFC2784-5.2-1` | Packets from an RFC 1701 transmitter with non-zero bits in bits 1-5 MUST be discarded unless the receiver implements RFC 1701 (§5.2) | MUST | 5.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this receive-side obligation has no ze code path | | `RFC2784-2.4-1` | An implementation receiving a Protocol Type not listed in RFC 1700 or ETYPES SHOULD discard the packet (§2.4) | SHOULD | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC2784-3.1-4` | Care should be taken when forwarding decapsulated payload to avoid routing loops (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2784-5-1` | Implementations MAY support RFC 1701 features (Routing, Key, Sequence) but MUST also accept packets without them (§5) | MAY | 5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2784-2.2-1`](#rfc2784-2.2-1) A compliant implementation MUST accept and process the Checksum field when present (C bit set) (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this receive-side obligation has no ze code path | | [`RFC2784-2.3-1`](#rfc2784-2.3-1) Receiver MUST discard a packet where any of bits 1-5 of Reserved0 are non-zero, unless the receiver implements RFC 1701 (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this receive-side obligation has no ze code path | | [`RFC2784-2.3-2`](#rfc2784-2.3-2) Reserved0 bits 6-12 MUST be sent as zero (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this header-construction obligation has no ze code path | | [`RFC2784-2.3-3`](#rfc2784-2.3-3) Reserved0 bits 6-12 MUST be ignored on receipt (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this receive-side obligation has no ze code path | | [`RFC2784-2.3.1-1`](#rfc2784-2.3.1-1) Version Number field MUST contain the value zero (§2.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this header-construction obligation has no ze code path | | [`RFC2784-2.6-1`](#rfc2784-2.6-1) Reserved1 field, if present, MUST be transmitted as zero (§2.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this header-construction obligation has no ze code path | | [`RFC2784-3-1`](#rfc2784-3-1) When IPv4 is the payload, Protocol Type field MUST be set to 0x0800 (§3) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this header-construction obligation has no ze code path | | [`RFC2784-3.1-1`](#rfc2784-3.1-1) When decapsulating IPv4 payload, the destination address in the IPv4 payload header MUST be used to forward the packet (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this packet-forwarding/decapsulation obligation has no ze code path | | [`RFC2784-3.1-2`](#rfc2784-3.1-2) TTL of the decapsulated IPv4 payload packet MUST be decremented (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this packet-forwarding/decapsulation obligation has no ze code path | | [`RFC2784-3.1-3`](#rfc2784-3.1-3) If decapsulated IPv4 payload destination address is the encapsulator (loop detected), the packet MUST be discarded (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this packet-forwarding/decapsulation obligation has no ze code path | | [`RFC2784-5.2-1`](#rfc2784-5.2-1) Packets from an RFC 1701 transmitter with non-zero bits in bits 1-5 MUST be discarded unless the receiver implements RFC 1701 (§5.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header: it programs kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go:129 buildGretun sets only the netlink.Gretun link descriptor) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:73), and the kernel ip_gre module and VPP dataplane own the GRE header wire bits and all decapsulation/forwarding, so this receive-side obligation has no ze code path | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2784-2.2-1`](#rfc2784-2.2-1) A compliant implementation MUST accept and process the Checksum field when present (C bit set) (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-2.2-1, so no unit is bound to it. ### [`RFC2784-2.3-1`](#rfc2784-2.3-1) Receiver MUST discard a packet where any of bits 1-5 of Reserved0 are non-zero, unless the receiver implements RFC 1701 (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-2.3-1, so no unit is bound to it. ### [`RFC2784-2.3-2`](#rfc2784-2.3-2) Reserved0 bits 6-12 MUST be sent as zero (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-2.3-2, so no unit is bound to it. ### [`RFC2784-2.3-3`](#rfc2784-2.3-3) Reserved0 bits 6-12 MUST be ignored on receipt (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-2.3-3, so no unit is bound to it. ### [`RFC2784-2.3.1-1`](#rfc2784-2.3.1-1) Version Number field MUST contain the value zero (§2.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-2.3.1-1, so no unit is bound to it. ### [`RFC2784-2.6-1`](#rfc2784-2.6-1) Reserved1 field, if present, MUST be transmitted as zero (§2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-2.6-1, so no unit is bound to it. ### [`RFC2784-3-1`](#rfc2784-3-1) When IPv4 is the payload, Protocol Type field MUST be set to 0x0800 (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-3-1, so no unit is bound to it. ### [`RFC2784-3.1-1`](#rfc2784-3.1-1) When decapsulating IPv4 payload, the destination address in the IPv4 payload header MUST be used to forward the packet (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-3.1-1, so no unit is bound to it. ### [`RFC2784-3.1-2`](#rfc2784-3.1-2) TTL of the decapsulated IPv4 payload packet MUST be decremented (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-3.1-2, so no unit is bound to it. ### [`RFC2784-3.1-3`](#rfc2784-3.1-3) If decapsulated IPv4 payload destination address is the encapsulator (loop detected), the packet MUST be discarded (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-3.1-3, so no unit is bound to it. ### [`RFC2784-5.2-1`](#rfc2784-5.2-1) Packets from an RFC 1701 transmitter with non-zero bits in bits 1-5 MUST be discarded unless the receiver implements RFC 1701 (§5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2784-5.2-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2784, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2784, so its obligations are stated where they were written. --- ### Page: RFC 2865 - Remote Authentication Dial In User Service (RADIUS) https://ze-software.net/quality/rfc-compliance/rfc2865/ # RFC 2865 - Remote Authentication Dial In User Service (RADIUS) Supported for subscriber access. Every requirement this repository extracted from RFC 2865, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 83.3% | 25 of 30 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 16.7% | 5 of 30 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 30 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 30 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 45.1% | 41 of 91 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 30 | of 33 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 30 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 30 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 30 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 30 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 30 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported for subscriber access | | Enrolment | Enrolled | | Requirements | 33 | | Gated MUST-level | 30 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 100 | | Tagged units | 91 | | Recorded audit verdicts | 0 | | Discrimination records | 41 | | Summary | `rfc/short/rfc2865.md` | | Requirement shard | `rfc/requirements/rfc2865.md` | | RFC text | `rfc/full/rfc2865.txt` | ## Enrolment Enrolled: Remote Authentication Dial In User Service (RADIUS), ze as a RADIUS client/NAS: ten MUST-level requirements, all met in internal/component/radius (and l2tp/authradius). Six carry positive+negative tags: 3-1 (packet length 20..4096), 3-3 (Response Authenticator = MD5 over Code, Identifier, Length, Request Authenticator, attributes, and secret), 3-4 (verify the Response Authenticator before trusting a response), 5.2-1 (User-Password hidden via the MD5-with-shared-secret XOR chain), 5-2 (an attribute Length is bounded to 255 octets), and 3-5 (accept a response only from the server the request was sent to). Four are {single-polarity: positive}: 3-2 (the Request Authenticator is 16 random octets), 2.5-1 (a retransmission reuses the same Identifier and Request Authenticator), 5-1 (an Access-Request includes a User-Name), and 5.2-2 (User-Password padded to a multiple of 16, capped at 128). Seven new tests bind the previously-untested behaviors. ## What the public ledger says **Status:** Supported for subscriber access **What the ledger says is covered:** Access-Accept profile extraction, Filter-Id, Session-Timeout, Idle-Timeout, VSAs, pool selection. **What the ledger says remains** 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`, [`internal/component/radius/authenticator.go`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator.go)); the profile attributes above are subscriber-access only. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 25 | one part of the gated population | | Annotated instead of tested | 5 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **30** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (25):** [`RFC2865-3-1`](#rfc2865-3-1), [`RFC2865-3-3`](#rfc2865-3-3), [`RFC2865-3-4`](#rfc2865-3-4), [`RFC2865-5-1`](#rfc2865-5-1), [`RFC2865-5.2-1`](#rfc2865-5.2-1), [`RFC2865-5-2`](#rfc2865-5-2), [`RFC2865-3-5`](#rfc2865-3-5), [`RFC2865-1.1-1`](#rfc2865-1.1-1), [`RFC2865-1.1-2`](#rfc2865-1.1-2), [`RFC2865-3-6`](#rfc2865-3-6), [`RFC2865-3-8`](#rfc2865-3-8), [`RFC2865-4.1-1`](#rfc2865-4.1-1), [`RFC2865-4.1-6`](#rfc2865-4.1-6), [`RFC2865-4.1-2`](#rfc2865-4.1-2), [`RFC2865-4.1-3`](#rfc2865-4.1-3), [`RFC2865-4.4-1`](#rfc2865-4.4-1), [`RFC2865-5-4`](#rfc2865-5-4), [`RFC2865-5-8`](#rfc2865-5-8), [`RFC2865-3-7`](#rfc2865-3-7), [`RFC2865-4.1-5`](#rfc2865-4.1-5), [`RFC2865-5-6`](#rfc2865-5-6), [`RFC2865-5-7`](#rfc2865-5-7), [`RFC2865-5.11-1`](#rfc2865-5.11-1), [`RFC2865-5.24-1`](#rfc2865-5.24-1), [`RFC2865-5.25-1`](#rfc2865-5.25-1) **Annotated instead of tested (5):** [`RFC2865-3-2`](#rfc2865-3-2), [`RFC2865-2.5-1`](#rfc2865-2.5-1), [`RFC2865-5.2-2`](#rfc2865-5.2-2), [`RFC2865-4.1-4`](#rfc2865-4.1-4), [`RFC2865-5-5`](#rfc2865-5-5) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2865-3-1` | Packet Length MUST be between 20 and 4096 bytes (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestPacketRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L141). **negative:** `unit/verify` [`TestDecodeBadLength`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L193). **negative:** `unit/verify` [`TestDecodeTooLong`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L180). **negative:** `unit/verify` [`TestDecodeTooShort`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L171) | | `RFC2865-3-2` | Request Authenticator MUST be 16 cryptographically random octets (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestRFC2865RequestAuthenticatorRandom`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L40). **negative:** no negative test. **{single-polarity}:** the Request Authenticator is 16 octets from crypto/rand (internal/component/radius/packet.go:32) and there is no invalid-authenticator generation path to drive a negative | | `RFC2865-3-3` | Response Authenticator MUST be MD5(Code+ID+Length+RequestAuth+Attributes+Secret) (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestRFC2865ResponseAuthenticatorMatchesTheFormula`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_response_auth_test.go#L58). **positive:** `unit/verify` [`TestResponseAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L206). **positive:** `unit/verify` [`TestVerifyResponseAuth`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L240). **negative:** `unit/verify` [`TestRFC2865ResponseAuthenticatorCoversEveryNamedField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_response_auth_test.go#L99). **negative:** `unit/verify` [`TestResponseAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L214). **negative:** `unit/verify` [`TestVerifyResponseAuth`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L246) | | `RFC2865-3-4` | NAS MUST verify Response Authenticator before trusting a response (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestClientAuthenticatorVerify`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client_test.go#L384). **negative:** `unit/verify` [`TestClientAuthenticatorVerify`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client_test.go#L389) | | `RFC2865-2.5-1` | A retransmitted request MUST use the same Identifier and Request Authenticator (§2.5) | MUST | 2.5 - Retransmission Hints | **positive:** `unit/verify` [`TestClientRetransmit`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client_test.go#L195). **negative:** no negative test. **{single-polarity}:** a retransmission resends the identical pre-encoded request buffer (internal/component/radius/client.go:159), so the Identifier and Request Authenticator are unchanged by construction and there is no divergent-retransmit code path | | `RFC2865-5-1` | The User-Name attribute MUST be sent in Access-Request packets if available (§5, sentence at §5.1) | MUST | 5 - Attributes | **positive:** `unit/verify` [`TestRFC2865AccessRequestUserName`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L219). **positive:** `unit/verify` [`TestRFC2865SubscriberAccessRequestUserName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_test.go#L31). **negative:** `unit/verify` [`TestRFC2865AccessRequestOmitsAnUnavailableUserName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_access_request_shape_test.go#L75) | | `RFC2865-5.2-1` | User-Password encoding MUST use MD5-based XOR chain: c[0] = p[0] XOR MD5(S+RA), c[i] = p[i] XOR MD5(S+c[i-1]) (§5.2) | MUST | 5.2 - User-Password | **positive:** `unit/verify` [`TestEncodeUserPassword`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L35). **positive:** `unit/verify` [`TestEncodeUserPasswordMultiBlock`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L97). **negative:** `unit/verify` [`TestRFC2865UserPasswordDependsOnSecret`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L156) | | `RFC2865-5.2-2` | User-Password MUST be padded to a multiple of 16 octets (max 128) (§5.2) | MUST | 5.2 - User-Password | **positive:** `unit/verify` [`TestEncodeUserPassword`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L17). **positive:** `unit/verify` [`TestEncodeUserPasswordEmpty`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L52). **positive:** `unit/verify` [`TestEncodeUserPasswordMultiBlock`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L75). **positive:** `unit/verify` [`TestRFC2865UserPasswordClamp`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L175). **negative:** no negative test. **{single-polarity}:** the encoder always pads and clamps and never rejects (internal/component/radius/attr.go:18-26), so there is no reject path to drive a negative | | `RFC2865-5-2` | Attribute length MUST NOT exceed 255 bytes (Type + Length + Value) (§5) | MUST | 5 - Attributes | **positive:** `unit/verify` [`TestRFC2865AttributeLengthBound`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L78). **negative:** `unit/verify` [`TestRFC2865AttributeLengthBound`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L94) | | `RFC2865-3-5` | Only accept responses from the server address the request was sent to (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestClientExchangeAccept`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client_test.go#L106). **positive:** `unit/verify` [`TestRFC2865ResponseSourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L139). **negative:** `unit/verify` [`TestRFC2865ResponseSourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L130) | | `RFC2865-1.1-1` | A NAS that does not implement a given service MUST NOT implement the RADIUS attributes for that service (§1.1) | MUST NOT | 1.1 - Specification of Requirements | **positive:** `unit/verify` [`TestAdminAccessRequestCarriesNoUnofferedServiceAttribute`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L38). **positive:** `unit/verify` [`TestRFC2869DictionaryCoversTheServicesZeOffers`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L117). **negative:** `unit/verify` [`TestRFC2869DictionaryDeclaresNoAttributeForAnUnofferedService`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L146) | | `RFC2865-1.1-2` | A NAS MUST treat a RADIUS access-accept authorizing an unavailable service as an access-reject instead, and MUST treat unknown or unsupported Service-Types the same way (§1.1, restated at §5.6) | MUST | 1.1 - Specification of Requirements | **positive:** `unit/verify` [`TestAdminAccessAcceptWithOfferedServiceTypeIsAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L61). **positive:** `unit/verify` [`TestRFC2865SubscriberServiceTypeAuthorization`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L208). **positive:** `unit/verify` [`TestRFC2865UnsupportedServiceTypeIsRejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L234). **negative:** `unit/verify` [`TestAdminAccessAcceptWithUnofferedServiceTypeIsRejected`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L82). **negative:** `unit/verify` [`TestRFC2865SubscriberServiceTypeAuthorization`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L215). **negative:** `unit/verify` [`TestRFC2865UnsupportedServiceTypeIsRejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L241) | | `RFC2865-3-6` | Octets outside the range of the Length field MUST be treated as padding and ignored on reception (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestDecodeIgnoresOctetsOutsideTheLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L104). **positive:** `unit/verify` [`TestRFC2866LengthPaddingIgnoredOnReception`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L117). **negative:** `unit/verify` [`TestDecodeIgnoresAnAttributeHiddenInThePadding`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L120). **negative:** `unit/verify` [`TestRFC2866LengthPaddingBoundaryIsTheLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L137) | | `RFC2865-3-8` | The secret MUST NOT be empty (length 0) since this would allow packets to be trivially forged (§3) | MUST NOT | 3 - Packet Format | **positive:** `unit/verify` [`TestExchangeAcceptsANonEmptySharedSecret`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L159). **positive:** `unit/verify` [`TestRFC2865EmptySharedSecretBuildsNoClient`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L168). **negative:** `unit/verify` [`TestExchangeRefusesAnEmptySharedSecret`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L179). **negative:** `unit/verify` [`TestRFC2865EmptySharedSecretBuildsNoClient`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L173) | | `RFC2865-4.1-1` | An implementation wishing to authenticate a user MUST transmit a RADIUS packet with the Code field set to 1 (Access-Request) (§4.1) | MUST | 4.1 - Access-Request | **positive:** `unit/verify` [`TestAdminAuthenticationTransmitsAccessRequest`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L198). **positive:** `unit/verify` [`TestRADIUSAuthNASIPAddress`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/handler_test.go#L405). **negative:** `unit/verify` [`TestRFC2865AccountingRequestDoesNotCarryTheAccessRequestCode`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_access_request_shape_test.go#L96) | | `RFC2865-4.1-6` | The Request Authenticator value MUST be changed each time a new Identifier is used (§4.1) | MUST | 4.1 - Access-Request | **positive:** `unit/verify` [`TestFailoverChangesTheRequestAuthenticatorWithTheIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L308). **positive:** `unit/verify` [`TestRFC2865FailoverRegeneratesRequestAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L117). **negative:** `unit/verify` [`TestRFC2865FailoverRegeneratesRequestAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L141). **negative:** `unit/verify` [`TestRetransmitToTheSameServerKeepsItsRequestAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L345) | | `RFC2865-4.1-2` | An Access-Request MUST contain either a NAS-IP-Address attribute or a NAS-Identifier attribute (or both) (§4.1, restated at §5.44 Note 2) | MUST | 4.1 - Access-Request | **positive:** `unit/verify` [`TestAdminAccessRequestIdentifiesTheNAS`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L216). **positive:** `unit/verify` [`TestRADIUSAuthNASIPAddress`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/handler_test.go#L407). **negative:** `unit/verify` [`TestRFC2865AccessRequestNamesTheNASWithNoIdentityConfigured`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_access_request_shape_test.go#L49) | | `RFC2865-4.1-3` | An Access-Request MUST contain either a User-Password or a CHAP-Password or a State (§4.1, restated at §5.44 Note 1) | MUST | 4.1 - Access-Request | **positive:** `unit/verify` [`TestAdminAccessRequestCarriesExactlyOneCredential`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L237). **positive:** `unit/verify` [`TestRFC2865SubscriberAccessRequestCarriesCredential`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L101). **negative:** `unit/verify` [`TestRFC2865SubscriberAccessRequestCarriesCredential`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L128) | | `RFC2865-4.4-1` | A NAS that does not support challenge/response MUST treat an Access-Challenge as though it had received an Access-Reject instead (§4.4; ze supports challenge/response for the two EAP auth-methods and not for PAP or CHAP, so the rule binds the PAP and CHAP paths) | MUST | 4.4 - Access-Challenge | **positive:** `unit/verify` [`TestAccessChallengeIsTreatedAsAccessReject`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L371). **positive:** `unit/verify` [`TestRFC2865AccessChallengeIsRejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L193). **negative:** `unit/verify` [`TestAccessChallengeDoesNotFallThroughToTheNextBackend`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L396). **negative:** `unit/verify` [`TestRFC2865AccessChallengeIsRejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L200) | | `RFC2865-5-4` | A RADIUS server or client MUST NOT have any dependencies on the order of attributes of different types (§5) | MUST NOT | 5 - Attributes | **positive:** `unit/verify` [`TestAttributeLookupIgnoresTheOrderOfDifferentTypes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L417). **positive:** `unit/verify` [`TestRFC2865AccessAcceptExtractionIsOrderIndependent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_attribute_order_test.go#L66). **negative:** `unit/verify` [`TestRFC2865AttributeOrderIsObservableByPosition`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_attribute_order_test.go#L101) | | `RFC2865-5-8` | Text or String of length zero (0) MUST NOT be sent; omit the entire attribute instead (§5) | MUST NOT | 5 - Attributes | **positive:** `unit/verify` [`TestRFC2865SubscriberZeroLengthUserNameOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L159). **positive:** `unit/verify` [`TestRFC2865ZeroLengthTextIsOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L274). **positive:** `unit/verify` [`TestZeroLengthAttributeIsOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L511). **negative:** `unit/verify` [`TestOneOctetAttributeIsNotOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L528). **negative:** `unit/verify` [`TestRFC2865SubscriberZeroLengthUserNameOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L171). **negative:** `unit/verify` [`TestRFC2865ZeroLengthTextIsOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L282) | | `RFC2865-3-7` | A packet shorter than its Length field indicates MUST be silently discarded (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestDecodeAcceptsAPacketAsLongAsItsLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L136). **negative:** `unit/verify` [`TestDecodeRefusesAPacketShorterThanItsLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L147) | | `RFC2865-4.1-4` | An Access-Request MUST NOT contain both a User-Password and a CHAP-Password (§4.1, §5.44) | MUST NOT | 4.1 - Access-Request | **positive:** `unit/verify` [`TestAdminAccessRequestCarriesExactlyOneCredential`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L239). **positive:** `unit/verify` [`TestRadiusAdminChapAttributes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_test.go#L404). **negative:** no negative test. **{single-polarity}:** the credential method is a switch with one arm per method, so no builder adds both and there is no both-credentials path to drive a negative | | `RFC2865-4.1-5` | The Identifier field MUST be changed whenever the content of the Attributes field changes, and whenever a valid reply has been received for a previous request (§4.1, §2.5) | MUST | 4.1 - Access-Request | **positive:** `unit/verify` [`TestSuccessiveRequestsUseDifferentIdentifiers`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L258). **negative:** `unit/verify` [`TestRetransmitToTheSameServerKeepsItsIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L281) | | `RFC2865-5-5` | A RADIUS server or client MUST NOT require attributes of the same type to be contiguous (§5) | MUST NOT | 5 - Attributes | **positive:** `unit/verify` [`TestAttributeLookupDoesNotRequireContiguity`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L434). **negative:** no negative test. **{single-polarity}:** FindAllAttr walks the whole attribute list, so no contiguity-requiring path exists to drive a negative | | `RFC2865-5-6` | An Attribute received in an Access-Accept, Access-Reject or Access-Challenge with an invalid length MUST cause the packet to be treated as an Access-Reject or else silently discarded (§5) | MUST | 5 - Attributes | **positive:** `unit/verify` [`TestResponseWithValidAttributeLengthsIsDelivered`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L449). **negative:** `unit/verify` [`TestResponseWithAnInvalidAttributeLengthIsDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L462) | | `RFC2865-5-7` | Servers and clients MUST be able to deal with embedded nulls (§5) | MUST | 5 - Attributes | **positive:** `unit/verify` [`TestAttributeValueWithAnEmbeddedNullRoundTrips`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L479). **negative:** `unit/verify` [`TestAnEmbeddedNullDoesNotEndTheAttributeWalk`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L494) | | `RFC2865-5.11-1` | A human-readable or opaque carrier attribute (Filter-Id §5.11, Reply-Message §5.18, Framed-Route §5.22, Vendor-Specific §5.26, Proxy-State §5.33) MUST NOT affect operation of the RADIUS protocol | MUST NOT | 5.11 - Filter-Id | **positive:** `unit/verify` [`TestCarrierAttributesDoNotAffectAnAccessAccept`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L546). **negative:** `unit/verify` [`TestCarrierAttributesDoNotAffectAnAccessReject`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L563) | | `RFC2865-5.24-1` | State "MUST be sent unmodified from the client to the server in the new Access-Request reply to that challenge, if any" (§5.24) | MUST | 5.24 - State | **positive:** `unit/verify` [`TestRadiusAdminStateIsReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_state_echo_test.go#L31). **negative:** `unit/verify` [`TestRadiusAdminStateIsNotManufactured`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_state_echo_test.go#L68) | | `RFC2865-5.25-1` | The client MUST NOT interpret the State (§5.24) or Class (§5.25) attribute locally | MUST NOT | 5.25 - Class | **positive:** `unit/verify` [`TestRadiusAdminEapChallengeLoopCarriesState`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L426). **positive:** `unit/verify` [`TestRadiusClassIsNotInterpretedLocally`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_test.go#L307). **positive:** `unit/verify` [`TestStateIsNotInterpretedLocally`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L583). **negative:** `unit/verify` [`TestExtractRadiusConfigProfileAttrNeverClass`](https://github.com/ze-software/ze/blob/main/internal/component/radius/config_test.go#L102) | | `RFC2865-2.5-2` | NAS SHOULD use exponential backoff between retransmits (§2.5) | SHOULD | 2.5 - Retransmission Hints | **positive:** no positive test. **negative:** no negative test | | `RFC2865-x-1` | Use constant-time comparison for authenticator verification (Implementation Constraints) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC2865-5-3` | Server MAY include Reply-Message attribute in Access-Reject (§5) | MAY | 5 - Attributes | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 2865 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2865-3-1`](#rfc2865-3-1) Packet Length MUST be between 20 and 4096 bytes (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDecodeBadLength`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L193) | unit/verify | unproven | | negative | [`TestDecodeTooLong`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L180) | unit/verify | unproven | | negative | [`TestDecodeTooShort`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L171) | unit/verify | unproven | | positive | [`TestPacketRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L141) | unit/verify | unproven | ### [`RFC2865-3-2`](#rfc2865-3-2) Request Authenticator MUST be 16 cryptographically random octets (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC2865RequestAuthenticatorRandom`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L40) | unit/verify | unproven | ### [`RFC2865-3-3`](#rfc2865-3-3) Response Authenticator MUST be MD5(Code+ID+Length+RequestAuth+Attributes+Secret) (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestResponseAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L214) | unit/verify | unproven | | negative | [`TestVerifyResponseAuth`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L246) | unit/verify | unproven | | negative | [`TestRFC2865ResponseAuthenticatorCoversEveryNamedField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_response_auth_test.go#L99) | unit/verify | unproven | | positive | [`TestResponseAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L206) | unit/verify | unproven | | positive | [`TestVerifyResponseAuth`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L240) | unit/verify | unproven | | positive | [`TestRFC2865ResponseAuthenticatorMatchesTheFormula`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_response_auth_test.go#L58) | unit/verify | unproven | ### [`RFC2865-3-4`](#rfc2865-3-4) NAS MUST verify Response Authenticator before trusting a response (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestClientAuthenticatorVerify`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client_test.go#L389) | unit/verify | unproven | | positive | [`TestClientAuthenticatorVerify`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client_test.go#L384) | unit/verify | unproven | ### [`RFC2865-2.5-1`](#rfc2865-2.5-1) A retransmitted request MUST use the same Identifier and Request Authenticator (§2.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestClientRetransmit`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client_test.go#L195) | unit/verify | unproven | ### [`RFC2865-5-1`](#rfc2865-5-1) The User-Name attribute MUST be sent in Access-Request packets if available (§5, sentence at §5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865AccessRequestOmitsAnUnavailableUserName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_access_request_shape_test.go#L75) | unit/verify | revert, verified | | positive | [`TestRFC2865SubscriberAccessRequestUserName`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_test.go#L31) | unit/verify | unproven | | positive | [`TestRFC2865AccessRequestUserName`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L219) | unit/verify | unproven | ### [`RFC2865-5.2-1`](#rfc2865-5.2-1) User-Password encoding MUST use MD5-based XOR chain: c[0] = p[0] XOR MD5(S+RA), c[i] = p[i] XOR MD5(S+c[i-1]) (§5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865UserPasswordDependsOnSecret`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L156) | unit/verify | unproven | | positive | [`TestEncodeUserPassword`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L35) | unit/verify | unproven | | positive | [`TestEncodeUserPasswordMultiBlock`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L97) | unit/verify | unproven | ### [`RFC2865-5.2-2`](#rfc2865-5.2-2) User-Password MUST be padded to a multiple of 16 octets (max 128) (§5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEncodeUserPassword`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L17) | unit/verify | unproven | | positive | [`TestEncodeUserPasswordEmpty`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L52) | unit/verify | unproven | | positive | [`TestEncodeUserPasswordMultiBlock`](https://github.com/ze-software/ze/blob/main/internal/component/radius/attr_test.go#L75) | unit/verify | unproven | | positive | [`TestRFC2865UserPasswordClamp`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L175) | unit/verify | unproven | ### [`RFC2865-5-2`](#rfc2865-5-2) Attribute length MUST NOT exceed 255 bytes (Type + Length + Value) (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865AttributeLengthBound`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L94) | unit/verify | unproven | | positive | [`TestRFC2865AttributeLengthBound`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L78) | unit/verify | unproven | ### [`RFC2865-3-5`](#rfc2865-3-5) Only accept responses from the server address the request was sent to (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865ResponseSourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L130) | unit/verify | unproven | | positive | [`TestClientExchangeAccept`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client_test.go#L106) | unit/verify | unproven | | positive | [`TestRFC2865ResponseSourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_test.go#L139) | unit/verify | unproven | ### [`RFC2865-1.1-1`](#rfc2865-1.1-1) A NAS that does not implement a given service MUST NOT implement the RADIUS attributes for that service (§1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869DictionaryDeclaresNoAttributeForAnUnofferedService`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L146) | unit/verify | unproven | | positive | [`TestAdminAccessRequestCarriesNoUnofferedServiceAttribute`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L38) | unit/verify | revert, verified | | positive | [`TestRFC2869DictionaryCoversTheServicesZeOffers`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L117) | unit/verify | unproven | ### [`RFC2865-1.1-2`](#rfc2865-1.1-2) A NAS MUST treat a RADIUS access-accept authorizing an unavailable service as an access-reject instead, and MUST treat unknown or unsupported Service-Types the same way (§1.1, restated at §5.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865SubscriberServiceTypeAuthorization`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L215) | unit/verify | unproven | | negative | [`TestRFC2865UnsupportedServiceTypeIsRejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L241) | unit/verify | unproven | | negative | [`TestAdminAccessAcceptWithUnofferedServiceTypeIsRejected`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L82) | unit/verify | revert, verified | | positive | [`TestRFC2865SubscriberServiceTypeAuthorization`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L208) | unit/verify | unproven | | positive | [`TestRFC2865UnsupportedServiceTypeIsRejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L234) | unit/verify | unproven | | positive | [`TestAdminAccessAcceptWithOfferedServiceTypeIsAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L61) | unit/verify | revert, verified | ### [`RFC2865-3-6`](#rfc2865-3-6) Octets outside the range of the Length field MUST be treated as padding and ignored on reception (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDecodeIgnoresAnAttributeHiddenInThePadding`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L120) | unit/verify | revert, verified | | negative | [`TestRFC2866LengthPaddingBoundaryIsTheLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L137) | unit/verify | unproven | | positive | [`TestDecodeIgnoresOctetsOutsideTheLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L104) | unit/verify | revert, verified | | positive | [`TestRFC2866LengthPaddingIgnoredOnReception`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L117) | unit/verify | unproven | ### [`RFC2865-3-8`](#rfc2865-3-8) The secret MUST NOT be empty (length 0) since this would allow packets to be trivially forged (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865EmptySharedSecretBuildsNoClient`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L173) | unit/verify | unproven | | negative | [`TestExchangeRefusesAnEmptySharedSecret`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L179) | unit/verify | revert, verified | | positive | [`TestRFC2865EmptySharedSecretBuildsNoClient`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L168) | unit/verify | unproven | | positive | [`TestExchangeAcceptsANonEmptySharedSecret`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L159) | unit/verify | revert, verified | ### [`RFC2865-4.1-1`](#rfc2865-4.1-1) An implementation wishing to authenticate a user MUST transmit a RADIUS packet with the Code field set to 1 (Access-Request) (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865AccountingRequestDoesNotCarryTheAccessRequestCode`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_access_request_shape_test.go#L96) | unit/verify | revert, verified | | positive | [`TestRADIUSAuthNASIPAddress`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/handler_test.go#L405) | unit/verify | revert, verified | | positive | [`TestAdminAuthenticationTransmitsAccessRequest`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L198) | unit/verify | revert, verified | ### [`RFC2865-4.1-6`](#rfc2865-4.1-6) The Request Authenticator value MUST be changed each time a new Identifier is used (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865FailoverRegeneratesRequestAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L141) | unit/verify | unproven | | negative | [`TestRetransmitToTheSameServerKeepsItsRequestAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L345) | unit/verify | revert, verified | | positive | [`TestRFC2865FailoverRegeneratesRequestAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L117) | unit/verify | unproven | | positive | [`TestFailoverChangesTheRequestAuthenticatorWithTheIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L308) | unit/verify | revert, verified | ### [`RFC2865-4.1-2`](#rfc2865-4.1-2) An Access-Request MUST contain either a NAS-IP-Address attribute or a NAS-Identifier attribute (or both) (§4.1, restated at §5.44 Note 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865AccessRequestNamesTheNASWithNoIdentityConfigured`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_access_request_shape_test.go#L49) | unit/verify | revert, verified | | positive | [`TestRADIUSAuthNASIPAddress`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/handler_test.go#L407) | unit/verify | revert, verified | | positive | [`TestAdminAccessRequestIdentifiesTheNAS`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L216) | unit/verify | revert, verified | ### [`RFC2865-4.1-3`](#rfc2865-4.1-3) An Access-Request MUST contain either a User-Password or a CHAP-Password or a State (§4.1, restated at §5.44 Note 1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865SubscriberAccessRequestCarriesCredential`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L128) | unit/verify | unproven | | positive | [`TestRFC2865SubscriberAccessRequestCarriesCredential`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L101) | unit/verify | unproven | | positive | [`TestAdminAccessRequestCarriesExactlyOneCredential`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L237) | unit/verify | revert, verified | ### [`RFC2865-4.4-1`](#rfc2865-4.4-1) A NAS that does not support challenge/response MUST treat an Access-Challenge as though it had received an Access-Reject instead (§4.4; ze supports challenge/response for the two EAP auth-methods and not for PAP or CHAP, so the rule binds the PAP and CHAP paths) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865AccessChallengeIsRejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L200) | unit/verify | unproven | | negative | [`TestAccessChallengeDoesNotFallThroughToTheNextBackend`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L396) | unit/verify | revert, verified | | positive | [`TestRFC2865AccessChallengeIsRejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L193) | unit/verify | unproven | | positive | [`TestAccessChallengeIsTreatedAsAccessReject`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L371) | unit/verify | revert, verified | ### [`RFC2865-5-4`](#rfc2865-5-4) A RADIUS server or client MUST NOT have any dependencies on the order of attributes of different types (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865AttributeOrderIsObservableByPosition`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_attribute_order_test.go#L101) | unit/verify | revert, verified | | positive | [`TestRFC2865AccessAcceptExtractionIsOrderIndependent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_attribute_order_test.go#L66) | unit/verify | revert, verified | | positive | [`TestAttributeLookupIgnoresTheOrderOfDifferentTypes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L417) | unit/verify | revert, verified | ### [`RFC2865-5-8`](#rfc2865-5-8) Text or String of length zero (0) MUST NOT be sent; omit the entire attribute instead (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2865SubscriberZeroLengthUserNameOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L171) | unit/verify | unproven | | negative | [`TestRFC2865ZeroLengthTextIsOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L282) | unit/verify | unproven | | negative | [`TestOneOctetAttributeIsNotOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L528) | unit/verify | revert, verified | | positive | [`TestRFC2865SubscriberZeroLengthUserNameOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2865_nas_obligations_test.go#L159) | unit/verify | unproven | | positive | [`TestRFC2865ZeroLengthTextIsOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_nas_obligations_test.go#L274) | unit/verify | unproven | | positive | [`TestZeroLengthAttributeIsOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L511) | unit/verify | revert, verified | ### [`RFC2865-3-7`](#rfc2865-3-7) A packet shorter than its Length field indicates MUST be silently discarded (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDecodeRefusesAPacketShorterThanItsLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L147) | unit/verify | revert, verified | | positive | [`TestDecodeAcceptsAPacketAsLongAsItsLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L136) | unit/verify | revert, verified | ### [`RFC2865-4.1-4`](#rfc2865-4.1-4) An Access-Request MUST NOT contain both a User-Password and a CHAP-Password (§4.1, §5.44) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRadiusAdminChapAttributes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_test.go#L404) | unit/verify | revert, verified | | positive | [`TestAdminAccessRequestCarriesExactlyOneCredential`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L239) | unit/verify | revert, verified | ### [`RFC2865-4.1-5`](#rfc2865-4.1-5) The Identifier field MUST be changed whenever the content of the Attributes field changes, and whenever a valid reply has been received for a previous request (§4.1, §2.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRetransmitToTheSameServerKeepsItsIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L281) | unit/verify | revert, verified | | positive | [`TestSuccessiveRequestsUseDifferentIdentifiers`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L258) | unit/verify | revert, verified | ### [`RFC2865-5-5`](#rfc2865-5-5) A RADIUS server or client MUST NOT require attributes of the same type to be contiguous (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestAttributeLookupDoesNotRequireContiguity`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L434) | unit/verify | revert, verified | ### [`RFC2865-5-6`](#rfc2865-5-6) An Attribute received in an Access-Accept, Access-Reject or Access-Challenge with an invalid length MUST cause the packet to be treated as an Access-Reject or else silently discarded (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestResponseWithAnInvalidAttributeLengthIsDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L462) | unit/verify | revert, verified | | positive | [`TestResponseWithValidAttributeLengthsIsDelivered`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L449) | unit/verify | revert, verified | ### [`RFC2865-5-7`](#rfc2865-5-7) Servers and clients MUST be able to deal with embedded nulls (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAnEmbeddedNullDoesNotEndTheAttributeWalk`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L494) | unit/verify | revert, verified | | positive | [`TestAttributeValueWithAnEmbeddedNullRoundTrips`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L479) | unit/verify | revert, verified | ### [`RFC2865-5.11-1`](#rfc2865-5.11-1) A human-readable or opaque carrier attribute (Filter-Id §5.11, Reply-Message §5.18, Framed-Route §5.22, Vendor-Specific §5.26, Proxy-State §5.33) MUST NOT affect operation of the RADIUS protocol Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCarrierAttributesDoNotAffectAnAccessReject`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L563) | unit/verify | revert, verified | | positive | [`TestCarrierAttributesDoNotAffectAnAccessAccept`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L546) | unit/verify | revert, verified | ### [`RFC2865-5.24-1`](#rfc2865-5.24-1) State "MUST be sent unmodified from the client to the server in the new Access-Request reply to that challenge, if any" (§5.24) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminStateIsNotManufactured`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_state_echo_test.go#L68) | unit/verify | revert, verified | | positive | [`TestRadiusAdminStateIsReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_state_echo_test.go#L31) | unit/verify | revert, verified | ### [`RFC2865-5.25-1`](#rfc2865-5.25-1) The client MUST NOT interpret the State (§5.24) or Class (§5.25) attribute locally Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestExtractRadiusConfigProfileAttrNeverClass`](https://github.com/ze-software/ze/blob/main/internal/component/radius/config_test.go#L102) | unit/verify | unproven | | positive | [`TestRadiusAdminEapChallengeLoopCarriesState`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L426) | unit/verify | revert, verified | | positive | [`TestRadiusClassIsNotInterpretedLocally`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_test.go#L307) | unit/verify | unproven | | positive | [`TestStateIsNotInterpretedLocally`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_walk_test.go#L583) | unit/verify | revert, verified | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement walk agent, spec-rfcgate-6-supported-extraction-signoff phase 2 (Tier 1, rfc2865) | | Signed off | 2026-08-30 | | Register | rfc2119 | | Source | rfc/full/rfc2865.txt | | Source fingerprint | 5082eacae1b57b82 | | Record | rfc/extraction/rfc2865.json | | Mapped sentences | 23 | | Declined as scope | 51 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, Copyright Notice, Abstract and Table of Contents. The Abstract restates section 1 and states no obligation. | | `1` | not stated | 1 | walked | Introduction: what RADIUS is, its client/server model, its network security model and its extensibility. It states no obligation of its own. Its one derived site is the Section 5.44 table legend, which the section parser attached here because the legend line begins with '1'. | | `1.1` | Specification of Requirements | 3 | walked | Specification of Requirements. It carries the RFC 2119 key-words paragraph the site scan correctly refuses to count, the definition of unconditional and conditional compliance, and three NAS obligations about services a NAS does not implement (sites 1.1:1 to 1.1:3). | | `1.2` | Terminology: service, session and 'silently discard' | 0 | walked | Terminology: service, session and 'silently discard'. The two keywords in the 'silently discard' entry are SHOULDs about logging and counting a discarded packet, so the scan derives no site. | | `2` | Operation | 2 | walked | Operation. It narrates one authentication exchange end to end. Its two sites are both server obligations. The client-side sentences here are indicative or MAY-level and are stated normatively in sections 3, 4.1 and 4.4. | | `2.1` | Challenge/Response | 0 | walked | Challenge/Response. Descriptive: what a challenge is, what the user does with it, and a worked example. It states no obligation, and the NAS obligation for a challenge is in section 4.4. | | `2.2` | Interoperation with PAP and CHAP | 2 | walked | Interoperation with PAP and CHAP. It describes how a NAS maps PAP and CHAP credentials onto User-Password and CHAP-Password, in indicative prose, and then states two server obligations (sites 2.2:1 and 2.2:2). | | `2.3` | Proxy | 10 | walked | Proxy. Ten sites, every one binding a forwarding server or a remote server. Ze forwards no RADIUS request and answers none, so the whole section is excluded by role. | | `2.4` | Why UDP? Design rationale for the transport choice | 0 | walked | Why UDP? Design rationale for the transport choice. It uses 'must' in lowercase throughout to describe consequences, and states no obligation. | | `2.5` | Retransmission Hints | 2 | walked | Retransmission Hints. Its two sites are the same-server retransmission rule (site 2.5:1) and the changed-attributes rule (site 2.5:2), which Section 4.1 states in full. The exponential-backoff row RFC2865-2.5-2 is a ze implementation constraint at SHOULD level, not a sentence of this section, and a SHOULD row never gates. | | `2.6` | Keep-Alives Considered Harmful | 0 | walked | Keep-Alives Considered Harmful. Advice against probing a server, phrased as 'strongly discouraged'. No capitalised keyword and no obligation. | | `3` | Packet Format | 5 | walked | Packet Format. The header layout, the Code, Identifier, Length and Authenticator fields, the Request and Response Authenticator definitions, and the Administrative Note on the shared secret. Five sites: three bind any receiver or both ends and are mapped, two bind a server or a proxy. | | `4` | Packet Types | 0 | walked | Packet Types. One sentence saying the Code field decides the type. The four types are 4.1 to 4.4. | | `4.1` | Access-Request | 8 | walked | Access-Request. Eight sites. Six bind the sender and are mapped or duplicated onto Section 2.5; one binds the server that must reply; one is the retransmission half of the Identifier rule. | | `4.2` | Access-Accept | 2 | walked | Access-Accept. Two sites: the server's obligation to transmit it, and the receiver's obligation to check the Response Authenticator against the pending request. | | `4.3` | Access-Reject | 1 | walked | Access-Reject. One site, binding the server that transmits it. The Reply-Message sentence is MAY-level and is the summary's RFC2865-5-3. | | `4.4` | Access-Challenge | 4 | walked | Access-Challenge. Four sites: two bind the server, and two state what a NAS without challenge/response support owes, which is RFC2865-4.4-1. | | `5` | Attributes | 7 | walked | Attributes. The attribute format, the Type, Length and Value fields, and the five data types. Seven sites: five bind a client and are mapped, one binds a proxy, one repeats the zero-length rule for String. | | `5.1` | User-Name | 1 | walked | User-Name. One site: the attribute MUST be sent in an Access-Request if available. | | `5.2` | User-Password | 0 | walked | User-Password. The hiding algorithm, stated entirely in indicative prose and as a set of equations, so the scan derives no site. The two obligations the summary declares from it are listed below. | | `5.3` | CHAP-Password | 0 | walked | CHAP-Password. Format and the rule that the challenge is in CHAP-Challenge if present and the Request Authenticator otherwise. Indicative prose, no site. | | `5.4` | NAS-IP-Address | 3 | walked | NAS-IP-Address. Three sites: the Section 4.1 identification rule restated, and the two shared-secret-selection sentences that bind the server. | | `5.5` | NAS-Port | 0 | walked | NAS-Port. Attribute format and a note on 16-bit port values. No obligation. | | `5.6` | Service-Type | 1 | walked | Service-Type. One site: a NAS MUST treat an unknown or unsupported Service-Type as an Access-Reject. The rest is the value table. | | `5.7` | Framed-Protocol | 0 | walked | Framed-Protocol. Value table for PPP, SLIP, ARAP, Gandalf, Xylogics and X.75. No obligation. | | `5.8` | Framed-IP-Address | 0 | walked | Framed-IP-Address. Format and the two special values 0xFFFFFFFF and 0xFFFFFFFE. No obligation. | | `5.9` | Framed-IP-Netmask | 0 | walked | Framed-IP-Netmask. Format. No obligation. | | `5.10` | Framed-Routing | 0 | walked | Framed-Routing. Value table. No obligation. | | `5.11` | Filter-Id | 1 | walked | Filter-Id. One site: the attribute is human readable and MUST NOT affect operation of the protocol. | | `5.12` | Framed-MTU | 0 | walked | Framed-MTU. Format and range. No obligation. | | `5.13` | Framed-Compression | 0 | walked | Framed-Compression. Value table. No obligation. | | `5.14` | Login-IP-Host | 0 | walked | Login-IP-Host. Format and the two special values. No obligation. | | `5.15` | Login-Service | 0 | walked | Login-Service. Value table. No obligation. | | `5.16` | Login-TCP-Port | 0 | walked | Login-TCP-Port. Format. No obligation. | | `5.17` | Unassigned attribute type 17 | 0 | walked | Unassigned attribute type 17. A heading and a note. No obligation. | | `5.18` | Reply-Message | 2 | walked | Reply-Message. Two sites: the display-order rule for several messages, and the human-readable sentence the other carrier attributes repeat. | | `5.19` | Callback-Number | 0 | walked | Callback-Number. Format. No obligation. | | `5.20` | Callback-Id | 0 | walked | Callback-Id. Format and a note that the id is NAS specific. No obligation. | | `5.21` | Unassigned attribute type 21 | 0 | walked | Unassigned attribute type 21. A heading and a note. No obligation. | | `5.22` | Framed-Route | 1 | walked | Framed-Route. One site: the human-readable sentence. The route text format is stated in indicative prose. | | `5.23` | Framed-IPX-Network | 0 | walked | Framed-IPX-Network. Format and the special value 0xFFFFFFFE. No obligation. | | `5.24` | State | 3 | walked | State. Three sites: the challenge-reply carry rule, the Termination-Action carry rule, and the prohibition on interpreting the attribute locally. | | `5.25` | Class | 1 | walked | Class. One site: the client MUST NOT interpret the attribute locally. The accounting-echo sentence beside it is a SHOULD. | | `5.26` | Vendor-Specific | 2 | walked | Vendor-Specific. Two sites: the attribute MUST NOT affect protocol operation, and a server not equipped to interpret vendor information MUST ignore it. | | `5.27` | Session-Timeout | 0 | walked | Session-Timeout. Format and meaning. No obligation. | | `5.28` | Idle-Timeout | 0 | walked | Idle-Timeout. Format and meaning. No obligation. | | `5.29` | Termination-Action | 0 | walked | Termination-Action. Value table. No obligation. | | `5.30` | Called-Station-Id | 0 | walked | Called-Station-Id. Format and a note on dialed-number identification. No obligation. | | `5.31` | Calling-Station-Id | 0 | walked | Calling-Station-Id. Format and a note on automatic number identification. No obligation. | | `5.32` | NAS-Identifier | 3 | walked | NAS-Identifier. Three sites, the same three sentences Section 5.4 carries for NAS-IP-Address. | | `5.33` | Proxy-State | 4 | walked | Proxy-State. Four sites, every one binding a proxy server or a server that adds a Proxy-State. | | `5.34` | Login-LAT-Service | 0 | walked | Login-LAT-Service. Format and a note on LAT string handling. No obligation, and ze offers no LAT service. | | `5.35` | Login-LAT-Node | 0 | walked | Login-LAT-Node. Format. No obligation, and ze offers no LAT service. | | `5.36` | Login-LAT-Group | 0 | walked | Login-LAT-Group. Format of the 32-octet group code bitmap. No obligation, and ze offers no LAT service. | | `5.37` | Framed-AppleTalk-Link | 0 | walked | Framed-AppleTalk-Link. Format. No obligation, and ze offers no AppleTalk service. | | `5.38` | Framed-AppleTalk-Network | 0 | walked | Framed-AppleTalk-Network. Format. No obligation, and ze offers no AppleTalk service. | | `5.39` | Framed-AppleTalk-Zone | 0 | walked | Framed-AppleTalk-Zone. Format. No obligation, and ze offers no AppleTalk service. | | `5.40` | CHAP-Challenge | 0 | walked | CHAP-Challenge. Format and the rule that a 16-octet challenge may travel in the Request Authenticator instead. Indicative prose, no site. | | `5.41` | NAS-Port-Type | 0 | walked | NAS-Port-Type. Value table. No obligation. | | `5.42` | Port-Limit | 0 | walked | Port-Limit. Format and meaning. No obligation. | | `5.43` | Login-LAT-Port | 0 | walked | Login-LAT-Port. Format. No obligation, and ze offers no LAT service. | | `5.44` | Table of Attributes | 3 | walked | Table of Attributes. Three sites, all in Note 1 and Note 2, restating Section 4.1's credential and identification rules. The table's own cells and its legend are covered by the two legend sites the section parser split off into sections '1' and '0'. | | `0` | not stated | 1 | walked | The trailing legend block of the Section 5.44 Table of Attributes, which the section parser split into a section of its own because its first line begins with '0'. Its one site is the '0' legend entry. | | `6` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. It names the registries and the assignment process for RADIUS packet type codes, attribute types and attribute values. Binds IANA, not a speaker. | | `6.1` | not stated | 0 | skipped (iana) | Definition of Terms for the IANA section: Private Use, Specification Required, Designated Expert and so on. | | `6.2` | Recommended Registration Policies | 0 | skipped (iana) | Recommended Registration Policies. It tells IANA which policy governs each RADIUS namespace. | | `7` | Examples | 0 | walked | Examples. Three worked exchanges shown as attribute dumps. Non-normative illustration of sections 4 and 5, and it states no obligation. | | `7.1` | Example: user Telnet to a specified host | 0 | walked | Example: user Telnet to a specified host. An attribute dump of one Access-Request and its Access-Accept. | | `7.2` | Example: framed user authenticating with CHAP | 0 | walked | Example: framed user authenticating with CHAP. An attribute dump of one exchange. | | `7.3` | Example: user with a challenge-response card | 0 | walked | Example: user with a challenge-response card. An attribute dump of an Access-Request, an Access-Challenge and a second Access-Request. | | `8` | Security Considerations | 0 | walked | Security Considerations. It discusses one authentication method per user name, secret storage, secret distribution and the known weakness of the Section 5.2 hiding mechanism. Its two keywords are SHOULDs, so the scan derives no site. | | `9` | Change Log | 1 | walked | Change Log. It lists what changed from RFC 2138. Its one site quotes the Section 5 proxy ordering sentence while describing that change. | | `10` | References, normative and informative | 0 | skipped (references) | References, normative and informative. | | `11` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `12` | Chair's Address | 0 | skipped (front-matter) | Chair's Address. A contact block, the same boilerplate class as the title block at the head of the document. | | `13` | Authors' Addresses | 0 | skipped (front-matter) | Authors' Addresses. A contact block. | | `14` | Full Copyright Statement | 0 | skipped (front-matter) | Full Copyright Statement. The RFC boilerplate closing the document. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The legend of the Section 5.44 Table of Attributes, which the section parser attached to section 1 because the line begins with '1'. It defines what the table entry '1' means and binds no speaker: 'this attribute' names none. No column of that table carries '1' for any RFC 2865 attribute, so the case it defines is never exercised. | Exactly one instance of this attribute MUST be present in packet. | | `1.1:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The ARAP example restating the sentence before it. RFC2865-1.1-1 carries the obligation and site 1.1:1 maps it. | For example, a NAS that is unable to offer ARAP service MUST NOT implement the RADIUS attributes for ARAP. | | `2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. | A request from a client for which the RADIUS server does not have a shared secret MUST be silently discarded. | | `2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. The obligation is to copy Proxy-State into the response it builds. | If any Proxy-State attributes were present in the Access-Request, they MUST be copied unmodified and in order into the response packet. | | `2.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. The obligation is to answer with an Access-Reject when it cannot perform the authentication. | If the RADIUS server is unable to perform the requested authentication it MUST return an Access-Reject. | | `2.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. The obligation turns on whether the server holds the password in cleartext, which is a server-side database question. | If the password is not available in cleartext to the RADIUS server then the server MUST send an Access-Reject to the client. | | `2.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | The forwarding server MUST treat any Proxy-State attributes already in the packet as opaque data. | | `2.3:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | Its operation MUST NOT depend on the content of Proxy-State attributes added by previous servers. | | `2.3:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | If there are any Proxy-State attributes in the request received from the client, the forwarding server MUST include those Proxy-State attributes in its reply to the client. | | `2.3:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | If the forwarding server omits the Proxy-State attributes in the forwarded access-request, it MUST attach them to the response before sending it to the client. | | `2.3:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. The Request-Authenticator-to-CHAP-Challenge copy is a forwarding act. | If a CHAP-Password attribute is present in the packet and no CHAP-Challenge attribute is present, the forwarding server MUST leave the Request- Authenticator untouched or copy it to a CHAP-Challenge attribute. | | `2.3:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. It bounds the Proxy-State a forwarder may add. | (It MUST NOT add more than one.) If it adds a Proxy- State, the Proxy-State MUST appear after any other Proxy-States in the packet. | | `2.3:7` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | The forwarding server MUST NOT modify any other Proxy-States that were in the packet (it may choose not to forward them, but it MUST NOT change their contents). | | `2.3:8` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | The forwarding server MUST NOT change the order of any attributes of the same type, including Proxy-State. | | `2.3:9` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the REMOTE SERVER at the end of a proxy chain, which builds the response. Ze implements no RADIUS server role at all, remote or forwarding. The producer that would act as it if ze did is ze's RADIUS code, which is a client only: `Exchange` and `SendToServers` (`internal/component/radius/client.go`) send a request and match the reply, and `Authenticate` (`internal/component/radius/authenticator.go`) consumes the response. Nothing serves, proxies or re-authenticates. | The remote server MUST copy all Proxy-State attributes (and only the Proxy-State attributes) in order from the access-request to the response packet, without modifying them. | | `2.3:10` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. It bounds what local policy may rewrite in a packet passing through. | A forwarding server MUST not modify existing Proxy-State, State, or Class attributes present in the packet. | | `2.5:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates in the retransmission-hints section what Section 4.1 states for the Identifier field and for the Request Authenticator. RFC2865-4.1-5 carries the Identifier half and site 4.1:6 maps it; RFC2865-4.1-6 carries the Request Authenticator half and site 4.1:8 maps that. | If any attributes have changed, you MUST use a new Request Authenticator and ID. | | `3:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. Selecting a shared secret from the source address of a received Access-Request is a server act. | A RADIUS server MUST use the source IP address of the RADIUS UDP packet to decide which shared secret to use, so that RADIUS requests can be proxied. | | `3:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. The obligation is to strip the Proxy-State the proxy itself added. | When using a forwarding proxy, the proxy must be able to alter the packet as it passes through in each direction - when the proxy forwards the request, the proxy MAY add a Proxy-State Attribute, and when the proxy forwards a response, it MUST remove its Proxy- State Attribute if it added one. | | `4.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. The obligation is to transmit a reply on receipt of an Access-Request. | Upon receipt of an Access-Request from a valid client, an appropriate reply MUST be transmitted. | | `4.1:7` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The retransmission half of the Identifier rule, which Section 2.5 states in full. RFC2865-2.5-1 carries it and site 2.5:1 maps it. | For retransmissions, the Identifier MUST remain unchanged. | | `4.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. The obligation is to transmit the Access-Accept. | If all Attribute values received in an Access-Request are acceptable then the RADIUS implementation MUST transmit a packet with the Code field set to 2 (Access-Accept). | | `4.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. The obligation is to transmit the Access-Reject. | If any value of the received Attributes is not acceptable, then the RADIUS server MUST transmit a packet with the Code field set to 3 (Access-Reject). | | `4.4:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. The obligation is to transmit the Access-Challenge. | If the RADIUS server desires to send the user a challenge requiring a response, then the RADIUS server MUST respond to the Access-Request by transmitting a packet with the Code field set to 11 (Access-Challenge). | | `4.4:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Section 4.4 repeats for an Access-Challenge what Section 4.2 states for an Access-Accept. RFC2865-3-4 carries it and site 4.2:2 maps it. | Additionally, the Response Authenticator field MUST contain the correct response for the pending Access-Request. | | `4.4:4` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The PAP-forwarding variant of the same sentence: a NAS that cannot forward the Reply-Message treats the challenge as a reject. RFC2865-4.4-1 carries it and site 4.4:3 maps it. | If the NAS cannot do so, it MUST treat the Access-Challenge as though it had received an Access-Reject instead. | | `5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a PROXY: the sentence names the speaker, 'MUST be preserved by any proxies'. the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | If multiple Attributes with the same Type are present, the order of Attributes with the same Type MUST be preserved by any proxies. | | `5:7` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The String paragraph repeating the Text paragraph's sentence word for word. RFC2865-5-8 covers both and site 5:6 maps it. | Strings of length zero (0) MUST NOT be sent; omit the entire attribute instead. | | `5.4:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The NAS-IP-Address section restating the Section 4.1 rule. RFC2865-4.1-2 carries it and site 4.1:3 maps it. | Either NAS-IP- Address or NAS-Identifier MUST be present in an Access-Request packet. | | `5.4:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. Choosing which shared secret authenticates a received request is a server act, and this sentence forbids one input to that choice. | Note that NAS-IP-Address MUST NOT be used to select the shared secret used to authenticate the request. | | `5.4:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. It names the input the server must use instead, and repeats Section 3's sentence at site 3:4. | The source IP address of the Access-Request packet MUST be used to select the shared secret. | | `5.6:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The Service-Type section stating for one attribute what Section 1.1 states in general. RFC2865-1.1-2 names both sections and site 1.1:3 maps it. | A NAS is not required to implement all of these service types, and MUST treat unknown or unsupported Service-Types as though an Access-Reject had been received instead. | | `5.18:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that DISPLAYS several Reply-Messages to a user, a role ze does not implement. Both RADIUS paths surface at most one: FindAttr returns the first (internal/component/radius/authenticator.go, internal/component/l2tp/plugins/authradius/handler.go), so no display order exists to get wrong. | Multiple Reply-Message's MAY be included and if any are displayed, they MUST be displayed in the same order as they appear in the packet. | | `5.18:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Reply-Message repeating the Filter-Id sentence. RFC2865-5.11-1 names Section 5.18 and site 5.11:1 maps it. | It is intended to be human readable, and MUST NOT affect operation of the protocol. | | `5.22:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Framed-Route repeating the Filter-Id sentence. RFC2865-5.11-1 names Section 5.22 and site 5.11:1 maps it. | It is intended to be human readable and MUST NOT affect operation of the protocol. | | `5.24:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that performs the Termination-Action by sending a new Access-Request, a role ze does not implement: neither RADIUS path reads Termination-Action (attribute 29) and neither re-authenticates on session termination. The producer that would act as it if ze did is ze's RADIUS code, which is a client only: `Exchange` and `SendToServers` (`internal/component/radius/client.go`) send a request and match the reply, and `Authenticate` (`internal/component/radius/authenticator.go`) consumes the response. Nothing serves, proxies or re-authenticates. | If the NAS performs the Termination-Action by sending a new Access-Request upon termination of the current session, it MUST include the State attribute unchanged in that Access-Request. | | `5.24:3` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | State stating what Section 5.25 states for Class in the same words. RFC2865-5.25-1 names both sections and site 5.25:1 maps it. | In either usage, the client MUST NOT interpret the attribute locally. | | `5.26:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Vendor-Specific repeating the Filter-Id sentence. RFC2865-5.11-1 names Section 5.26 and site 5.11:1 maps it. | It MUST not affect the operation of the RADIUS protocol. | | `5.26:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. The sentence names its speaker: 'Servers not equipped to interpret the vendor-specific information sent by a client'. | Servers not equipped to interpret the vendor-specific information sent by a client MUST ignore it (although it may be reported). | | `5.32:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The NAS-Identifier section restating the Section 4.1 rule. RFC2865-4.1-2 carries it and site 4.1:3 maps it. | Either NAS-IP-Address or NAS-Identifier MUST be present in an Access-Request packet. | | `5.32:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. It forbids one input to the server's shared-secret choice, as site 5.4:2 does for NAS-IP-Address. | Note that NAS-Identifier MUST NOT be used to select the shared secret used to authenticate the request. | | `5.32:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS SERVER. Ze is a RADIUS CLIENT (a NAS): it sends Access-Requests and reads the reply. No file in the tree implements a RADIUS authentication server, and the only RADIUS listener ze runs is the RFC 5176 CoA/Disconnect port (internal/component/l2tp/plugins/authradius/coa.go), whose obligations belong to RFC 5176. It names the input the server must use instead. | The source IP address of the Access-Request packet MUST be used to select the shared secret. | | `5.33:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a PROXY SERVER adding Proxy-State and the server returning it. the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | This Attribute is available to be sent by a proxy server to another server when forwarding an Access-Request and MUST be returned unmodified in the Access-Accept, Access-Reject or Access-Challenge. | | `5.33:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a PROXY SERVER stripping its own Proxy-State from a response. the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | When the proxy server receives the response to its request, it MUST remove its own Proxy-State (the last Proxy- State in the packet) before forwarding the response to the NAS. | | `5.33:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds whichever speaker ADDS a Proxy-State when forwarding a packet. the FORWARDING SERVER of proxy RADIUS. Ze never forwards a RADIUS request: internal/component/radius/client.go sends its own Access-Requests and internal/component/l2tp/plugins/authradius does the same for subscribers. Ze adds no Proxy-State and reads none. | If a Proxy-State Attribute is added to a packet when forwarding the packet, the Proxy-State Attribute MUST be added after any existing Proxy-State attributes. | | `5.33:4` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Proxy-State repeating the Filter-Id sentence for the Proxy-States a speaker did not add. RFC2865-5.11-1 names Section 5.33 and site 5.11:1 maps it. | The content of any Proxy-State other than the one added by the current server should be treated as opaque octets and MUST NOT affect operation of the protocol. | | `5.44:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Note 1 of the Table of Attributes restating Section 4.1's credential rule. RFC2865-4.1-3 carries it and site 4.1:4 maps it. | [Note 1] An Access-Request MUST contain either a User-Password or a CHAP-Password or State. | | `5.44:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The second sentence of Note 1, restating Section 4.1's both-credentials prohibition. RFC2865-4.1-4 carries it and site 4.1:5 maps it. | An Access-Request MUST NOT contain both a User-Password and a CHAP-Password. | | `5.44:3` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Note 2 of the Table of Attributes restating Section 4.1's NAS identification rule. RFC2865-4.1-2 carries it and site 4.1:3 maps it. | [Note 2] An Access-Request MUST contain either a NAS-IP-Address or a NAS-Identifier (or both). | | `0:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The legend of the Section 5.44 Table of Attributes, which the section parser split into a section of its own because the line begins with '0'. It defines what a '0' cell means and binds no speaker: 'this attribute' names none. The obligations it expands to are the table's per-attribute rows, and both ze Access-Request builders send only attributes the Request column permits. | This attribute MUST NOT be present in packet. | | `9:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A Change Log entry describing a change made between RFC 2138 and this document. It quotes the Section 5 sentence at site 5:1, whose speaker is a proxy, and binds nobody itself. | If multiple Attributes with the same Type are present, the order of Attributes with the same Type MUST be preserved by any proxies. | ## Superseded No document obsoletes RFC 2865, so its obligations are stated where they were written. --- ### Page: RFC 2866 - RADIUS Accounting https://ze-software.net/quality/rfc-compliance/rfc2866/ # RFC 2866 - RADIUS Accounting Supported for subscriber access. Every requirement this repository extracted from RFC 2866, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 16 of 16 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 16 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 16 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 16 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 5.6% | 2 of 36 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 16 | of 18 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 16 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 16 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 16 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 16 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 16 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported for subscriber access | | Enrolment | Enrolled | | Requirements | 18 | | Gated MUST-level | 16 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 36 | | Tagged units | 36 | | Recorded audit verdicts | 0 | | Discrimination records | 2 | | Summary | `rfc/short/rfc2866.md` | | Requirement shard | `rfc/requirements/rfc2866.md` | | RFC text | `rfc/full/rfc2866.txt` | ## Enrolment Enrolled: ze is a RADIUS accounting client (NAS); the five gated MUST requirements the checklist held before 2026-08-31 are tested with positive+negative pairs. RFC2866-3-1 (MUST NOT tear down sessions on accounting failure): TestRFC2866AcctFailureKeepsSession positive (a failed Accounting-Start against an unreachable server leaves the session tracked), TestRFC2866SessionTeardownIndependentOfAccounting negative (teardown is driven only by the session-down event; producer authradius/acct.go:254-264 sendAcctPacket only logs on error, onSessionDown separate at acct.go:143-162). RFC2866-3-2 (authenticator = MD5(Code+ID+Length+16 zero octets+Attributes+Secret)): TestRFC2866AccountingRequestAuthFormula positive (equals an independent MD5 reference; producer radius/packet.go:252 AccountingRequestAuth, applied radius/client.go:127-132), TestRFC2866AccountingRequestAuthRejectsTampering negative (authenticator changes when secret/attribute/Code changes). RFC2866-3-3 (a retransmission whose contents are identical reuses the Identifier, and one whose Acct-Delay-Time was updated takes a new one): TestRFC2866AccountingRetransmitTakesANewIdentifier positive (ze stamps Acct-Delay-Time on an Accounting-Request by default, so its attributes move on every attempt and each attempt carries a distinct Identifier; producer radius/client.go Exchange, encodeRequest and setAcctDelayTime), TestRFC2866AccountingDistinctRequestsDifferIdentifier negative. The identical-contents half is proven on the Access-Request path by TestAccessRequestRetransmitIsByteIdentical, and on the accounting path by TestRFC2866AccountingRetransmitWithoutDelayTimeKeepsIdentifier, which is the record of an operator who wrote `attributes exclude acct-delay-time`: producer radius/client.go stampsAcctDelayTime, whose false branch re-sends the first buffer under its original Identifier. The delay value itself is proven by TestAcctDelayTimeUpdatesOnRetransmit. RFC2866-5-1 (Acct-Status-Type present Start/Stop/Interim): TestRFC2866AcctStatusTypePresent positive (each lifecycle event yields exactly one attribute valued 1/2/3; producer authradius/acct.go:196 buildAcctPacket), TestRFC2866AcctStatusTypeNeverOmitted negative. RFC2866-5.5-1 (Acct-Session-Id unique across the NAS): TestRFC2866AcctSessionIDUnique positive (1600 concurrent ids all distinct; producer authradius/acct.go:77-84 genSessionID monotonic counter under lock), TestRFC2866AcctSessionIDNoCollisionOnReusedKey negative. No SHOULD/MAY requirements are gated. The extraction walk of 2026-08-31 (rfc/extraction/rfc2866.json) added ten obligations the checklist never declared, each with a positive+negative pair: RFC2866-3-4 and RFC2866-3-5 (octets outside the Length are padding, a datagram shorter than its Length is discarded; producers radius/packet.go Decode and radius/client.go readLoop), RFC2866-4.1-2 (User-Password, CHAP-Password, Reply-Message and State never present), RFC2866-4.1-3 (either NAS-IP-Address or NAS-Identifier present; producer authradius/nasidentity.go appendNASIdentity, which fixed a config setting neither leaf), RFC2866-4.1-4 (a new request takes a new Identifier; producer radius/client.go SendToServers), RFC2866-4.2-1 (the Response Authenticator of an Accounting-Response; producer radius/client.go dispatchResponse), RFC2866-5-2 (embedded nulls survive an attribute value), RFC2866-5-3 (text of length zero omitted; producer authradius/acct.go buildAcctPacket, which fixed an empty User-Name reaching the wire), RFC2866-5.5-2 and RFC2866-5.5-3 (every record carries an Acct-Session-Id, and the records of one session carry the same one). ## What the public ledger says **Status:** Supported for subscriber access **What the ledger says is covered** 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). **What the ledger says remains:** Admin/operator RADIUS accounting is not wired; the admin backend is authentication-only. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 16 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **16** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (16):** [`RFC2866-3-1`](#rfc2866-3-1), [`RFC2866-3-2`](#rfc2866-3-2), [`RFC2866-5.5-1`](#rfc2866-5.5-1), [`RFC2866-4.1-1`](#rfc2866-4.1-1), [`RFC2866-5-1`](#rfc2866-5-1), [`RFC2866-3-3`](#rfc2866-3-3), [`RFC2866-3-4`](#rfc2866-3-4), [`RFC2866-3-5`](#rfc2866-3-5), [`RFC2866-4.1-2`](#rfc2866-4.1-2), [`RFC2866-4.1-3`](#rfc2866-4.1-3), [`RFC2866-4.1-4`](#rfc2866-4.1-4), [`RFC2866-4.2-1`](#rfc2866-4.2-1), [`RFC2866-5-2`](#rfc2866-5-2), [`RFC2866-5-3`](#rfc2866-5-3), [`RFC2866-5.5-2`](#rfc2866-5.5-2), [`RFC2866-5.5-3`](#rfc2866-5.5-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2866-3-1` | Accounting failures MUST NOT tear down user sessions (§3) | MUST NOT | 3 - Packet Format | **positive:** `unit/verify` [`TestRFC2866AcctFailureKeepsSession`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L156). **negative:** `unit/verify` [`TestRFC2866SessionTeardownIndependentOfAccounting`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L192) | | `RFC2866-3-2` | Accounting-Request authenticator MUST be computed as MD5(Code+ID+Length+16_zero_octets+Attributes+Secret) (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestRFC2866AccountingRequestAuthFormula`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_accounting_test.go#L84). **negative:** `unit/verify` [`TestRFC2866AccountingRequestAuthRejectsTampering`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_accounting_test.go#L127) | | `RFC2866-5.5-1` | Acct-Session-Id MUST be unique across all active sessions on the NAS (§5.5) | MUST | 5.5 - Acct-Session-Id | **positive:** `unit/verify` [`TestRFC2866AcctSessionIDUnique`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L112). **negative:** `unit/verify` [`TestRFC2866AcctSessionIDNoCollisionOnReusedKey`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L144) | | `RFC2866-4.1-1` | A Framed-IP-Address included in an Accounting-Request MUST contain the IP address of the user, and where the Access-Accept used a special value telling the NAS to assign or negotiate an address, MUST contain the address actually assigned or negotiated (§4.1) | MUST | 4.1 - Accounting-Request | **positive:** `unit/verify` [`TestAcctFramedIPAddressPresent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_address_test.go#L32). **positive:** `unit/verify` [`TestSessionEventDrivesAddressAndPortID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_address_test.go#L127). **negative:** `unit/verify` [`TestAcctFramedIPAddressIsSubscriberNotNAS`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_address_test.go#L56). **negative:** `unit/verify` [`TestAcctFramedIPAddressOmittedWhenNotIPv4`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_address_test.go#L79). **positive:** `functional/verify` [`radius-acct-wire.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/radius-acct-wire.ci#L40) | | `RFC2866-5-1` | Acct-Status-Type attribute MUST be included in Accounting-Request to indicate Start (1), Stop (2), or Interim-Update (3) (§5) | MUST | 5 - Attributes | **positive:** `unit/verify` [`TestRFC2866AcctStatusTypePresent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L57). **negative:** `unit/verify` [`TestRFC2866AcctStatusTypeNeverOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L88) | | `RFC2866-3-3` | Same retransmit rules as RFC 2865: "For retransmissions where the contents are identical, the Identifier MUST remain unchanged", so an Accounting-Request carrying Acct-Delay-Time, whose value "will be updated when the packet is retransmitted", takes a new Identifier and Request Authenticator instead (§3, stated at §4.1) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestRFC2866AccountingRetransmitTakesANewIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_accounting_test.go#L167). **positive:** `unit/verify` [`TestRFC2866AccountingRetransmitWithoutDelayTimeKeepsIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/acct_delay_time_omit_test.go#L120). **negative:** `unit/verify` [`TestRFC2866AccountingDistinctRequestsDifferIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_accounting_test.go#L225) | | `RFC2866-3-4` | Octets outside the range of the Length field MUST be treated as padding and ignored on reception (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestRFC2866LengthPaddingIgnoredOnReception`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L112). **negative:** `unit/verify` [`TestRFC2866LengthPaddingBoundaryIsTheLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L133) | | `RFC2866-3-5` | A packet shorter than its Length field indicates MUST be silently discarded (§3) | MUST | 3 - Packet Format | **positive:** `unit/verify` [`TestRFC2866ShortPacketSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L164). **negative:** `unit/verify` [`TestRFC2866HonestLengthIsAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L181) | | `RFC2866-4.1-2` | User-Password, CHAP-Password, Reply-Message and State MUST NOT be present in an Accounting-Request (§4.1) | MUST NOT | 4.1 - Accounting-Request | **positive:** `unit/verify` [`TestRFC2866AcctForbiddenAttributesAbsent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L37). **negative:** `unit/verify` [`TestRFC2866AcctForbiddenAttributesDoNotEmptyTheRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L67) | | `RFC2866-4.1-3` | Either NAS-IP-Address or NAS-Identifier MUST be present in an Accounting-Request (§4.1, restated at §5.13 Note 1) | MUST | 4.1 - Accounting-Request | **positive:** `unit/verify` [`TestRFC2866AcctNASIdentityAlwaysPresent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L90). **negative:** `unit/verify` [`TestRFC2866AcctNASIdentityFallbackIsNarrow`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L126) | | `RFC2866-4.1-4` | The Identifier MUST change whenever the content of the Attributes field changes, and whenever a valid reply has been received for a previous request (§4.1) | MUST | 4.1 - Accounting-Request | **positive:** `unit/verify` [`TestRFC2866IdentifierChangesForANewRequest`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L193). **negative:** `unit/verify` [`TestRFC2866IdentifierCounterCoversTheWholeSpace`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L234) | | `RFC2866-4.2-1` | The Response Authenticator of an Accounting-Response MUST contain the correct response for the pending Accounting-Request (§4.2) | MUST | 4.2 - Accounting-Response | **positive:** `unit/verify` [`TestRFC2866AccountingResponseAuthenticatorAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L260). **negative:** `unit/verify` [`TestRFC2866AccountingResponseAuthenticatorForgeryDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L272) | | `RFC2866-5-2` | Servers and clients MUST be able to deal with embedded nulls in an attribute value (§5) | MUST | 5 - Attributes | **positive:** `unit/verify` [`TestRFC2866EmbeddedNullsSurviveTheWire`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L288). **negative:** `unit/verify` [`TestRFC2866AllNullValueKeepsItsLength`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L313) | | `RFC2866-5-3` | Text of length zero MUST NOT be sent; the entire attribute is omitted instead (§5) | MUST NOT | 5 - Attributes | **positive:** `unit/verify` [`TestRFC2866AcctZeroLengthTextOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L148). **negative:** `unit/verify` [`TestRFC2866AcctNonEmptyTextIsSent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L168) | | `RFC2866-5.5-2` | The start and stop records for a given session MUST have the same Acct-Session-Id (§5.5) | MUST | 5.5 - Acct-Session-Id | **positive:** `unit/verify` [`TestRFC2866AcctSessionIDSameAcrossRecords`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L181). **negative:** `unit/verify` [`TestRFC2866AcctSessionIDDiffersBetweenSessions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L200) | | `RFC2866-5.5-3` | An Accounting-Request packet MUST have an Acct-Session-Id (§5.5) | MUST | 5.5 - Acct-Session-Id | **positive:** `unit/verify` [`TestRFC2866AcctSessionIDPresentOnEveryRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L216). **negative:** `unit/verify` [`TestRFC2866AcctSessionIDNeverEmpty`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L236) | | `RFC2866-x-1` | NAS SHOULD use exponential backoff between retransmits (per RFC 2865 §2.5) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC2866-x-2` | Interim-Update interval MAY be locally configured (Implementation Constraints) | MAY | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 2866 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2866-3-1`](#rfc2866-3-1) Accounting failures MUST NOT tear down user sessions (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866SessionTeardownIndependentOfAccounting`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L192) | unit/verify | unproven | | positive | [`TestRFC2866AcctFailureKeepsSession`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L156) | unit/verify | unproven | ### [`RFC2866-3-2`](#rfc2866-3-2) Accounting-Request authenticator MUST be computed as MD5(Code+ID+Length+16_zero_octets+Attributes+Secret) (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AccountingRequestAuthRejectsTampering`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_accounting_test.go#L127) | unit/verify | unproven | | positive | [`TestRFC2866AccountingRequestAuthFormula`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_accounting_test.go#L84) | unit/verify | unproven | ### [`RFC2866-5.5-1`](#rfc2866-5.5-1) Acct-Session-Id MUST be unique across all active sessions on the NAS (§5.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AcctSessionIDNoCollisionOnReusedKey`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L144) | unit/verify | unproven | | positive | [`TestRFC2866AcctSessionIDUnique`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L112) | unit/verify | unproven | ### [`RFC2866-4.1-1`](#rfc2866-4.1-1) A Framed-IP-Address included in an Accounting-Request MUST contain the IP address of the user, and where the Access-Accept used a special value telling the NAS to assign or negotiate an address, MUST contain the address actually assigned or negotiated (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAcctFramedIPAddressIsSubscriberNotNAS`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_address_test.go#L56) | unit/verify | unproven | | negative | [`TestAcctFramedIPAddressOmittedWhenNotIPv4`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_address_test.go#L79) | unit/verify | unproven | | positive | [`TestAcctFramedIPAddressPresent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_address_test.go#L32) | unit/verify | unproven | | positive | [`TestSessionEventDrivesAddressAndPortID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_address_test.go#L127) | unit/verify | unproven | | positive | [`radius-acct-wire.ci`](https://github.com/ze-software/ze/blob/main/test/l2tp/radius-acct-wire.ci#L40) | functional/verify | unproven | ### [`RFC2866-5-1`](#rfc2866-5-1) Acct-Status-Type attribute MUST be included in Accounting-Request to indicate Start (1), Stop (2), or Interim-Update (3) (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AcctStatusTypeNeverOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L88) | unit/verify | unproven | | positive | [`TestRFC2866AcctStatusTypePresent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_accounting_test.go#L57) | unit/verify | unproven | ### [`RFC2866-3-3`](#rfc2866-3-3) Same retransmit rules as RFC 2865: "For retransmissions where the contents are identical, the Identifier MUST remain unchanged", so an Accounting-Request carrying Acct-Delay-Time, whose value "will be updated when the packet is retransmitted", takes a new Identifier and Request Authenticator instead (§3, stated at §4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AccountingDistinctRequestsDifferIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_accounting_test.go#L225) | unit/verify | unproven | | positive | [`TestRFC2866AccountingRetransmitWithoutDelayTimeKeepsIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/acct_delay_time_omit_test.go#L120) | unit/verify | revert, verified | | positive | [`TestRFC2866AccountingRetransmitTakesANewIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_accounting_test.go#L167) | unit/verify | revert, verified | ### [`RFC2866-3-4`](#rfc2866-3-4) Octets outside the range of the Length field MUST be treated as padding and ignored on reception (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866LengthPaddingBoundaryIsTheLengthField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L133) | unit/verify | unproven | | positive | [`TestRFC2866LengthPaddingIgnoredOnReception`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L112) | unit/verify | unproven | ### [`RFC2866-3-5`](#rfc2866-3-5) A packet shorter than its Length field indicates MUST be silently discarded (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866HonestLengthIsAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L181) | unit/verify | unproven | | positive | [`TestRFC2866ShortPacketSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L164) | unit/verify | unproven | ### [`RFC2866-4.1-2`](#rfc2866-4.1-2) User-Password, CHAP-Password, Reply-Message and State MUST NOT be present in an Accounting-Request (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AcctForbiddenAttributesDoNotEmptyTheRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L67) | unit/verify | unproven | | positive | [`TestRFC2866AcctForbiddenAttributesAbsent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L37) | unit/verify | unproven | ### [`RFC2866-4.1-3`](#rfc2866-4.1-3) Either NAS-IP-Address or NAS-Identifier MUST be present in an Accounting-Request (§4.1, restated at §5.13 Note 1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AcctNASIdentityFallbackIsNarrow`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L126) | unit/verify | unproven | | positive | [`TestRFC2866AcctNASIdentityAlwaysPresent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L90) | unit/verify | unproven | ### [`RFC2866-4.1-4`](#rfc2866-4.1-4) The Identifier MUST change whenever the content of the Attributes field changes, and whenever a valid reply has been received for a previous request (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866IdentifierCounterCoversTheWholeSpace`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L234) | unit/verify | unproven | | positive | [`TestRFC2866IdentifierChangesForANewRequest`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L193) | unit/verify | unproven | ### [`RFC2866-4.2-1`](#rfc2866-4.2-1) The Response Authenticator of an Accounting-Response MUST contain the correct response for the pending Accounting-Request (§4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AccountingResponseAuthenticatorForgeryDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L272) | unit/verify | unproven | | positive | [`TestRFC2866AccountingResponseAuthenticatorAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L260) | unit/verify | unproven | ### [`RFC2866-5-2`](#rfc2866-5-2) Servers and clients MUST be able to deal with embedded nulls in an attribute value (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AllNullValueKeepsItsLength`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L313) | unit/verify | unproven | | positive | [`TestRFC2866EmbeddedNullsSurviveTheWire`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2866_packet_test.go#L288) | unit/verify | unproven | ### [`RFC2866-5-3`](#rfc2866-5-3) Text of length zero MUST NOT be sent; the entire attribute is omitted instead (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AcctNonEmptyTextIsSent`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L168) | unit/verify | unproven | | positive | [`TestRFC2866AcctZeroLengthTextOmitted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L148) | unit/verify | unproven | ### [`RFC2866-5.5-2`](#rfc2866-5.5-2) The start and stop records for a given session MUST have the same Acct-Session-Id (§5.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AcctSessionIDDiffersBetweenSessions`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L200) | unit/verify | unproven | | positive | [`TestRFC2866AcctSessionIDSameAcrossRecords`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L181) | unit/verify | unproven | ### [`RFC2866-5.5-3`](#rfc2866-5.5-3) An Accounting-Request packet MUST have an Acct-Session-Id (§5.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2866AcctSessionIDNeverEmpty`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L236) | unit/verify | unproven | | positive | [`TestRFC2866AcctSessionIDPresentOnEveryRequest`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2866_request_contents_test.go#L216) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6, rfc2866 | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc2866.txt | | Source fingerprint | 928686054877a829 | | Record | rfc/extraction/rfc2866.json | | Mapped sentences | 14 | | Declined as scope | 8 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, copyright notice, Abstract, the Implementation Note on UDP port 1646 against 1813, and the table of contents. The Implementation Note records deployment history and binds no speaker. | | `1` | Introduction | 0 | walked | Introduction. Names the problem RADIUS Accounting answers, and lists the key features: the client/server model, the shared secret that is never sent over the network, and the variable-length Attribute-Length-Value 3-tuples. No obligation. | | `1.1` | Specification of Requirements | 0 | walked | Specification of Requirements. The RFC 2119 key-words paragraph, plus one sentence of its own: 'These key words mean the same thing whether capitalized or not.' That sentence is why this walk reads a lowercase modal as normative wherever it binds a speaker, and it is why site 2.1:1 counts although the source writes 'MUST not'. | | `1.2` | Terminology | 0 | walked | Terminology. Defines service, session and 'silently discard'. The 'silently discard' entry carries two SHOULD clauses, on logging the error and on counting the event; neither is gated and neither is declared in rfc/short/rfc2866.md. | | `2` | Operation | 1 | walked | Operation. Describes the Start and Stop exchange, recommends that the client retry with some form of backoff and can fall back to an alternate server, and states that retry and fallback algorithms are not specified in detail in this document. One capitalised site, binding the accounting server. RFC2866-3-1 is declared unsourced here: RFC 2866 nowhere states that an accounting failure must not tear a session down. The summary read it from this section's model, in which accounting is an exchange beside the session rather than a step of it, and ze meets it (sendAcctPacket logs a failed record and returns; onSessionDown is driven by the session-down event alone). | | `2.1` | Proxy | 1 | walked | Proxy. Walks the four steps of a forwarding server and a remote server, and states one obligation binding the forwarding server. The rest is advice to whoever implements a proxy that takes responsibility for retransmissions. | | `3` | Packet Format | 2 | walked | Packet Format. States the encapsulation, UDP port 1813, the field layout, and both authenticator computations in RFC 2866's own voice; it does not defer to RFC 2865. The two capitalised sites are the Length rules. The Request Authenticator formula is stated in indicative prose ('contains a one-way MD5 hash calculated over a stream of octets consisting of the Code + Identifier + Length + 16 zero octets + request attributes + shared secret'), so the site scan sees no keyword there and RFC2866-3-2 is declared unsourced. The Response Authenticator paragraph is indicative in the same way; RFC2866-4.2-1 is instead read from the capitalised sentence in Section 4.2. The section closes with one SHOULD on preserving the order of attributes of the same type. | | `4` | Packet Types | 0 | walked | Packet Types. One sentence: the Code field in the first octet decides the packet type. | | `4.1` | Accounting-Request | 7 | walked | Accounting-Request. The description, the packet diagram, and the field notes for Code, Identifier, Request Authenticator and Attributes. Seven capitalised sites: one binds the accounting server, and six bind the client that builds the record, which is the role ze plays. | | `4.2` | Accounting-Response | 2 | walked | Accounting-Response. Two capitalised sites: the server transmits the response when it recorded the request, and the Response Authenticator holds the correct response for the pending Accounting-Request, which the client checks on reception. | | `5` | Attributes | 4 | walked | Attributes. The attribute format, the type list for 40 to 51, the Length rule, and the five data types. RFC 2866 states this section in its own voice: unlike RFC 2869 Section 5, it carries no sentence saying the format is included from RFC 2865 for ease of reference, so its four capitalised sites are read as RFC 2866's own obligations rather than as citations. | | `5.1` | Acct-Status-Type | 0 | walked | Acct-Status-Type. Defines type 40 and its values: Start, Stop, Interim-Update, Accounting-On, Accounting-Off, the tunnel range and Failed. One MAY, on marking the start and the end of accounting with Accounting-On and Accounting-Off; ze sends neither, and the option is the owner's to take (ai/rules/rfc-compliance.md). No capitalised MUST-level keyword. | | `5.2` | Acct-Delay-Time | 0 | walked | Acct-Delay-Time. Defines type 41, and notes that changing it changes the Attributes field and so requires a new Identifier and Request Authenticator. ze sends no Acct-Delay-Time. | | `5.3` | Acct-Input-Octets | 0 | walked | Acct-Input-Octets. Defines type 42 and says it can only be present where Acct-Status-Type is Stop. Indicative prose, no capitalised keyword. | | `5.4` | Acct-Output-Octets | 0 | walked | Acct-Output-Octets. Defines type 43 in the wording of Section 5.3 with the direction reversed. Indicative prose, no capitalised keyword. | | `5.5` | Acct-Session-Id | 3 | walked | Acct-Session-Id. Defines type 44, states three capitalised obligations, and adds two SHOULDs on UTF-8 encoding. RFC2866-5.5-1 is declared unsourced here: 'unique across all active sessions on the NAS' is the summary's rendering of the indicative opening sentence, 'This attribute is a unique Accounting ID to make it easy to match start and stop records in a log file', which carries no keyword for the site scan to see. genSessionID (authradius/acct.go) is its producer. | | `5.6` | Acct-Authentic | 0 | walked | Acct-Authentic. Defines type 45, how the user was authenticated. Carries one MAY on including it and one SHOULD NOT: 'Users who are delivered service without being authenticated SHOULD NOT generate Accounting records.' Neither is gated and ze sends no Acct-Authentic. The SHOULD NOT is the RFC's own acknowledgement that an unauthenticated session reaches accounting, which is the session whose empty User-Name site 5:3 governs. | | `5.7` | Acct-Session-Time | 0 | walked | Acct-Session-Time. Defines type 46. Indicative prose, no capitalised keyword. | | `5.8` | Acct-Input-Packets | 0 | walked | Acct-Input-Packets. Defines type 47. Indicative prose, no capitalised keyword. | | `5.9` | Acct-Output-Packets | 0 | walked | Acct-Output-Packets. Defines type 48. Indicative prose, no capitalised keyword. | | `5.10` | Acct-Terminate-Cause | 0 | walked | Acct-Terminate-Cause. Defines type 49 and its eighteen values, each with a one-line meaning. ze sends no Acct-Terminate-Cause. No obligation. | | `5.11` | Acct-Multi-Session-Id | 0 | walked | Acct-Multi-Session-Id. Defines type 50 for linking the sessions of one multilink bundle. 'It is strongly recommended that' the value is UTF-8, which is advice and not a keyword. ze sends no Acct-Multi-Session-Id. | | `5.12` | Acct-Link-Count | 0 | walked | Acct-Link-Count. Defines type 51, carries one MAY on including it in an Accounting-Request that might have multiple links, and works an eight-record example. ze runs no multilink bundle and sends no Acct-Link-Count. | | `5.13` | Table of Attributes | 2 | walked | Table of Attributes. The table itself, Note 1, and the legend that defines the 0, 0+, 0-1 and 1 symbols. The two capitalised sites are Note 1 and the legend. | | `6` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Registers the packet type codes, attribute types and attribute values from the RADIUS name spaces of RFC 2865, under BCP 26. Binds IANA, not a speaker. | | `7` | Security Considerations | 0 | walked | Security Considerations. One sentence: the security issues are discussed in the sections about the authenticator and the shared secret. No obligation of its own. | | `8` | Change Log | 0 | skipped (appendix-non-normative) | Change Log. Section 1 calls it an appendix: it lists what changed against RFC 2139, in the past tense. Its 'must' in 'it must be used in the accounting-request for that session' is a report of the change site 5.5:3 carries, not a second obligation. | | `9` | not stated | 0 | skipped (references) | References: RFC 2139, RFC 2865, RFC 2119, RFC 768, RFC 1321, RFC 1700, RFC 2279 and RFC 2434. | | `10` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. Credits Livingston Enterprises for the original RADIUS Accounting work. | | `11` | Chair's Address | 0 | skipped (front-matter) | Chair's Address. A postal, phone and mail address block, skipped the way rfc/extraction/rfc2869.json skips the same section. | | `12` | Author's Address | 0 | skipped (front-matter) | Author's Address. An address block only. | | `13` | Full Copyright Statement | 0 | skipped (front-matter) | Full Copyright Statement. The ISOC boilerplate, its disclaimer, and the RFC Editor funding acknowledgement. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS accounting server, the party that records the packet. ze runs no accounting server: radius/client.go opens a UDP socket to send requests and match the replies to them, and the one listener ze does run (authradius/coa.go) accepts a CoA-Request and a Disconnect-Request under RFC 5176, never an Accounting-Request. | If the RADIUS accounting server is unable to successfully record the accounting packet it MUST NOT send an Accounting-Response acknowledgment to the client. | | `2.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a forwarding server: the party that, in the four steps above the sentence, logs an accounting-request, adds its own Proxy-State after any other, updates the Request Authenticator and forwards the request to a remote server. ze proxies no RADIUS. buildAcctPacket (authradius/acct.go) builds the records of ze's own subscriber sessions and adds neither Proxy-State nor Class, and SendToServers (radius/client.go) sends them to the servers the operator configured. | A forwarding server MUST not modify existing Proxy-State or Class attributes present in the packet. | | `4.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS accounting server, the party that records the packet. ze runs no accounting server: radius/client.go opens a UDP socket to send requests and match the replies to them, and the one listener ze does run (authradius/coa.go) accepts a CoA-Request and a Disconnect-Request under RFC 5176, never an Accounting-Request. | Upon receipt of an Accounting-Request, the server MUST transmit an Accounting-Response reply if it successfully records the accounting packet, and MUST NOT transmit any reply if it fails to record the accounting packet. | | `4.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS accounting server, the party that records the packet. ze runs no accounting server: radius/client.go opens a UDP socket to send requests and match the replies to them, and the one listener ze does run (authradius/coa.go) accepts a CoA-Request and a Disconnect-Request under RFC 5176, never an Accounting-Request. | If the Accounting- Request was recorded successfully then the RADIUS accounting server MUST transmit a packet with the Code field set to 5 (Accounting-Response). | | `5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The sentence scopes itself to an attribute 'received in an Accounting-Request', and only the accounting server receives one. Binds the RADIUS accounting server, the party that records the packet. ze runs no accounting server: radius/client.go opens a UDP socket to send requests and match the replies to them, and the one listener ze does run (authradius/coa.go) accepts a CoA-Request and a Disconnect-Request under RFC 5176, never an Accounting-Request. Decode (radius/packet.go) does refuse an attribute whose Length is below 2 or runs past the packet, for every code it parses, so the rule holds wherever ze could reach it. | If an attribute is received in an Accounting-Request with an invalid Length, the entire request MUST be silently discarded. | | `5:4` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The same obligation for the string data type that the sentence before it states for text; the two differ only in the type they name, and site 5:3 maps it. The producer is the same and no attribute ze builds is a zero-length string: an Accounting-Request from ze carries string-typed values only in NAS-Port-Id, appended only when the template resolved to text, and in the NAS identity, which appendNASIdentity never leaves empty. | Strings of length zero (0) MUST NOT be sent; omit the entire attribute instead. | | `5.5:3` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The enclosing construction is 'An Access-Request packet MAY have an Acct-Session-Id; if it does, then the NAS MUST use the same Acct-Session-Id in the Accounting-Request packets for that session.' The MUST is conditioned on the MAY, and ze declines the option: buildAuthAttrs (authradius/handler.go) puts no Acct-Session-Id in an Access-Request, and the id does not exist yet at that point, because onSessionIPAssigned generates it when IPCP assigns the address (authradius/acct.go). Whether ze should take the option is a MAY for the owner to answer (ai/rules/rfc-compliance.md), not a requirement this walk can close. | An Access-Request packet MAY have an Acct-Session-Id; if it does, then the NAS MUST use the same Acct-Session-Id in the Accounting- Request packets for that session. | | `5.13:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Note 1 restates over the Table of Attributes the NAS identity rule Section 4.1 already states; site 4.1:3 maps it, and appendNASIdentity (authradius/nasidentity.go) is the one producer both sentences describe. | [Note 1] An Accounting-Request MUST contain either a NAS-IP-Address or a NAS-Identifier (or both). | ## Superseded No document obsoletes RFC 2866, so its obligations are stated where they were written. --- ### Page: RFC 2869 - RADIUS Extensions https://ze-software.net/quality/rfc-compliance/rfc2869/ # RFC 2869 - RADIUS Extensions Supported for subscriber access. Every requirement this repository extracted from RFC 2869, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 72.7% | 8 of 11 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 9.1% | 1 of 11 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 11 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 11 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 17 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 11 | of 14 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 2 | of 11 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 18.2% | 2 of 11 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 11 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 11 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 11 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported for subscriber access | | Enrolment | Enrolled | | Requirements | 14 | | Gated MUST-level | 11 | | Not applicable, so out of scope | 2 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 17 | | Tagged units | 17 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2869.md` | | Requirement shard | `rfc/requirements/rfc2869.md` | | RFC text | `rfc/full/rfc2869.txt` | ## Enrolment Enrolled: RADIUS Extensions (NAS/accounting-client obligations): five MUST-level requirements. x-2 (Gigawords present only for Stop/Interim) and x-3 (Gigawords present only when the counter is non-zero) are met with positive+negative tags on the accounting builder tests (internal/component/l2tp/plugins/authradius/acct.go). x-5 (NAS derives Gigawords from the actual 64-bit byte count) is {single-polarity: positive} bound to TestSplitGigawords. x-1 (server handles missing Gigawords) and x-4 (reconstruct total from received Gigawords) are {not-applicable}: ze runs only the RADIUS accounting client (NAS) role and has no accounting-server receive path (internal/component/radius/client.go binds only to receive responses to its own requests). ## What the public ledger says **Status:** Supported for subscriber access **What the ledger says is covered:** Gigaword counters and selected accounting extensions. **What the ledger says remains:** Scoped to subscriber access. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 8 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **11** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (8):** [`RFC2869-1.1-1`](#rfc2869-1.1-1), [`RFC2869-1.1-2`](#rfc2869-1.1-2), [`RFC2869-2.1-1`](#rfc2869-2.1-1), [`RFC2869-2.1-2`](#rfc2869-2.1-2), [`RFC2869-5.19-1`](#rfc2869-5.19-1), [`RFC2869-x-2`](#rfc2869-x-2), [`RFC2869-x-3`](#rfc2869-x-3), [`RFC2869-5.14-1`](#rfc2869-5.14-1) **Annotated instead of tested (3):** [`RFC2869-x-1`](#rfc2869-x-1), [`RFC2869-x-4`](#rfc2869-x-4), [`RFC2869-x-5`](#rfc2869-x-5) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2869-1.1-1` | A NAS that does not implement a given service MUST NOT implement the RADIUS attributes for that service (§1.1) | MUST NOT | 1.1 - Specification of Requirements | **positive:** `unit/verify` [`TestRFC2869DictionaryCoversTheServicesZeOffers`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L108). **negative:** `unit/verify` [`TestRFC2869DictionaryDeclaresNoAttributeForAnUnofferedService`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L144) | | `RFC2869-1.1-2` | A NAS MUST treat a RADIUS access-request requesting an unavailable service as an access-reject instead (§1.1) | MUST | 1.1 - Specification of Requirements | **positive:** `unit/verify` [`TestRFC2869AccessRequestNamesTheServiceZeOffers`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_access_request_service_test.go#L57). **negative:** `unit/verify` [`TestRFC2869AccessRequestNeverRequestsAnUnavailableService`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_access_request_service_test.go#L99) | | `RFC2869-2.1-1` | A NAS MUST ensure that only a single generation of an interim Accounting message for a given session is present in the retransmission queue at any given time (§2.1) | MUST | 2.1 - RADIUS support for Interim Accounting Updates | **positive:** `unit/verify` [`TestRFC2869InterimLoopSendsOnItsInterval`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_interim_generation_test.go#L89). **negative:** `unit/verify` [`TestRFC2869InterimLoopKeepsOneGenerationOutstanding`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_interim_generation_test.go#L117) | | `RFC2869-2.1-2` | A locally configured interim interval value on the NAS MUST override the value found in an Access-Accept (§2.1) | MUST | 2.1 - RADIUS support for Interim Accounting Updates | **positive:** `unit/verify` [`TestRFC2869LocalAcctIntervalOverridesAccessAccept`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_interim_interval_precedence_test.go#L102). **negative:** `unit/verify` [`TestRFC2869AbsentAcctIntervalLeavesTheAccessAcceptInCharge`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_interim_interval_precedence_test.go#L126) | | `RFC2869-5.19-1` | An Access-Request that contains a User-Password, a CHAP-Password, an ARAP-Password or one or more EAP-Message attributes MUST NOT contain more than one type of those four attributes (§5.19 Note 1) | MUST NOT | 5.19 - Table of Attributes heading and its opening paragraph | **positive:** `unit/verify` [`TestRFC2869AccessRequestCarriesTheCredentialOfItsMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_access_request_service_test.go#L137). **negative:** `unit/verify` [`TestRFC2869AccessRequestCarriesOneKindOfCredential`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_access_request_service_test.go#L169) | | `RFC2869-x-1` | Accounting servers MUST handle the absence of Gigaword attributes for backward compatibility with RFC 2866-only implementations (Implementation Constraints) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs only the RADIUS accounting client (NAS) role; it has no accounting-server receive path (internal/component/radius/client.go binds only to receive responses to its own requests), so it never handles missing Gigawords on receipt | | `RFC2869-x-2` | Gigaword attributes MUST only be present in Accounting-Request records where Acct-Status-Type is Stop or Interim-Update (Presence Rules) | MUST | x | **positive:** `unit/verify` [`TestBuildAcctPacketGigawords`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_test.go#L297). **negative:** `unit/verify` [`TestRFC2869GigawordsAbsentOnStart`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_accounting_test.go#L21) | | `RFC2869-x-3` | Gigaword attributes MUST only be included when the counter value is non-zero (i.e., 32-bit octet counter has wrapped at least once) (Presence Rules) | MUST | x | **positive:** `unit/verify` [`TestBuildAcctPacketGigawords`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_test.go#L299). **negative:** `unit/verify` [`TestBuildAcctPacketWithCounters`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_test.go#L261) | | `RFC2869-x-4` | Total byte count MUST be reconstructed as (Gigawords * 2^32) + Octets (Gigaword Accounting) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs only the RADIUS accounting client (NAS) role; it has no accounting-server receive path (internal/component/radius/client.go binds only to receive responses to its own requests), so it never reconstructs a byte total from received Gigaword attributes | | `RFC2869-x-5` | NAS MUST compute Gigawords from the actual byte count, not independently track wrap events (Implementation Constraints) | MUST | x | **positive:** `unit/verify` [`TestSplitGigawords`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_test.go#L231). **negative:** no negative test. **{single-polarity}:** splitGigawords derives Gigawords directly from the 64-bit byte counter (internal/component/l2tp/plugins/authradius/acct.go) with no separate wrap counter; there is no reject path so no negative case exists | | `RFC2869-5.14-1` | A RADIUS client receiving an Access-Accept, Access-Reject or Access-Challenge with a Message-Authenticator attribute present MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent (§5.14) | MUST | 5.14 - Message-Authenticator | **positive:** `unit/verify` [`TestRFC2869AccessAcceptWithValidMessageAuthenticatorIsAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_message_authenticator_test.go#L153). **negative:** `unit/verify` [`TestRFC2869AccessAcceptWithWrongMessageAuthenticatorIsDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_message_authenticator_test.go#L185) | | `RFC2869-5.17-1` | Either NAS-Port or NAS-Port-Id SHOULD be present in an Access-Request packet, if the NAS differentiates among its ports (§5.17) | SHOULD | 5.17 - NAS-Port-Id | **positive:** no positive test. **negative:** no negative test | | `RFC2869-x-6` | State attribute (type 24) MAY be maintained between Access-Challenge and Access-Request (Other Attributes) | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC2869-x-7` | Acct-Interim-Interval attribute (type 85) MAY be used to configure seconds between Interim-Updates (Other Attributes) | MAY | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2869-x-1`](#rfc2869-x-1) Accounting servers MUST handle the absence of Gigaword attributes for backward compatibility with RFC 2866-only implementations (Implementation Constraints) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs only the RADIUS accounting client (NAS) role; it has no accounting-server receive path (internal/component/radius/client.go binds only to receive responses to its own requests), so it never handles missing Gigawords on receipt | | [`RFC2869-x-4`](#rfc2869-x-4) Total byte count MUST be reconstructed as (Gigawords * 2^32) + Octets (Gigaword Accounting) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs only the RADIUS accounting client (NAS) role; it has no accounting-server receive path (internal/component/radius/client.go binds only to receive responses to its own requests), so it never reconstructs a byte total from received Gigaword attributes | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2869-1.1-1`](#rfc2869-1.1-1) A NAS that does not implement a given service MUST NOT implement the RADIUS attributes for that service (§1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869DictionaryDeclaresNoAttributeForAnUnofferedService`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L144) | unit/verify | unproven | | positive | [`TestRFC2869DictionaryCoversTheServicesZeOffers`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L108) | unit/verify | unproven | ### [`RFC2869-1.1-2`](#rfc2869-1.1-2) A NAS MUST treat a RADIUS access-request requesting an unavailable service as an access-reject instead (§1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869AccessRequestNeverRequestsAnUnavailableService`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_access_request_service_test.go#L99) | unit/verify | unproven | | positive | [`TestRFC2869AccessRequestNamesTheServiceZeOffers`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_access_request_service_test.go#L57) | unit/verify | unproven | ### [`RFC2869-2.1-1`](#rfc2869-2.1-1) A NAS MUST ensure that only a single generation of an interim Accounting message for a given session is present in the retransmission queue at any given time (§2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869InterimLoopKeepsOneGenerationOutstanding`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_interim_generation_test.go#L117) | unit/verify | unproven | | positive | [`TestRFC2869InterimLoopSendsOnItsInterval`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_interim_generation_test.go#L89) | unit/verify | unproven | ### [`RFC2869-2.1-2`](#rfc2869-2.1-2) A locally configured interim interval value on the NAS MUST override the value found in an Access-Accept (§2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869AbsentAcctIntervalLeavesTheAccessAcceptInCharge`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_interim_interval_precedence_test.go#L126) | unit/verify | unproven | | positive | [`TestRFC2869LocalAcctIntervalOverridesAccessAccept`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_interim_interval_precedence_test.go#L102) | unit/verify | unproven | ### [`RFC2869-5.19-1`](#rfc2869-5.19-1) An Access-Request that contains a User-Password, a CHAP-Password, an ARAP-Password or one or more EAP-Message attributes MUST NOT contain more than one type of those four attributes (§5.19 Note 1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869AccessRequestCarriesOneKindOfCredential`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_access_request_service_test.go#L169) | unit/verify | unproven | | positive | [`TestRFC2869AccessRequestCarriesTheCredentialOfItsMethod`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_access_request_service_test.go#L137) | unit/verify | unproven | ### [`RFC2869-x-1`](#rfc2869-x-1) Accounting servers MUST handle the absence of Gigaword attributes for backward compatibility with RFC 2866-only implementations (Implementation Constraints) Audit verdict: not audited: no reader has judged these tests No test carries RFC2869-x-1, so no unit is bound to it. ### [`RFC2869-x-2`](#rfc2869-x-2) Gigaword attributes MUST only be present in Accounting-Request records where Acct-Status-Type is Stop or Interim-Update (Presence Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869GigawordsAbsentOnStart`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc2869_accounting_test.go#L21) | unit/verify | unproven | | positive | [`TestBuildAcctPacketGigawords`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_test.go#L297) | unit/verify | unproven | ### [`RFC2869-x-3`](#rfc2869-x-3) Gigaword attributes MUST only be included when the counter value is non-zero (i.e., 32-bit octet counter has wrapped at least once) (Presence Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildAcctPacketWithCounters`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_test.go#L261) | unit/verify | unproven | | positive | [`TestBuildAcctPacketGigawords`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_test.go#L299) | unit/verify | unproven | ### [`RFC2869-x-4`](#rfc2869-x-4) Total byte count MUST be reconstructed as (Gigawords * 2^32) + Octets (Gigaword Accounting) Audit verdict: not audited: no reader has judged these tests No test carries RFC2869-x-4, so no unit is bound to it. ### [`RFC2869-x-5`](#rfc2869-x-5) NAS MUST compute Gigawords from the actual byte count, not independently track wrap events (Implementation Constraints) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSplitGigawords`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/acct_test.go#L231) | unit/verify | unproven | ### [`RFC2869-5.14-1`](#rfc2869-5.14-1) A RADIUS client receiving an Access-Accept, Access-Reject or Access-Challenge with a Message-Authenticator attribute present MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent (§5.14) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869AccessAcceptWithWrongMessageAuthenticatorIsDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_message_authenticator_test.go#L185) | unit/verify | unproven | | positive | [`TestRFC2869AccessAcceptWithValidMessageAuthenticatorIsAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_message_authenticator_test.go#L153) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase 2, rfc2869 | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc2869.txt | | Source fingerprint | 5b468f2205956613 | | Record | rfc/extraction/rfc2869.json | | Mapped sentences | 6 | | Declined as scope | 38 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, copyright notice, Abstract and table of contents. The Abstract states what the memo adds to RADIUS and binds no speaker. | | `1` | Introduction | 0 | walked | Introduction. Says what RFC 2865 and RFC 2866 describe, that the new attributes are experimental, and that attributes are Type-Length-Value triples. No obligation. | | `1.1` | Specification of Requirements | 3 | walked | Specification of Requirements. The RFC 2119 key-words paragraph, the compliant / unconditionally compliant / conditionally compliant definitions, and one normative paragraph that scopes the document's other requirements to the services a NAS offers. The three sites below come from that last paragraph. | | `1.2` | Terminology | 0 | walked | Terminology. Defines service, session and 'silently discard'. The 'silently discard' entry carries two SHOULD clauses about logging the error and counting the event; neither is gated and neither is declared in rfc/short/rfc2869.md. | | `2` | Operation | 0 | walked | Operation. One sentence: operation is identical to RFC 2865 and RFC 2866. No obligation of its own. | | `2.1` | RADIUS support for Interim Accounting Updates | 2 | walked | RADIUS support for Interim Accounting Updates. Two capitalised MUSTs, both binding the NAS, both classified below. | | `2.2` | RADIUS support for Apple Remote Access Protocol | 1 | walked | RADIUS support for Apple Remote Access Protocol. Describes ARAP's two-way DES exchange and the ARAP attribute set. Ze implements no ARAP: internal/component/radius/dict.go declares no attribute in the 70-74 or 84 range, and internal/component/l2tp/ppp offers PAP, CHAP and MS-CHAPv2 only. | | `2.3` | RADIUS Support for Extensible Authentication Protocol | 0 | walked | RADIUS Support for Extensible Authentication Protocol. Introduces EAP-Message and Message-Authenticator. No capitalised keyword. | | `2.3.1` | Protocol Overview | 12 | walked | Protocol Overview. Twelve capitalised MUSTs describing an EAP conversation carried over RADIUS. Ze carries no EAP over RADIUS: internal/component/radius/dict.go declares no EAP-Message attribute (type 79), and internal/component/l2tp/ppp has no EAP module, so LCP never negotiates EAP. Ze's IKEv2 EAP (internal/core/eap) terminates EAP locally and imports no RADIUS package. | | `2.3.2` | Retransmission | 0 | walked | Retransmission. Says the NAS retransmits between peer and NAS and the RADIUS client retransmits between client and server, and that Session-Timeout in an Access-Challenge bounds the wait for an EAP-Response. Indicative prose, no capitalised keyword. | | `2.3.3` | Fragmentation | 0 | walked | Fragmentation. Says Framed-MTU may be included in an Access-Request carrying an EAP-Message. Permissive, no capitalised keyword. | | `2.3.4` | Examples | 0 | walked | Examples. Two message-sequence diagrams of an OTP authentication. Illustration only. | | `2.3.5` | Alternative uses | 1 | walked | Alternative uses. One capitalised MUST binding a RADIUS server that proxies RADIUS-encapsulated EAP to a backend security server. | | `3` | Packet Format | 0 | walked | Packet Format. One sentence: identical to RFC 2865 and RFC 2866. | | `4` | Packet Types | 0 | walked | Packet Types. Identical to RFC 2865 and RFC 2866, and points at the Table of Attributes. | | `5` | Attributes | 3 | walked | Attributes. The attribute type list and the five data types. The section states that 'A summary of the attribute format is the same as in RFC 2865 [1] but is included here for ease of reference', so its three capitalised MUSTs restate RFC 2865 Section 5. | | `5.1` | Acct-Input-Gigawords | 0 | walked | Acct-Input-Gigawords. Defines type 52 and says the attribute 'indicates how many times the Acct-Input-Octets counter has wrapped around 2^32 over the course of this service being provided, and can only be present in Accounting-Request records where the Acct-Status-Type is set to Stop or Interim-Update'. Indicative prose with no capitalised keyword, which is why the site scan sees nothing here. The five gated Gigawords rows in rfc/short/rfc2869.md were all read from this sentence and its identically worded twin in Section 5.2, so they are declared unsourced here. | | `5.2` | Acct-Output-Gigawords | 0 | walked | Acct-Output-Gigawords. Defines type 53 in wording identical to Section 5.1 with Acct-Output-Octets substituted. The five gated rows it shares with Section 5.1 are declared unsourced on Section 5.1 rather than twice. | | `5.3` | Event-Timestamp | 0 | walked | Event-Timestamp. Defines type 55 as the time the event occurred. No obligation. | | `5.4` | ARAP-Password | 0 | walked | ARAP-Password. Defines type 70. Ze implements no ARAP. | | `5.5` | ARAP-Features | 0 | walked | ARAP-Features. Defines type 71. Ze implements no ARAP. | | `5.6` | ARAP-Zone-Access | 0 | walked | ARAP-Zone-Access. Defines type 72. Ze implements no ARAP. | | `5.7` | ARAP-Security | 0 | walked | ARAP-Security. Defines type 73. Ze implements no ARAP. | | `5.8` | ARAP-Security-Data | 0 | walked | ARAP-Security-Data. Defines type 74. Ze implements no ARAP. | | `5.9` | Password-Retry | 0 | walked | Password-Retry. Defines type 75, how many attempts a user is allowed. Ze declares no such attribute constant. | | `5.10` | Prompt | 0 | walked | Prompt. Defines type 76, whether the NAS echoes the user's response. Ze declares no such attribute constant. | | `5.11` | Connect-Info | 0 | walked | Connect-Info. Defines type 77, the connect speed the NAS reports. Ze declares no such attribute constant. | | `5.12` | Configuration-Token | 0 | walked | Configuration-Token. Defines type 78, for a RADIUS proxy. Ze is no RADIUS proxy. | | `5.13` | EAP-Message | 6 | walked | EAP-Message. Defines type 79 and six capitalised MUSTs over its handling. Ze declares no EAP-Message attribute constant and sends and receives none. | | `5.14` | Message-Authenticator | 3 | walked | Message-Authenticator. Defines type 80, its two HMAC-MD5 formulas, and three capitalised MUSTs. The third binds a RADIUS client, which is the role Ze plays (internal/component/radius/client.go), and is captured as RFC2869-5.14-1. | | `5.15` | ARAP-Challenge-Response | 0 | walked | ARAP-Challenge-Response. Defines type 84. Ze implements no ARAP. | | `5.16` | Acct-Interim-Interval | 1 | walked | Acct-Interim-Interval. Defines type 85 and one capitalised MUST NOT over the value the sender puts in it. The section states the attribute 'can only appear in the Access-Accept message', so the obligation binds the RADIUS server. | | `5.17` | NAS-Port-Id | 0 | walked | NAS-Port-Id. Defines type 87 as UTF-8 text of length 3 or more naming the physical port. Its one obligation is a SHOULD read from indicative prose, which the site scan does not surface and which rfc/short/rfc2869.md declares as RFC2869-5.17-1. | | `5.18` | Framed-Pool | 0 | walked | Framed-Pool. Defines type 88, the address pool name. No obligation. | | `5.19` | Table of Attributes heading and its opening paragraph | 0 | walked | Table of Attributes heading and its opening paragraph. The table body, Note 1 and the notation legend fall outside the numbered-section split and are carried by the derived section '0'. | | `0` | not stated | 3 | walked | The tail of Section 5.19: the packet-versus-attribute table, Note 1, and the legend defining 0, 0+, 0-1 and 1. The section splitter cannot attribute this block to a numbered heading, so it derives as section '0'. | | `6` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Registers the packet type codes, attribute types and attribute values from the RADIUS name spaces per BCP 26. Binds IANA, not a speaker. | | `7` | Security Considerations | 0 | walked | Security Considerations. One sentence: the attributes other than Message-Authenticator and EAP-Message add nothing beyond RFC 2865. | | `7.1` | Message-Authenticator Security | 0 | walked | Message-Authenticator Security. Explains why an Access-Request without a User-Password should carry a Message-Authenticator. Lowercase 'should', no capitalised keyword. | | `7.2` | EAP Security | 0 | walked | EAP Security. Lists the five threats the subsections address. No obligation. | | `7.2.1` | Separation of EAP server and PPP authenticator | 0 | walked | Separation of EAP server and PPP authenticator. Describes key transport between the EAP server and the PPP authenticator. No capitalised keyword. | | `7.2.2` | Connection hijacking | 1 | walked | Connection hijacking. One capitalised MUST requiring every EAP/RADIUS packet to be authenticated with the Message-Authenticator attribute. | | `7.2.3` | Man in the middle attacks | 0 | walked | Man in the middle attacks. States that a compromised RADIUS proxy can modify EAP packets. No countermeasure is directed at a speaker. | | `7.2.4` | Multiple databases | 0 | walked | Multiple databases. Recommends consolidating the RADIUS and backend security databases. Advice to a deployer, no capitalised keyword. | | `7.2.5` | Negotiation attacks | 8 | walked | Negotiation attacks. Eight capitalised keywords across the authenticating peer, an EAP-capable NAS, and the RADIUS server or proxy. | | `8` | not stated | 0 | skipped (references) | References: RFC 2865, RFC 2866, RFC 2284, RFC 2119, RFC 1700, RFC 2868, RFC 2867, RFC 2279, RFC 2104, RFC 2434 and one informative citation. | | `9` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `10` | Chair's Address | 0 | skipped (front-matter) | Chair's Address. A postal and mail address block, matching how rfc/extraction/rfc3765.json skips its Author's Address section. | | `11` | Authors' Addresses | 0 | skipped (front-matter) | Authors' Addresses. Address blocks only. | | `12` | Full Copyright Statement | 0 | skipped (front-matter) | Full Copyright Statement. The ISOC boilerplate and its disclaimer. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1.1:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The sentence opens 'For example' and names ARAP as an instance of the rule site 1.1:1 states. It adds no obligation site 1.1:1 does not already carry. | For example, a NAS that is unable to offer ARAP service MUST NOT implement the RADIUS attributes for ARAP. | | `2.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. The sentence sits inside the list of attributes the server returns in an ARAP Access-Accept, and the actor is the party that computes ARAP-Challenge-Response by DES-encrypting the dial-in client's challenge with the user's password. Ze is a RADIUS client only (internal/component/radius/client.go opens a UDP socket to send requests and match replies) and implements no ARAP. | If the user's password is greater than 8 octets in length, an Access-Reject MUST be sent instead. | | `2.3.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that has negotiated EAP within PPP LCP. Ze never plays that role: internal/component/l2tp/ppp carries pap.go, chap.go and mschapv2.go and no EAP module, so LCP never offers EAP, and internal/component/radius/dict.go declares no EAP-Message attribute (type 79). Ze's IKEv2 EAP (internal/core/eap) is a separate protocol terminated locally and imports no RADIUS package. | Once EAP has been negotiated, the NAS MUST send an EAP-Request/Identity message to the authenticating peer, unless identity is determined via some other means such as Called-Station-Id or Calling-Station-Id. | | `2.3.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that has negotiated EAP within PPP LCP. Ze never plays that role: internal/component/l2tp/ppp carries pap.go, chap.go and mschapv2.go and no EAP module, so LCP never offers EAP, and internal/component/radius/dict.go declares no EAP-Message attribute (type 79). Ze's IKEv2 EAP (internal/core/eap) is a separate protocol terminated locally and imports no RADIUS package. | In order to permit non-EAP aware RADIUS proxies to forward the Access-Request packet, if the NAS sends the EAP-Request/Identity, the NAS MUST copy the contents of the EAP-Response/Identity into the User-Name attribute and MUST include the EAP-Response/Identity in the User-Name attribute in every subsequent Access-Request. | | `2.3.1:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that has negotiated EAP within PPP LCP. Ze never plays that role: internal/component/l2tp/ppp carries pap.go, chap.go and mschapv2.go and no EAP module, so LCP never offers EAP, and internal/component/radius/dict.go declares no EAP-Message attribute (type 79). Ze's IKEv2 EAP (internal/core/eap) is a separate protocol terminated locally and imports no RADIUS package. | NAS-Port or NAS-Port-Id SHOULD be included in the attributes issued by the NAS in the Access-Request packet, and either NAS-Identifier or NAS-IP- Address MUST be included. | | `2.3.1:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | In order to permit forwarding of the Access-Reply by EAP-unaware proxies, if a User-Name attribute was included in an Access-Request, the RADIUS Server MUST include the User-Name attribute in subsequent Access-Accept packets. | | `2.3.1:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that has negotiated EAP within PPP LCP. Ze never plays that role: internal/component/l2tp/ppp carries pap.go, chap.go and mschapv2.go and no EAP module, so LCP never offers EAP, and internal/component/radius/dict.go declares no EAP-Message attribute (type 79). Ze's IKEv2 EAP (internal/core/eap) is a separate protocol terminated locally and imports no RADIUS package. | If identity is determined via another means such as Called-Station-Id or Calling-Station-Id, the NAS MUST include these identifying attributes in every Access-Request. | | `2.3.1:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | If the RADIUS server supports EAP, it MUST respond with an Access- Challenge packet containing an EAP-Message attribute. | | `2.3.1:7` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | If the RADIUS server does not support EAP, it MUST respond with an Access-Reject. | | `2.3.1:8` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that has negotiated EAP within PPP LCP. Ze never plays that role: internal/component/l2tp/ppp carries pap.go, chap.go and mschapv2.go and no EAP module, so LCP never offers EAP, and internal/component/radius/dict.go declares no EAP-Message attribute (type 79). Ze's IKEv2 EAP (internal/core/eap) is a separate protocol terminated locally and imports no RADIUS package. | Reception of a RADIUS Access-Reject packet, with or without an EAP- Message attribute encapsulating EAP-Failure, MUST result in the NAS issuing an LCP Terminate Request to the authenticating peer. | | `2.3.1:9` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | The RADIUS Access-Accept/EAP-Message/EAP-Success packet MUST contain all of the expected attributes which are currently returned in an Access-Accept packet. | | `2.3.1:10` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | If the domain is determined based on the user's identity, the local RADIUS Server MUST respond with a RADIUS Access-Challenge/EAP-Identity packet. | | `2.3.1:11` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | The response from the authenticating peer MUST be proxied to the final authentication server. | | `2.3.1:12` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that has negotiated EAP within PPP LCP. Ze never plays that role: internal/component/l2tp/ppp carries pap.go, chap.go and mschapv2.go and no EAP module, so LCP never offers EAP, and internal/component/radius/dict.go declares no EAP-Message attribute (type 79). Ze's IKEv2 EAP (internal/core/eap) is a separate protocol terminated locally and imports no RADIUS package. | On receiving an Access-Reject, the NAS MUST send an LCP Terminate Request to the authenticating peer, and disconnect. | | `2.3.5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | This means that the RADIUS server MUST add these attributes prior to sending an Access-Accept/EAP-Success message to the NAS. | | `5:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | RFC 2865 Section 5, 'servers and clients MUST be able to deal with embedded nulls'. Section 5 states 'A summary of the attribute format is the same as in RFC 2865 [1] but is included here for ease of reference', and the sentence appears verbatim in RFC 2865 Section 5. The obligation belongs to RFC 2865, which carries its own summary at rfc/short/rfc2865.md. | Servers and servers and clients MUST be able to deal with embedded nulls. | | `5:2` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | RFC 2865 Section 5, 'Text of length zero (0) MUST NOT be sent; omit the entire attribute instead'. Section 5 states 'A summary of the attribute format is the same as in RFC 2865 [1] but is included here for ease of reference', and the sentence appears verbatim in RFC 2865 Section 5. The obligation belongs to RFC 2865, which carries its own summary at rfc/short/rfc2865.md. | Text of length zero (0) MUST NOT be sent; omit the entire attribute instead. string 1-253 octets containing binary data (values 0 through 255 decimal, inclusive). | | `5:3` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | RFC 2865 Section 5, 'Strings of length zero (0) MUST NOT be sent; omit the entire attribute instead'. Section 5 states 'A summary of the attribute format is the same as in RFC 2865 [1] but is included here for ease of reference', and the sentence appears verbatim in RFC 2865 Section 5. The obligation belongs to RFC 2865, which carries its own summary at rfc/short/rfc2865.md. | Strings of length zero (0) MUST NOT be sent; omit the entire attribute instead. | | `5.13:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a RADIUS speaker that puts EAP-Message attributes in a packet. internal/component/radius/dict.go declares no EAP-Message attribute (type 79) and no encoder emits one, so Ze never builds a packet the ordering rule can govern. | If multiple EAP- Messages are contained within an Access-Request or Access- Challenge packet, they MUST be in order and they MUST be consecutive attributes in the Access-Request or Access-Challenge packet. | | `5.13:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the sender of a packet carrying an EAP-Message attribute. Ze sends none: internal/component/radius/dict.go declares no type 79. | Therefore the Message-Authenticator attribute MUST be used to protect all Access-Request, Access-Challenge, Access-Accept, and Access-Reject packets containing an EAP-Message attribute. | | `5.13:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | A RADIUS Server supporting EAP-Message MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent. | | `5.13:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | A RADIUS Server not supporting EAP-Message MUST return an Access- Reject if it receives an Access-Request containing an EAP-Message attribute. | | `5.13:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | A RADIUS Server receiving an EAP-Message attribute that it does not understand MUST return an Access-Reject. | | `5.13:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds 'A NAS supporting EAP-Message'. Ze supports none: internal/component/radius/dict.go declares no type 79, so no Access-Challenge, Access-Accept or Access-Reject Ze reads can carry the attribute this rule conditions on. The unconditional client-side rule in the same section, site 5.14:3, is the one that binds Ze, and it is mapped. | A NAS supporting EAP-Message MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent. | | `5.14:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the sender of a packet that includes an EAP-Message attribute. Ze includes none in the Access-Request it builds (internal/component/l2tp/plugins/authradius/handler.go, buildAuthAttrs) and declares no type 79 in internal/component/radius/dict.go, so the condition never holds for a packet Ze sends. Ze does add a Message-Authenticator when reading one: the receive-side obligation is site 5.14:3. | It MUST be used in any Access-Request, Access-Accept, Access-Reject or Access- Challenge that includes an EAP-Message attribute. | | `5.14:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | A RADIUS Server receiving an Access-Request with a Message- Authenticator Attribute present MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent. | | `5.16:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Section 5.16 states the attribute 'can only appear in the Access-Accept message', and Section 5.19's table gives Acct-Interim-Interval 0-1 instances in an Accept and 0 in a Request, so the value the MUST NOT constrains is the one the server writes. Ze never sends the attribute; it reads it (internal/component/l2tp/plugins/authradius/extract.go) and clamps a received value into [60, 3600] (clampAcctInterval, acct.go), which is a defence against a non-conformant server rather than the obligation this sentence states. | The value MUST NOT be smaller than 60. | | `0:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the sender of any packet type carrying an EAP-Message attribute. Ze sends no EAP-Message: internal/component/radius/dict.go declares no type 79. | If any packet type contains an EAP-Message attribute it MUST also contain a Message-Authenticator. | | `0:3` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The legend of the Section 5.19 table, quoted from the four lines that define what the cells 0, 0+, 0-1 and 1 mean. The keywords describe the notation, not a speaker's behaviour; the obligations the table expresses are carried by the individual attribute sections and by Note 1. | 0 This attribute MUST NOT be present 0+ Zero or more instances of this attribute MAY be present. 0-1 Zero or one instance of this attribute MAY be present. 1 Exactly one instance of this attribute MUST be present. | | `7.2.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a speaker of EAP over RADIUS: the sentence's subject is 'all EAP/RADIUS packets'. Ze exchanges none, because internal/component/radius/dict.go declares no EAP-Message attribute and internal/component/l2tp/ppp never negotiates EAP. | In order to provide for authentication of all packets in the EAP exchange, all EAP/RADIUS packets MUST be authenticated using the Message-Authenticator attribute, as described previously. | | `7.2.5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the authenticating peer, the dial-in client at the far end of the PPP link. Ze is the NAS and LNS side (internal/component/l2tp), never the dial-in client. | Should the NAS not be able to negotiate EAP, or should the EAP-Request sent by the NAS be of a different EAP type than what is expected, the authenticating peer MUST disconnect. | | `7.2.5:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the authenticating peer, the dial-in client at the far end of the PPP link. Ze is the NAS and LNS side (internal/component/l2tp), never the dial-in client. | An authenticating peer expecting EAP to be negotiated for a session MUST NOT negotiate CHAP or PAP. | | `7.2.5:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS some of whose users are required to authenticate with EAP. Ze offers no EAP at all (internal/component/l2tp/ppp has no EAP module), so it has no such users. The first 'MUST' in the sentence is indicative ('if any users of the NAS MUST do EAP') and states the condition rather than an obligation. | In such cases, if any users of the NAS MUST do EAP, then the NAS MUST attempt to negotiate EAP for every call. | | `7.2.5:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | However, if CHAP has been negotiated but EAP is required, the RADIUS server MUST respond with an Access-Reject, rather than an Access- Challenge/EAP-Message/EAP-Request packet. | | `7.2.5:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the authenticating peer, the dial-in client at the far end of the PPP link. Ze is the NAS and LNS side (internal/component/l2tp), never the dial-in client. | The authenticating peer MUST refuse to renegotiate authentication, even if the renegotiation is from CHAP to EAP. | | `7.2.5:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS server. Ze runs no RADIUS server: internal/component/radius/client.go binds a socket only to receive replies to its own requests, and the one listener Ze does run (internal/component/l2tp/plugins/authradius/coa.go) accepts CoA-Request and Disconnect-Request under RFC 5176, never an Access-Request. | If EAP is negotiated but is not supported by the RADIUS proxy or server, then the server or proxy MUST respond with an Access-Reject. | | `7.2.5:7` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds a NAS that has negotiated EAP within PPP LCP. Ze never plays that role: internal/component/l2tp/ppp carries pap.go, chap.go and mschapv2.go and no EAP module, so LCP never offers EAP, and internal/component/radius/dict.go declares no EAP-Message attribute (type 79). Ze's IKEv2 EAP (internal/core/eap) is a separate protocol terminated locally and imports no RADIUS package. | In these cases, the NAS MUST send an LCP-Terminate and disconnect the user. | | `7.2.5:8` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the authenticating peer, the dial-in client at the far end of the PPP link. Ze is the NAS and LNS side (internal/component/l2tp), never the dial-in client. | An EAP-capable authenticating peer MUST refuse to renegotiate the authentication protocol if EAP had initially been negotiated. | ## Superseded No document obsoletes RFC 2869, so its obligations are stated where they were written. --- ### Page: RFC 2890 - Key and Sequence Number Extensions to GRE https://ze-software.net/quality/rfc-compliance/rfc2890/ # RFC 2890 - Key and Sequence Number Extensions to GRE No row in the public ledger. Every requirement this repository extracted from RFC 2890, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 7 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 7 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 7 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 7 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 7 | of 13 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 7 | of 7 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 7 of 7 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 7 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 7 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 7 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 13 | | Gated MUST-level | 7 | | Not applicable, so out of scope | 7 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2890.md` | | Requirement shard | `rfc/requirements/rfc2890.md` | | RFC text | `rfc/full/rfc2890.txt` | ## Enrolment Enrolled: Key and Sequence Number Extensions to GRE: seven MUST-level requirements, all {not-applicable}. ze builds and parses no GRE header: it configures kernel GRE tunnels via netlink (internal/plugins/iface/netlink/tunnel_linux.go buildGretun sets only IKey/OKey) and VPP tunnels via gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go), delegating all C/K/S flag construction, Key/Sequence field encoding, receiver ordering (OUTOFORDER_TIMER), and IPsec protection to the kernel/VPP dataplane. ze has no GRE header-construction, sequence, decapsulation, or receiver code path. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 2890. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 7 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **7** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (7):** [`RFC2890-2.1-1`](#rfc2890-2.1-1), [`RFC2890-2.1-2`](#rfc2890-2.1-2), [`RFC2890-2.2-1`](#rfc2890-2.2-1), [`RFC2890-2.2-2`](#rfc2890-2.2-2), [`RFC2890-2.2-3`](#rfc2890-2.2-3), [`RFC2890-2.2-4`](#rfc2890-2.2-4), [`RFC2890-3-1`](#rfc2890-3-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2890-2.1-1` | When K=1, the Key field MUST be present (4 octets) (S2.1) | MUST | 2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze builds no GRE header; the netlink backend sets only the tunnel IKey/OKey config fields (internal/plugins/iface/netlink/tunnel_linux.go:129) and the VPP backend calls gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:70), so the kernel/VPP dataplane constructs the K flag and Key octets, not ze | | `RFC2890-2.1-2` | When K=0, the Key field MUST NOT be present (S2.1) | MUST NOT | 2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze emits no GRE header bytes; the K-bit-and-Key-absence invariant is enforced by the kernel, which sets the flag only when IKey/OKey are non-zero (internal/plugins/iface/netlink/tunnel_linux.go:128-139); ze has no header-flag code path | | `RFC2890-2.2-1` | When S=1, the Sequence Number field MUST be present (S2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze exposes no GRE sequence-number configuration and constructs no Sequence Number field; grep for a sequence producer across the iface tunnel paths finds none | | `RFC2890-2.2-2` | When S=0, the Sequence Number field MUST NOT be present (S2.2) | MUST NOT | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no S-bit or Sequence Number code path; it configures kernel/VPP tunnels and builds no GRE header | | `RFC2890-2.2-3` | Sequence Number MUST be used by the receiver to establish packet order (S2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** GRE receive-side sequence ordering is performed by the kernel/VPP datapath; ze has no GRE decapsulation or packet-parse code path | | `RFC2890-2.2-4` | If a packet has waited longer than OUTOFORDER_TIMER milliseconds in the buffer, the receiver MUST immediately traverse the buffer in sorted order, decapsulating packets (S2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no GRE receiver buffer or OUTOFORDER_TIMER; it programs kernel/VPP tunnels and does not process GRE payloads | | `RFC2890-3-1` | IP security protocols (ESP or AH) MUST be used to protect the GRE header and tunneled payload when using Sequence Number (S3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze applies no ESP or AH to GRE; IPsec is not wired to the GRE tunnel builders (internal/plugins/iface/netlink/tunnel_linux.go, internal/plugins/iface/vpp/tunnel.go) | | `RFC2890-1.1-1` | When silently discarding, the implementation SHOULD provide the capability of logging the error (S1.1) | SHOULD | 1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2890-1.1-2` | When silently discarding, the implementation SHOULD record the event in a statistics counter (S1.1) | SHOULD | 1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC2890-2.2-5` | An out-of-sequence packet SHOULD be silently discarded (S2.2) | SHOULD | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2890-2.2-6` | The first packet's sequence number MAY be any value (S2.2) | MAY | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2890-2.2-7` | A receiver MAY discard out-of-order packets (S2.2) | MAY | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC2890-2.2-8` | Reordering of out-of-sequence packets MAY be performed by the decapsulator (S2.2) | MAY | 2.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2890-2.1-1`](#rfc2890-2.1-1) When K=1, the Key field MUST be present (4 octets) (S2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze builds no GRE header; the netlink backend sets only the tunnel IKey/OKey config fields (internal/plugins/iface/netlink/tunnel_linux.go:129) and the VPP backend calls gre_tunnel_add_del (internal/plugins/iface/vpp/tunnel.go:70), so the kernel/VPP dataplane constructs the K flag and Key octets, not ze | | [`RFC2890-2.1-2`](#rfc2890-2.1-2) When K=0, the Key field MUST NOT be present (S2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze emits no GRE header bytes; the K-bit-and-Key-absence invariant is enforced by the kernel, which sets the flag only when IKey/OKey are non-zero (internal/plugins/iface/netlink/tunnel_linux.go:128-139); ze has no header-flag code path | | [`RFC2890-2.2-1`](#rfc2890-2.2-1) When S=1, the Sequence Number field MUST be present (S2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze exposes no GRE sequence-number configuration and constructs no Sequence Number field; grep for a sequence producer across the iface tunnel paths finds none | | [`RFC2890-2.2-2`](#rfc2890-2.2-2) When S=0, the Sequence Number field MUST NOT be present (S2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no S-bit or Sequence Number code path; it configures kernel/VPP tunnels and builds no GRE header | | [`RFC2890-2.2-3`](#rfc2890-2.2-3) Sequence Number MUST be used by the receiver to establish packet order (S2.2) | no test | no test carries this requirement id; annotated {not-applicable}: GRE receive-side sequence ordering is performed by the kernel/VPP datapath; ze has no GRE decapsulation or packet-parse code path | | [`RFC2890-2.2-4`](#rfc2890-2.2-4) If a packet has waited longer than OUTOFORDER_TIMER milliseconds in the buffer, the receiver MUST immediately traverse the buffer in sorted order, decapsulating packets (S2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no GRE receiver buffer or OUTOFORDER_TIMER; it programs kernel/VPP tunnels and does not process GRE payloads | | [`RFC2890-3-1`](#rfc2890-3-1) IP security protocols (ESP or AH) MUST be used to protect the GRE header and tunneled payload when using Sequence Number (S3) | no test | no test carries this requirement id; annotated {not-applicable}: ze applies no ESP or AH to GRE; IPsec is not wired to the GRE tunnel builders (internal/plugins/iface/netlink/tunnel_linux.go, internal/plugins/iface/vpp/tunnel.go) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2890-2.1-1`](#rfc2890-2.1-1) When K=1, the Key field MUST be present (4 octets) (S2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2890-2.1-1, so no unit is bound to it. ### [`RFC2890-2.1-2`](#rfc2890-2.1-2) When K=0, the Key field MUST NOT be present (S2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC2890-2.1-2, so no unit is bound to it. ### [`RFC2890-2.2-1`](#rfc2890-2.2-1) When S=1, the Sequence Number field MUST be present (S2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2890-2.2-1, so no unit is bound to it. ### [`RFC2890-2.2-2`](#rfc2890-2.2-2) When S=0, the Sequence Number field MUST NOT be present (S2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2890-2.2-2, so no unit is bound to it. ### [`RFC2890-2.2-3`](#rfc2890-2.2-3) Sequence Number MUST be used by the receiver to establish packet order (S2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2890-2.2-3, so no unit is bound to it. ### [`RFC2890-2.2-4`](#rfc2890-2.2-4) If a packet has waited longer than OUTOFORDER_TIMER milliseconds in the buffer, the receiver MUST immediately traverse the buffer in sorted order, decapsulating packets (S2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC2890-2.2-4, so no unit is bound to it. ### [`RFC2890-3-1`](#rfc2890-3-1) IP security protocols (ESP or AH) MUST be used to protect the GRE header and tunneled payload when using Sequence Number (S3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2890-3-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2890, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2890, so its obligations are stated where they were written. --- ### Page: RFC 2918 - Route Refresh Capability for BGP-4 https://ze-software.net/quality/rfc-compliance/rfc2918/ # RFC 2918 - Route Refresh Capability for BGP-4 Supported. Every requirement this repository extracted from RFC 2918, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 6 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 17 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 11 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 11 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 17 | | Tagged units | 17 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2918.md` | | Requirement shard | `rfc/requirements/rfc2918.md` | | RFC text | `rfc/full/rfc2918.txt` | ## Enrolment Enrolled: BGP Route Refresh: six MUST-level requirements, all met and test-bound with positive+negative tags. 2-1 (Route Refresh capability code 2, length 0), 3-1 (ROUTE-REFRESH message type 5), and 3-2 (4-octet AFI+Res+SAFI body, receive length validated) via internal/core/bgp/capability and internal/component/bgp/message tests; 4-1 (send ROUTE-REFRESH only to peers that advertised the capability) via a new internal/component/bgp/reactor test driving the real sendRouteRefresh and SoftClearPeer against Established session state; 4-2 (ignore a refresh for a non-negotiated family) in the reactor; 4-3 (re-advertise the Adj-RIB-Out on a valid refresh) in internal/component/bgp/plugins/rib. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** Route Refresh capability and ROUTE-REFRESH message handling. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 6 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (6):** [`RFC2918-2-1`](#rfc2918-2-1), [`RFC2918-3-1`](#rfc2918-3-1), [`RFC2918-3-2`](#rfc2918-3-2), [`RFC2918-4-1`](#rfc2918-4-1), [`RFC2918-4-2`](#rfc2918-4-2), [`RFC2918-4-3`](#rfc2918-4-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2918-2-1` | Capability code MUST be 2, capability length MUST be 0 (S2) | MUST | 2 - Route Refresh Capability | **positive:** `unit/verify` [`TestCapabilityWriteTo`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L650). **negative:** `unit/verify` [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L87) | | `RFC2918-3-1` | ROUTE-REFRESH message type MUST be 5 (S3) | MUST | 3 - Route-REFRESH Message | **positive:** `unit/verify` [`TestRouteRefreshType`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/routerefresh_test.go#L16). **negative:** `unit/verify` [`TestParseHeaderAllTypes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/header_test.go#L39) | | `RFC2918-3-2` | ROUTE-REFRESH payload MUST be exactly 4 bytes (AFI 2 + Reserved 1 + SAFI 1) (S3) | MUST | 3 - Route-REFRESH Message | **positive:** `unit/verify` [`TestRouteRefreshPack`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/routerefresh_test.go#L31). **negative:** `unit/verify` [`TestHandleRouteRefresh_InvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L381). **negative:** `unit/verify` [`TestRouteRefreshUnpackShort`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/routerefresh_test.go#L75) | | `RFC2918-4-1` | A BGP speaker MUST NOT send a ROUTE-REFRESH message to a peer unless it has received the Route Refresh Capability from that peer (S4) | MUST | 4 - Operation | **positive:** `unit/verify` [`TestRFC2918SendRouteRefreshToCapablePeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reactor_route_refresh_test.go#L94). **positive:** `unit/verify` [`TestRFC2918SoftClearPeerSendsRefreshToCapablePeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reactor_route_refresh_test.go#L158). **negative:** `unit/verify` [`TestRFC2918SendRouteRefreshSkipsPeerWithoutCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reactor_route_refresh_test.go#L115). **negative:** `unit/verify` [`TestRFC2918SoftClearPeerSkipsPeerWithoutCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reactor_route_refresh_test.go#L173) | | `RFC2918-4-2` | If a ROUTE-REFRESH is received with an AFI/SAFI not advertised by the receiver at session establishment, the receiver SHALL ignore the message (S4) | MUST | 4 - Operation | **positive:** `unit/verify` [`TestHandleRouteRefresh_NonNegotiatedFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L466). **negative:** `unit/verify` [`TestRouteRefreshValidLengthDelivered`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_test.go#L2418) | | `RFC2918-4-3` | Otherwise, the receiver SHALL re-advertise the Adj-RIB-Out of the requested AFI/SAFI based on its outbound route filtering policy (S4) | MUST | 4 - Operation | **positive:** `unit/verify` [`TestHandleRefresh_InternalState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_test.go#L986). **negative:** `unit/verify` [`TestHandleRefresh_PeerNotUp`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_test.go#L1029) | | `RFC2918-4-4` | A BGP speaker willing to receive ROUTE-REFRESH SHOULD advertise the Route Refresh Capability to the peer (S4) | SHOULD | 4 - Operation | **positive:** no positive test. **negative:** no negative test | | `RFC2918-4-5` | The AFI/SAFI in a ROUTE-REFRESH SHOULD be one the peer advertised at session establishment (S4) | SHOULD | 4 - Operation | **positive:** no positive test. **negative:** no negative test | | `RFC2918-3-3` | Reserved field SHOULD be set to 0 by the sender (S3) | SHOULD | 3 - Route-REFRESH Message | **positive:** no positive test. **negative:** no negative test | | `RFC2918-4-6` | A BGP speaker MAY send a ROUTE-REFRESH message to its peer (sending is optional) (S4) | MAY | 4 - Operation | **positive:** no positive test. **negative:** no negative test | | `RFC2918-3-4` | Reserved field SHOULD be ignored by the receiver (even if non-zero) (S3) | SHOULD | 3 - Route-REFRESH Message | **positive:** `unit/verify` [`TestRFC2918ReservedOctetIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reserved_field_test.go#L112). **negative:** `unit/verify` [`TestRFC2918ReservedOctetDoesNotExemptTheMessage`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reserved_field_test.go#L188) | ## Gaps and untested MUSTs RFC 2918 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2918-2-1`](#rfc2918-2-1) Capability code MUST be 2, capability length MUST be 0 (S2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L87) | unit/verify | unproven | | positive | [`TestCapabilityWriteTo`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L650) | unit/verify | unproven | ### [`RFC2918-3-1`](#rfc2918-3-1) ROUTE-REFRESH message type MUST be 5 (S3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseHeaderAllTypes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/header_test.go#L39) | unit/verify | unproven | | positive | [`TestRouteRefreshType`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/routerefresh_test.go#L16) | unit/verify | unproven | ### [`RFC2918-3-2`](#rfc2918-3-2) ROUTE-REFRESH payload MUST be exactly 4 bytes (AFI 2 + Reserved 1 + SAFI 1) (S3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRouteRefreshUnpackShort`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/routerefresh_test.go#L75) | unit/verify | unproven | | negative | [`TestHandleRouteRefresh_InvalidLength`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L381) | unit/verify | unproven | | positive | [`TestRouteRefreshPack`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/routerefresh_test.go#L31) | unit/verify | unproven | ### [`RFC2918-4-1`](#rfc2918-4-1) A BGP speaker MUST NOT send a ROUTE-REFRESH message to a peer unless it has received the Route Refresh Capability from that peer (S4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2918SendRouteRefreshSkipsPeerWithoutCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reactor_route_refresh_test.go#L115) | unit/verify | unproven | | negative | [`TestRFC2918SoftClearPeerSkipsPeerWithoutCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reactor_route_refresh_test.go#L173) | unit/verify | unproven | | positive | [`TestRFC2918SendRouteRefreshToCapablePeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reactor_route_refresh_test.go#L94) | unit/verify | unproven | | positive | [`TestRFC2918SoftClearPeerSendsRefreshToCapablePeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reactor_route_refresh_test.go#L158) | unit/verify | unproven | ### [`RFC2918-4-2`](#rfc2918-4-2) If a ROUTE-REFRESH is received with an AFI/SAFI not advertised by the receiver at session establishment, the receiver SHALL ignore the message (S4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRouteRefreshValidLengthDelivered`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_test.go#L2418) | unit/verify | unproven | | positive | [`TestHandleRouteRefresh_NonNegotiatedFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L466) | unit/verify | unproven | ### [`RFC2918-4-3`](#rfc2918-4-3) Otherwise, the receiver SHALL re-advertise the Adj-RIB-Out of the requested AFI/SAFI based on its outbound route filtering policy (S4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestHandleRefresh_PeerNotUp`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_test.go#L1029) | unit/verify | unproven | | positive | [`TestHandleRefresh_InternalState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_test.go#L986) | unit/verify | unproven | ### [`RFC2918-3-4`](#rfc2918-3-4) Reserved field SHOULD be ignored by the receiver (even if non-zero) (S3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2918ReservedOctetDoesNotExemptTheMessage`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reserved_field_test.go#L188) | unit/verify | unproven | | positive | [`TestRFC2918ReservedOctetIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc2918_reserved_field_test.go#L112) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-fixit-rfc-drain-quota-never-armed WP-1 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc2918.txt | | Source fingerprint | 705e36a852d934fb | | Record | rfc/extraction/rfc2918.json | | Mapped sentences | 2 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, Copyright Notice and Abstract. The Abstract says what the document defines, a capability and a message that let one speaker ask another to re-advertise its Adj-RIB-Out, and it binds no speaker. | | `1` | Introduction | 2 | walked | Introduction. States the problem: BGP-4 has no way to ask a peer to re-advertise its Adj-RIB-Out, so the common answer is soft-reconfiguration, which keeps an unmodified copy of every route from the peer. Both of its sites describe that problem in indicative prose and are excluded below. The document's own rules start at section 2. | | `2` | Route Refresh Capability | 0 | walked | Route Refresh Capability. Fixes the capability code at 2 and the capability length at 0, and says what advertising the capability conveys to the peer. Every sentence is indicative ('This capability is advertised using the Capability code 2 and Capability length 0'), so no site derives here and the one requirement the section carries is listed unsourced. | | `3` | Route-REFRESH Message | 0 | walked | Route-REFRESH Message. Fixes the message type at 5 and the message body at one 4-byte <AFI, SAFI>, and states the Reserved field's handling. The type line and the field diagram carry no modal at all, and the Reserved field's 'Should be set to 0 by the sender and ignored by the receiver' is not sited here by either scan, so all four requirements read from this section are listed unsourced. | | `4` | Operation | 2 | walked | Operation. The only section that binds a speaker on the wire. Its two sites are the two 'shall' sentences of the receive path and are mapped to RFC2918-4-2 and RFC2918-4-3. The four other requirements read from this section come from its 'may ... only if', 'should advertise' and 'should be one of' sentences, which no scan sites, and are listed unsourced. | | `5` | Security Considerations | 0 | walked | Security Considerations. One sentence: this extension to BGP does not change the underlying security issues. It directs no countermeasure at a speaker. | | `6` | Acknowledgments | 0 | skipped (acknowledgements) | Acknowledgments. Names IDRP as the source of the Route Refresh concept and thanks four reviewers. | | `7` | References: RFC 1771, RFC 2858, RFC 2842 | 0 | skipped (references) | References: RFC 1771, RFC 2858, RFC 2842. | | `8` | Author's Address | 0 | walked | Author's Address. Postal address and e-mail for the author. No obligation. | | `9` | Full Copyright Statement | 1 | walked | Full Copyright Statement. The Internet Society boilerplate governing copying and translation of the document. Its one site is a condition on republishing the text and is excluded below. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A description of the problem, in the Introduction, written in indicative prose. The 'must' states what necessarily follows from a policy change, that the prefixes have to be available again to be re-examined, and it is the motivation for the document rather than a rule the document adds. It names no message, field or timer for a speaker to get right. | When the inbound routing policy for a peer changes, all prefixes from that peer must be somehow made available and then re- examined against the new policy. | | `1:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A description of another approach, in the Introduction. The subject is soft-reconfiguration, which the sentence before it defines as storing an unmodified copy of all routes from the peer. 'Are required' reports the cost of that approach, which is what motivates this document; it directs nobody. | Additional memory and CPU are required to maintain these routes. | | `9:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Internet Society copyright boilerplate that the site scanner did not strip. Its 'must' governs the copyright procedures to be followed when the document text is republished or translated, and it binds a publisher of the document rather than an implementation of the protocol the document specifies. | However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English. | ## Superseded No document obsoletes RFC 2918, so its obligations are stated where they were written. --- ### Page: RFC 2966 - Domain-wide Prefix Distribution with Two-Level IS-IS https://ze-software.net/quality/rfc-compliance/rfc2966/ # RFC 2966 - Domain-wide Prefix Distribution with Two-Level IS-IS Experimental. Every requirement this repository extracted from RFC 2966, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 75.0% | 3 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 6 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 6 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 25.0% | 1 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 6 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 1 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 6 | | Tagged units | 6 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc2966.md` | | Requirement shard | `rfc/requirements/rfc2966.md` | | RFC text | `rfc/full/rfc2966.txt` | ## Enrolment Enrolled: Domain-wide Prefix Distribution with Two-Level IS-IS (the RFC 2966 up/down bit): four MUST-level requirements over the pure inter-level leak producer LeakPrefixes/leakInto (internal/plugins/isis/spf/leak.go). RFC2966-2-1 (up/down bit set for L2->L1 prefixes, clear otherwise) both polarities via TestISISLeakOriginationL1L2: an L2-derived prefix leaks DOWN into L1 with up/down=true, an L1-native prefix leaks UP into L2 with up/down=false. RFC2966-2-2 (MUST NOT re-advertise up/down-set L1-learned prefixes back into L2) both polarities via TestISISLeakOriginationL1L2: a prefix already carrying the down bit is skipped (leakInto skips p.UpDown) while a clear-bit L1 prefix is still leaked. RFC2966-2-3 (L1L2 routers never advertise L2->L1 routes back into L2) both polarities via TestISISLeakFixpoint: a re-originated down-bit prefix is not leaked back up, while the same prefix without the down bit does leak down. RFC2966-x-1 (ignore internal-reachability-with-external-metric-type on receipt) is {not-applicable}: Ze does not decode the old narrow TLV 128/130 (only the wide TLV 135/236), so the internal/external-metric-type conflict never arises. No SHOULD/MAY requirements are gated. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** Up/down bit retention and redistribution behavior. **What the ledger says remains:** Same IS-IS experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 3 | one part of the gated population | | Annotated instead of tested | 1 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (3):** [`RFC2966-2-1`](#rfc2966-2-1), [`RFC2966-2-2`](#rfc2966-2-2), [`RFC2966-2-3`](#rfc2966-2-3) **Annotated instead of tested (1):** [`RFC2966-x-1`](#rfc2966-x-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC2966-2-1` | Set the up/down bit to one for L2-derived prefixes advertised into L1 LSPs, zero otherwise (Section 2) | MUST | 2 | **positive:** `unit/verify` [`TestISISLeakOriginationL1L2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L52). **negative:** `unit/verify` [`TestISISLeakOriginationL1L2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L56) | | `RFC2966-2-2` | Never advertise up/down-bit-set, L1-learned prefixes back into L2 (Section 2) | MUST NOT | 2 | **positive:** `unit/verify` [`TestISISLeakOriginationL1L2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L59). **negative:** `unit/verify` [`TestISISLeakOriginationL1L2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L62) | | `RFC2966-2-3` | L1L2 routers never advertise L2->L1 inter-area routes learned via L1 routing back into L2 (Section 2) | MUST NOT | 2 | **positive:** `unit/verify` [`TestISISLeakFixpoint`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L129). **negative:** `unit/verify` [`TestISISLeakFixpoint`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L133) | | `RFC2966-x-1` | Ignore a prefix combining "IP Internal Reachability Information" with external metric-type on receipt (Sections 3.1, 3.3) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** This requirement concerns the OLD narrow-metric TLV 128 (IP Internal Reachability Information), whose per-prefix external-metric-type bit could conflict with internal reachability. Ze does not decode TLV 128 or TLV 130 (old narrow IP reachability) at all -- its codec recognizes only the wide-metric TLV 135 / TLV 236 (Extended IP/IPv6 Reachability, RFC 5305/5308), and TLV 135 has no internal/external-metric-type octet (internal/plugins/isis/packet/tlv.go recognized-type set: 1,2,6,8,9,10,22,129,132,135,137,232,236,240). A received TLV 128 is an unrecognized TLV, retained opaquely for re-flood but never interpreted for routing, so the internal-reachability-with-external-metric prefix RFC 2966 warns against is never acted upon in Ze. | | `RFC2966-3.3-1` | Ignore the up/down bit in L2 LSPs and accept the prefixes regardless of its setting (Section 3.3) | SHOULD | 3.3 | **positive:** no positive test. **negative:** no negative test | | `RFC2966-x-2` | Default configuration does not advertise L2 routes into L1; require manual configuration to do so (Sections 3.3, 4) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC2966-x-1`](#rfc2966-x-1) Ignore a prefix combining "IP Internal Reachability Information" with external metric-type on receipt (Sections 3.1, 3.3) | no test | no test carries this requirement id; annotated {not-applicable}: This requirement concerns the OLD narrow-metric TLV 128 (IP Internal Reachability Information), whose per-prefix external-metric-type bit could conflict with internal reachability. Ze does not decode TLV 128 or TLV 130 (old narrow IP reachability) at all -- its codec recognizes only the wide-metric TLV 135 / TLV 236 (Extended IP/IPv6 Reachability, RFC 5305/5308), and TLV 135 has no internal/external-metric-type octet (internal/plugins/isis/packet/tlv.go recognized-type set: 1,2,6,8,9,10,22,129,132,135,137,232,236,240). A received TLV 128 is an unrecognized TLV, retained opaquely for re-flood but never interpreted for routing, so the internal-reachability-with-external-metric prefix RFC 2966 warns against is never acted upon in Ze. | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC2966-2-1`](#rfc2966-2-1) Set the up/down bit to one for L2-derived prefixes advertised into L1 LSPs, zero otherwise (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISLeakOriginationL1L2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L56) | unit/verify | unproven | | positive | [`TestISISLeakOriginationL1L2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L52) | unit/verify | unproven | ### [`RFC2966-2-2`](#rfc2966-2-2) Never advertise up/down-bit-set, L1-learned prefixes back into L2 (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISLeakOriginationL1L2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L62) | unit/verify | unproven | | positive | [`TestISISLeakOriginationL1L2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L59) | unit/verify | unproven | ### [`RFC2966-2-3`](#rfc2966-2-3) L1L2 routers never advertise L2->L1 inter-area routes learned via L1 routing back into L2 (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISLeakFixpoint`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L133) | unit/verify | unproven | | positive | [`TestISISLeakFixpoint`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/leak_test.go#L129) | unit/verify | unproven | ### [`RFC2966-x-1`](#rfc2966-x-1) Ignore a prefix combining "IP Internal Reachability Information" with external metric-type on receipt (Sections 3.1, 3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC2966-x-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 2966, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 2966, so its obligations are stated where they were written. --- ### Page: RFC 3031 - Multiprotocol Label Switching Architecture https://ze-software.net/quality/rfc-compliance/rfc3031/ # RFC 3031 - Multiprotocol Label Switching Architecture No row in the public ledger. Every requirement this repository extracted from RFC 3031, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 7 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 7 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 7 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 7 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 7 | of 11 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 7 | of 7 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 7 of 7 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 7 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 7 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 7 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 11 | | Gated MUST-level | 7 | | Not applicable, so out of scope | 7 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3031.md` | | Requirement shard | `rfc/requirements/rfc3031.md` | | RFC text | `rfc/full/rfc3031.txt` | ## Enrolment Enrolled: Multiprotocol Label Switching Architecture: seven MUST-level requirements, all {not-applicable}. ze is an MPLS control plane that programs label operations as kernel AF_MPLS routes (internal/plugins/fib/kernel/mplsentry_linux.go addMPLSSwap) and VPP entries (internal/plugins/fib/vpp/mpls.go); the gated MUSTs (NHLFE lookup, empty-label-stack network-layer forwarding, label-stack TTL decrement and TTL-zero discard, top-label-only forwarding, unknown-label discard) are packet forwarding-plane behaviors executed by the kernel/VPP dataplane, and ze has no in-process MPLS packet-forwarding path. Label merging (Section 3.14) is an ATM/Frame-Relay VC-merge concern ze does not implement as a packet LSR. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 3031. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 7 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **7** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (7):** [`RFC3031-3.8-1`](#rfc3031-3.8-1), [`RFC3031-3.14-1`](#rfc3031-3.14-1), [`RFC3031-3.10-1`](#rfc3031-3.10-1), [`RFC3031-3.24-1`](#rfc3031-3.24-1), [`RFC3031-3.24-2`](#rfc3031-3.24-2), [`RFC3031-3.10-2`](#rfc3031-3.10-2), [`RFC3031-x-1`](#rfc3031-x-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3031-3.8-1` | Label forwarding MUST use the Next Hop Label Forwarding Entry (NHLFE) for lookup (S3.8) | MUST | 3.8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is an MPLS control plane that programs the incoming-label-to-NHLFE mapping as kernel AF_MPLS routes (internal/plugins/fib/kernel/mplsentry_linux.go:21 addMPLSSwap) and VPP entries (internal/plugins/fib/vpp/mpls.go); the NHLFE lookup on a forwarded packet is executed by the kernel/VPP dataplane, and ze has no in-process MPLS packet-forwarding path | | `RFC3031-3.14-1` | Merged upstream labels MUST all map into the same egress point (label merging) (S3.14) | MUST | 3.14 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** label merging is an ATM/Frame-Relay VC-merge concern; ze is a packet LSR with no VC-merge code path, and the RSVP-TE merge-point handling in internal/plugins/rsvpte is Fast Reroute, unrelated to RFC 3031 label merging | | `RFC3031-3.10-1` | If the packet's label stack is empty, forward based on network layer header (S3.10) | MUST | 3.10 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs the egress pop disposition so the kernel IP-routes the exposed inner packet (internal/plugins/fib/kernel/mplsentry_linux.go:44-56); the empty-label-stack network-layer forwarding decision is executed by the kernel dataplane, not by ze | | `RFC3031-3.24-1` | TTL field MUST be decremented at each LSR (S3.24) | MUST | 3.24 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** MPLS label-stack TTL decrement on a forwarded packet is performed by the kernel/VPP dataplane; ze sets only the initial push TTL (internal/plugins/fib/vpp/mpls.go) and has no transit packet-forwarding TTL code path | | `RFC3031-3.24-2` | If TTL reaches 0, packet MUST be discarded (S3.24) | MUST | 3.24 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** discarding a packet whose MPLS TTL reached zero is executed by the kernel/VPP dataplane; ze has no MPLS packet-forwarding path that could observe or act on the label-stack TTL | | `RFC3031-3.10-2` | Top label only determines forwarding; lower labels are opaque to transit LSRs (S3.10) | MUST | 3.10 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze keys each programmed swap entry on the top/incoming label (internal/plugins/fib/kernel/mplsentry_linux.go:24 MPLSDst), but the top-label-only forwarding decision on a packet is executed by the kernel dataplane; ze has no in-process forwarding path | | `RFC3031-x-1` | Unknown label in lookup: discard packet (Validation) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** discarding a packet whose top label has no ILM entry is executed by the kernel/VPP dataplane on a lookup miss; ze programs the entries and has no MPLS packet-forwarding path that could observe an unknown-label miss | | `RFC3031-3.15-1` | An LSR SHOULD be able to support both liberal and conservative label retention modes (S3.15) | SHOULD | 3.15 | **positive:** no positive test. **negative:** no negative test | | `RFC3031-3.16-1` | LSR SHOULD support penultimate hop popping (PHP) (S3.16) | SHOULD | 3.16 | **positive:** no positive test. **negative:** no negative test | | `RFC3031-3.14-2` | LSR MAY support label merging to reduce label consumption (S3.14) | MAY | 3.14 | **positive:** no positive test. **negative:** no negative test | | `RFC3031-2.1-1` | Explicitly routed LSPs MAY be used for traffic engineering (S2.1) | MAY | 2.1 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3031-3.8-1`](#rfc3031-3.8-1) Label forwarding MUST use the Next Hop Label Forwarding Entry (NHLFE) for lookup (S3.8) | no test | no test carries this requirement id; annotated {not-applicable}: ze is an MPLS control plane that programs the incoming-label-to-NHLFE mapping as kernel AF_MPLS routes (internal/plugins/fib/kernel/mplsentry_linux.go:21 addMPLSSwap) and VPP entries (internal/plugins/fib/vpp/mpls.go); the NHLFE lookup on a forwarded packet is executed by the kernel/VPP dataplane, and ze has no in-process MPLS packet-forwarding path | | [`RFC3031-3.14-1`](#rfc3031-3.14-1) Merged upstream labels MUST all map into the same egress point (label merging) (S3.14) | no test | no test carries this requirement id; annotated {not-applicable}: label merging is an ATM/Frame-Relay VC-merge concern; ze is a packet LSR with no VC-merge code path, and the RSVP-TE merge-point handling in internal/plugins/rsvpte is Fast Reroute, unrelated to RFC 3031 label merging | | [`RFC3031-3.10-1`](#rfc3031-3.10-1) If the packet's label stack is empty, forward based on network layer header (S3.10) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs the egress pop disposition so the kernel IP-routes the exposed inner packet (internal/plugins/fib/kernel/mplsentry_linux.go:44-56); the empty-label-stack network-layer forwarding decision is executed by the kernel dataplane, not by ze | | [`RFC3031-3.24-1`](#rfc3031-3.24-1) TTL field MUST be decremented at each LSR (S3.24) | no test | no test carries this requirement id; annotated {not-applicable}: MPLS label-stack TTL decrement on a forwarded packet is performed by the kernel/VPP dataplane; ze sets only the initial push TTL (internal/plugins/fib/vpp/mpls.go) and has no transit packet-forwarding TTL code path | | [`RFC3031-3.24-2`](#rfc3031-3.24-2) If TTL reaches 0, packet MUST be discarded (S3.24) | no test | no test carries this requirement id; annotated {not-applicable}: discarding a packet whose MPLS TTL reached zero is executed by the kernel/VPP dataplane; ze has no MPLS packet-forwarding path that could observe or act on the label-stack TTL | | [`RFC3031-3.10-2`](#rfc3031-3.10-2) Top label only determines forwarding; lower labels are opaque to transit LSRs (S3.10) | no test | no test carries this requirement id; annotated {not-applicable}: ze keys each programmed swap entry on the top/incoming label (internal/plugins/fib/kernel/mplsentry_linux.go:24 MPLSDst), but the top-label-only forwarding decision on a packet is executed by the kernel dataplane; ze has no in-process forwarding path | | [`RFC3031-x-1`](#rfc3031-x-1) Unknown label in lookup: discard packet (Validation) | no test | no test carries this requirement id; annotated {not-applicable}: discarding a packet whose top label has no ILM entry is executed by the kernel/VPP dataplane on a lookup miss; ze programs the entries and has no MPLS packet-forwarding path that could observe an unknown-label miss | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3031-3.8-1`](#rfc3031-3.8-1) Label forwarding MUST use the Next Hop Label Forwarding Entry (NHLFE) for lookup (S3.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC3031-3.8-1, so no unit is bound to it. ### [`RFC3031-3.14-1`](#rfc3031-3.14-1) Merged upstream labels MUST all map into the same egress point (label merging) (S3.14) Audit verdict: not audited: no reader has judged these tests No test carries RFC3031-3.14-1, so no unit is bound to it. ### [`RFC3031-3.10-1`](#rfc3031-3.10-1) If the packet's label stack is empty, forward based on network layer header (S3.10) Audit verdict: not audited: no reader has judged these tests No test carries RFC3031-3.10-1, so no unit is bound to it. ### [`RFC3031-3.24-1`](#rfc3031-3.24-1) TTL field MUST be decremented at each LSR (S3.24) Audit verdict: not audited: no reader has judged these tests No test carries RFC3031-3.24-1, so no unit is bound to it. ### [`RFC3031-3.24-2`](#rfc3031-3.24-2) If TTL reaches 0, packet MUST be discarded (S3.24) Audit verdict: not audited: no reader has judged these tests No test carries RFC3031-3.24-2, so no unit is bound to it. ### [`RFC3031-3.10-2`](#rfc3031-3.10-2) Top label only determines forwarding; lower labels are opaque to transit LSRs (S3.10) Audit verdict: not audited: no reader has judged these tests No test carries RFC3031-3.10-2, so no unit is bound to it. ### [`RFC3031-x-1`](#rfc3031-x-1) Unknown label in lookup: discard packet (Validation) Audit verdict: not audited: no reader has judged these tests No test carries RFC3031-x-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 3031, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 3031, so its obligations are stated where they were written. --- ### Page: RFC 3032 - MPLS Label Stack Encoding https://ze-software.net/quality/rfc-compliance/rfc3032/ # RFC 3032 - MPLS Label Stack Encoding Partial. Every requirement this repository extracted from RFC 3032, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 17 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 17 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 17 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 17 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 17 | of 23 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 17 | of 17 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 17 of 17 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 17 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 17 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 17 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 23 | | Gated MUST-level | 17 | | Not applicable, so out of scope | 17 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3032.md` | | Requirement shard | `rfc/requirements/rfc3032.md` | | RFC text | `rfc/full/rfc3032.txt` | ## Enrolment Enrolled: MPLS Label Stack Encoding (RFC 3032): all 17 gated MUSTs not-applicable -- data-plane (TTL processing, S-bit-on-wire, fragmentation, MTU, ICMP, disposition) is kernel AF_MPLS / VPP; ze is an MPLS control plane (encodes label values, validates, programs the FIB). Same pattern as rfc3031/rfc4364 ## What the public ledger says **Status:** Partial **What the ledger says is covered:** 20-bit label stack encoding and validation used by labeled unicast and VPN NLRI. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 17 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **17** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (17):** [`RFC3032-1-1`](#rfc3032-1-1), [`RFC3032-2.2-1`](#rfc3032-2.2-1), [`RFC3032-2.2-2`](#rfc3032-2.2-2), [`RFC3032-2.2-3`](#rfc3032-2.2-3), [`RFC3032-2.4.2-1`](#rfc3032-2.4.2-1), [`RFC3032-2.4.2-2`](#rfc3032-2.4.2-2), [`RFC3032-2.4.2-3`](#rfc3032-2.4.2-3), [`RFC3032-2.4.3-1`](#rfc3032-2.4.3-1), [`RFC3032-3.3-1`](#rfc3032-3.3-1), [`RFC3032-3.3-2`](#rfc3032-3.3-2), [`RFC3032-3.4-1`](#rfc3032-3.4-1), [`RFC3032-3.4-2`](#rfc3032-3.4-2), [`RFC3032-3.4-3`](#rfc3032-3.4-3), [`RFC3032-3.5-1`](#rfc3032-3.5-1), [`RFC3032-3.5-2`](#rfc3032-3.5-2), [`RFC3032-3.6-1`](#rfc3032-3.6-1), [`RFC3032-3.6-2`](#rfc3032-3.6-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3032-1-1` | When top labels use different encoding (e.g., ATM), this encoding MUST be used for additional label stack entries (S1) | MUST | 1 - Introduction | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no ATM/Frame-Relay MPLS top-label path so the condition never arises, and the on-wire shim for additional entries is built by the kernel/VPP dataplane; ze's only shim encoder is the 3-octet BGP NLRI (internal/core/bgp/nlri/helpers.go:61), which carries no TTL | | `RFC3032-2.2-1` | Network layer protocol MUST be inferable from the label value at bottom of stack and/or inspection of the network layer header (S2.2) | MUST | 2.2 - Determining the Network Layer Protocol | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** disposition-time protocol identification when the stack empties is performed by the kernel AF_MPLS route (loopback re-injection into the IP path) or VPP dataplane, not by any ze control-plane function (internal/plugins/fib/kernel/mplsentry_linux.go:44) | | `RFC3032-2.2-2` | When the first label is pushed, it MUST be used ONLY for packets of a particular network layer, OR ONLY for a specified set distinguishable by header inspection (S2.2) | MUST | 2.2 - Determining the Network Layer Protocol | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze binds every label to a single FEC within one address family by construction, but the operative used-only-for-one-protocol forwarding disposition is realized by the kernel's per-in-label AF_MPLS entry, with no dedicated ze guard (internal/plugins/fib/kernel/mplsentry.go:69) | | `RFC3032-2.2-3` | If a packet cannot be forwarded and its network layer protocol cannot be identified or no protocol-dependent error rules exist, the packet MUST be silently discarded (S2.2) | MUST | 2.2 - Determining the Network Layer Protocol | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the discard-on-unidentifiable-protocol decision is a forwarding-plane action of the kernel/VPP MPLS datapath; ze forwards no MPLS packets in-process | | `RFC3032-2.4.2-1` | If outgoing TTL is 0, the labeled packet MUST NOT be further forwarded (S2.4.2) | MUST | 2.4.2 - Protocol-independent rules | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** TTL decrement and TTL-zero discard are per-packet forwarding operations performed by the kernel AF_MPLS path or VPP dataplane; no TTL logic exists in any ze MPLS Go path (internal/plugins/fib/kernel/) | | `RFC3032-2.4.2-2` | When TTL=0, the label stack MUST NOT be stripped off and the packet forwarded as unlabeled (S2.4.2) | MUST NOT | 2.4.2 - Protocol-independent rules | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the TTL-expiry no-strip-and-forward decision is the same forwarding action executed by the kernel/VPP dataplane, not by ze's control plane | | `RFC3032-2.4.2-3` | When forwarding, the TTL field of the top label stack entry MUST be set to the outgoing TTL value (S2.4.2) | MUST | 2.4.2 - Protocol-independent rules | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** rewriting the shim TTL on a swapped/forwarded packet is a dataplane write done by the kernel/VPP; ze passes only label values (and a static VPP route TTL), never per-packet TTL (internal/plugins/fib/vpp/mpls.go:75) | | `RFC3032-2.4.3-1` | When an IP packet is first labeled, the label TTL field MUST be set to the value of the IP TTL field (S2.4.3) | MUST | 2.4.3 - IP-dependent rules | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** copying IP TTL into the imposed shim at ingress is a dataplane operation of the kernel/VPP push path; ze's push programming carries no TTL propagation (internal/plugins/fib/kernel/mplsentry.go:88) | | `RFC3032-3.3-1` | A labeled packet that is not "too big" MUST be transmitted without fragmentation (S3.3) | MUST | 3.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** MTU comparison and non-fragmentation of a not-too-big labeled packet are forwarding-plane behaviors of the kernel/VPP; ze has no labeled-packet transmit path | | `RFC3032-3.3-2` | A labeled IP datagram whose size exceeds the True Maximum Frame Payload Size MUST be considered "too big" (S3.3) | MUST | 3.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the too-big MTU determination on forwarded labeled datagrams is a dataplane classification made by the kernel/VPP, not by ze | | `RFC3032-3.4-1` | If a labeled IPv4 datagram is too big and has the DF bit set, the LSR MUST execute the strip/fragment/ICMP algorithm (S3.4) | MUST | 3.4 - Processing Labeled IPv4 Datagrams which are Too Big | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** strip-labels/fragment/emit-ICMP for a too-big DF-set IPv4 datagram is entirely the kernel/VPP IPv4 forwarding path; ze neither fragments nor originates ICMP for forwarded packets | | `RFC3032-3.4-2` | Each IPv4 fragment MUST be at least N bytes less than the Effective Maximum Frame Payload Size (S3.4) | MUST | 3.4 - Processing Labeled IPv4 Datagrams which are Too Big | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** fragment sizing during labeled-packet forwarding is a dataplane computation performed by the kernel/VPP; ze has no fragmentation code | | `RFC3032-3.4-3` | If the DF bit is set and packet is too big, the datagram MUST NOT be forwarded (S3.4) | MUST NOT | 3.4 - Processing Labeled IPv4 Datagrams which are Too Big | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the DF-set too-big drop decision is a forwarding-plane action of the kernel/VPP datapath | | `RFC3032-3.5-1` | To process a labeled IPv6 datagram that is too big, the LSR MUST execute the specified algorithm (S3.5) | MUST | 3.5 - Processing Labeled IPv6 Datagrams which are Too Big | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the IPv6 too-big strip/ICMP-Packet-Too-Big/fragment algorithm is the kernel/VPP IPv6 forwarding path; ze runs no such path | | `RFC3032-3.5-2` | Each IPv6 fragment MUST be at least N bytes less than the Effective Maximum Frame Payload Size (S3.5) | MUST | 3.5 - Processing Labeled IPv6 Datagrams which are Too Big | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** IPv6 fragment sizing during forwarding is a dataplane computation of the kernel/VPP, absent from ze | | `RFC3032-3.6-1` | The tunnel transmitting endpoint MUST be able to determine the MTU of the tunnel as a whole (S3.6) | MUST | 3.6 - Implications with respect to Path MTU Discovery | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** LSP-tunnel MTU/PMTU determination is a forwarding-plane concern of the kernel/VPP tunnel ingress; ze's RSVP-TE/LDP signaling sets up the LSP but runs no in-process tunnel MTU discovery | | `RFC3032-3.6-2` | The tunnel transmitting endpoint MUST send ICMP Destination Unreachable when a DF-set packet exceeds tunnel MTU (S3.6) | MUST | 3.6 - Implications with respect to Path MTU Discovery | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** generating ICMP Destination Unreachable for oversized DF-set packets entering a tunnel is a kernel/VPP forwarding-plane action; ze originates no such ICMP | | `RFC3032-2.4.3-2` | When the last label is popped (stack empty), the IP TTL field SHOULD be replaced with the outgoing TTL value (S2.4.3) | SHOULD | 2.4.3 - IP-dependent rules | **positive:** no positive test. **negative:** no negative test | | `RFC3032-3.2-1` | LSR SHOULD support a "Maximum Initially Labeled IP Datagram Size" configuration parameter (S3.2) | SHOULD | 3.2 - Maximum Initially Labeled IP Datagram Size | **positive:** no positive test. **negative:** no negative test | | `RFC3032-2.4.2-4` | When outgoing TTL is 0, the packet MAY be simply discarded or passed to the network layer for error processing depending on the label value (S2.4.2) | MAY | 2.4.2 - Protocol-independent rules | **positive:** no positive test. **negative:** no negative test | | `RFC3032-3.3-3` | A labeled IP datagram exceeding the Conventional Maximum Frame Payload Size MAY be considered "too big" (S3.3) | MAY | 3.3 | **positive:** no positive test. **negative:** no negative test | | `RFC3032-3.4-4` | If a labeled IPv4 datagram is too big and DF is not set, the LSR MAY silently discard it (S3.4) | MAY | 3.4 - Processing Labeled IPv4 Datagrams which are Too Big | **positive:** no positive test. **negative:** no negative test | | `RFC3032-3.6-3` | The tunnel endpoint MAY determine tunnel MTU by sending packets and performing Path MTU Discovery (S3.6) | MAY | 3.6 - Implications with respect to Path MTU Discovery | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3032-1-1`](#rfc3032-1-1) When top labels use different encoding (e.g., ATM), this encoding MUST be used for additional label stack entries (S1) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no ATM/Frame-Relay MPLS top-label path so the condition never arises, and the on-wire shim for additional entries is built by the kernel/VPP dataplane; ze's only shim encoder is the 3-octet BGP NLRI (internal/core/bgp/nlri/helpers.go:61), which carries no TTL | | [`RFC3032-2.2-1`](#rfc3032-2.2-1) Network layer protocol MUST be inferable from the label value at bottom of stack and/or inspection of the network layer header (S2.2) | no test | no test carries this requirement id; annotated {not-applicable}: disposition-time protocol identification when the stack empties is performed by the kernel AF_MPLS route (loopback re-injection into the IP path) or VPP dataplane, not by any ze control-plane function (internal/plugins/fib/kernel/mplsentry_linux.go:44) | | [`RFC3032-2.2-2`](#rfc3032-2.2-2) When the first label is pushed, it MUST be used ONLY for packets of a particular network layer, OR ONLY for a specified set distinguishable by header inspection (S2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze binds every label to a single FEC within one address family by construction, but the operative used-only-for-one-protocol forwarding disposition is realized by the kernel's per-in-label AF_MPLS entry, with no dedicated ze guard (internal/plugins/fib/kernel/mplsentry.go:69) | | [`RFC3032-2.2-3`](#rfc3032-2.2-3) If a packet cannot be forwarded and its network layer protocol cannot be identified or no protocol-dependent error rules exist, the packet MUST be silently discarded (S2.2) | no test | no test carries this requirement id; annotated {not-applicable}: the discard-on-unidentifiable-protocol decision is a forwarding-plane action of the kernel/VPP MPLS datapath; ze forwards no MPLS packets in-process | | [`RFC3032-2.4.2-1`](#rfc3032-2.4.2-1) If outgoing TTL is 0, the labeled packet MUST NOT be further forwarded (S2.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: TTL decrement and TTL-zero discard are per-packet forwarding operations performed by the kernel AF_MPLS path or VPP dataplane; no TTL logic exists in any ze MPLS Go path (internal/plugins/fib/kernel/) | | [`RFC3032-2.4.2-2`](#rfc3032-2.4.2-2) When TTL=0, the label stack MUST NOT be stripped off and the packet forwarded as unlabeled (S2.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: the TTL-expiry no-strip-and-forward decision is the same forwarding action executed by the kernel/VPP dataplane, not by ze's control plane | | [`RFC3032-2.4.2-3`](#rfc3032-2.4.2-3) When forwarding, the TTL field of the top label stack entry MUST be set to the outgoing TTL value (S2.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: rewriting the shim TTL on a swapped/forwarded packet is a dataplane write done by the kernel/VPP; ze passes only label values (and a static VPP route TTL), never per-packet TTL (internal/plugins/fib/vpp/mpls.go:75) | | [`RFC3032-2.4.3-1`](#rfc3032-2.4.3-1) When an IP packet is first labeled, the label TTL field MUST be set to the value of the IP TTL field (S2.4.3) | no test | no test carries this requirement id; annotated {not-applicable}: copying IP TTL into the imposed shim at ingress is a dataplane operation of the kernel/VPP push path; ze's push programming carries no TTL propagation (internal/plugins/fib/kernel/mplsentry.go:88) | | [`RFC3032-3.3-1`](#rfc3032-3.3-1) A labeled packet that is not "too big" MUST be transmitted without fragmentation (S3.3) | no test | no test carries this requirement id; annotated {not-applicable}: MTU comparison and non-fragmentation of a not-too-big labeled packet are forwarding-plane behaviors of the kernel/VPP; ze has no labeled-packet transmit path | | [`RFC3032-3.3-2`](#rfc3032-3.3-2) A labeled IP datagram whose size exceeds the True Maximum Frame Payload Size MUST be considered "too big" (S3.3) | no test | no test carries this requirement id; annotated {not-applicable}: the too-big MTU determination on forwarded labeled datagrams is a dataplane classification made by the kernel/VPP, not by ze | | [`RFC3032-3.4-1`](#rfc3032-3.4-1) If a labeled IPv4 datagram is too big and has the DF bit set, the LSR MUST execute the strip/fragment/ICMP algorithm (S3.4) | no test | no test carries this requirement id; annotated {not-applicable}: strip-labels/fragment/emit-ICMP for a too-big DF-set IPv4 datagram is entirely the kernel/VPP IPv4 forwarding path; ze neither fragments nor originates ICMP for forwarded packets | | [`RFC3032-3.4-2`](#rfc3032-3.4-2) Each IPv4 fragment MUST be at least N bytes less than the Effective Maximum Frame Payload Size (S3.4) | no test | no test carries this requirement id; annotated {not-applicable}: fragment sizing during labeled-packet forwarding is a dataplane computation performed by the kernel/VPP; ze has no fragmentation code | | [`RFC3032-3.4-3`](#rfc3032-3.4-3) If the DF bit is set and packet is too big, the datagram MUST NOT be forwarded (S3.4) | no test | no test carries this requirement id; annotated {not-applicable}: the DF-set too-big drop decision is a forwarding-plane action of the kernel/VPP datapath | | [`RFC3032-3.5-1`](#rfc3032-3.5-1) To process a labeled IPv6 datagram that is too big, the LSR MUST execute the specified algorithm (S3.5) | no test | no test carries this requirement id; annotated {not-applicable}: the IPv6 too-big strip/ICMP-Packet-Too-Big/fragment algorithm is the kernel/VPP IPv6 forwarding path; ze runs no such path | | [`RFC3032-3.5-2`](#rfc3032-3.5-2) Each IPv6 fragment MUST be at least N bytes less than the Effective Maximum Frame Payload Size (S3.5) | no test | no test carries this requirement id; annotated {not-applicable}: IPv6 fragment sizing during forwarding is a dataplane computation of the kernel/VPP, absent from ze | | [`RFC3032-3.6-1`](#rfc3032-3.6-1) The tunnel transmitting endpoint MUST be able to determine the MTU of the tunnel as a whole (S3.6) | no test | no test carries this requirement id; annotated {not-applicable}: LSP-tunnel MTU/PMTU determination is a forwarding-plane concern of the kernel/VPP tunnel ingress; ze's RSVP-TE/LDP signaling sets up the LSP but runs no in-process tunnel MTU discovery | | [`RFC3032-3.6-2`](#rfc3032-3.6-2) The tunnel transmitting endpoint MUST send ICMP Destination Unreachable when a DF-set packet exceeds tunnel MTU (S3.6) | no test | no test carries this requirement id; annotated {not-applicable}: generating ICMP Destination Unreachable for oversized DF-set packets entering a tunnel is a kernel/VPP forwarding-plane action; ze originates no such ICMP | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3032-1-1`](#rfc3032-1-1) When top labels use different encoding (e.g., ATM), this encoding MUST be used for additional label stack entries (S1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-1-1, so no unit is bound to it. ### [`RFC3032-2.2-1`](#rfc3032-2.2-1) Network layer protocol MUST be inferable from the label value at bottom of stack and/or inspection of the network layer header (S2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-2.2-1, so no unit is bound to it. ### [`RFC3032-2.2-2`](#rfc3032-2.2-2) When the first label is pushed, it MUST be used ONLY for packets of a particular network layer, OR ONLY for a specified set distinguishable by header inspection (S2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-2.2-2, so no unit is bound to it. ### [`RFC3032-2.2-3`](#rfc3032-2.2-3) If a packet cannot be forwarded and its network layer protocol cannot be identified or no protocol-dependent error rules exist, the packet MUST be silently discarded (S2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-2.2-3, so no unit is bound to it. ### [`RFC3032-2.4.2-1`](#rfc3032-2.4.2-1) If outgoing TTL is 0, the labeled packet MUST NOT be further forwarded (S2.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-2.4.2-1, so no unit is bound to it. ### [`RFC3032-2.4.2-2`](#rfc3032-2.4.2-2) When TTL=0, the label stack MUST NOT be stripped off and the packet forwarded as unlabeled (S2.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-2.4.2-2, so no unit is bound to it. ### [`RFC3032-2.4.2-3`](#rfc3032-2.4.2-3) When forwarding, the TTL field of the top label stack entry MUST be set to the outgoing TTL value (S2.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-2.4.2-3, so no unit is bound to it. ### [`RFC3032-2.4.3-1`](#rfc3032-2.4.3-1) When an IP packet is first labeled, the label TTL field MUST be set to the value of the IP TTL field (S2.4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-2.4.3-1, so no unit is bound to it. ### [`RFC3032-3.3-1`](#rfc3032-3.3-1) A labeled packet that is not "too big" MUST be transmitted without fragmentation (S3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.3-1, so no unit is bound to it. ### [`RFC3032-3.3-2`](#rfc3032-3.3-2) A labeled IP datagram whose size exceeds the True Maximum Frame Payload Size MUST be considered "too big" (S3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.3-2, so no unit is bound to it. ### [`RFC3032-3.4-1`](#rfc3032-3.4-1) If a labeled IPv4 datagram is too big and has the DF bit set, the LSR MUST execute the strip/fragment/ICMP algorithm (S3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.4-1, so no unit is bound to it. ### [`RFC3032-3.4-2`](#rfc3032-3.4-2) Each IPv4 fragment MUST be at least N bytes less than the Effective Maximum Frame Payload Size (S3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.4-2, so no unit is bound to it. ### [`RFC3032-3.4-3`](#rfc3032-3.4-3) If the DF bit is set and packet is too big, the datagram MUST NOT be forwarded (S3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.4-3, so no unit is bound to it. ### [`RFC3032-3.5-1`](#rfc3032-3.5-1) To process a labeled IPv6 datagram that is too big, the LSR MUST execute the specified algorithm (S3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.5-1, so no unit is bound to it. ### [`RFC3032-3.5-2`](#rfc3032-3.5-2) Each IPv6 fragment MUST be at least N bytes less than the Effective Maximum Frame Payload Size (S3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.5-2, so no unit is bound to it. ### [`RFC3032-3.6-1`](#rfc3032-3.6-1) The tunnel transmitting endpoint MUST be able to determine the MTU of the tunnel as a whole (S3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.6-1, so no unit is bound to it. ### [`RFC3032-3.6-2`](#rfc3032-3.6-2) The tunnel transmitting endpoint MUST send ICMP Destination Unreachable when a DF-set packet exceeds tunnel MTU (S3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3032-3.6-2, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6, rfc3032 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc3032.txt | | Source fingerprint | 4907c7ce709476f7 | | Record | rfc/extraction/rfc3032.json | | Mapped sentences | 16 | | Declined as scope | 21 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 2 | walked | Title block, Status of this Memo, Copyright Notice, Abstract and Table of Contents. Walked rather than skipped because the site scan attributes two sites here: both are Abstract sentences, and both are classified below. The Abstract restates section 1 -- MPLS needs procedures for augmenting network layer packets with label stacks, and this document specifies the encoding on PPP and LAN data links. | | `1` | Introduction | 2 | walked | Introduction. States why the encoding is needed, what the document specifies (the encoding on PPP and LAN links, plus protocol-independent and IPv4/IPv6-dependent field-processing rules), and closes with the one obligation this section places on an implementation: an LSR using a different encoding for the top one or two entries, as an ATM switch does, still uses this encoding for the additional entries. Site 1:2 carries that obligation and is mapped to RFC3032-1-1; site 1:1 is the indicative premise sentence and is excluded below. | | `1.1` | Specification of Requirements | 0 | walked | Specification of Requirements. The RFC 2119 key-words paragraph. It tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. | | `2` | The Label Stack | 0 | walked | The Label Stack. A heading with no body text of its own; sections 2.1 to 2.4 carry the content. | | `2.1` | Encoding the Label Stack | 2 | walked | Encoding the Label Stack. The document's wire-format section: the 4-octet label stack entry with its 20-bit Label, 3-bit Exp, 1-bit S and 8-bit TTL fields, the ordering rule that the top of the stack appears earliest and the network layer packet follows the entry with S set, what a successful top-label lookup yields, and the reserved label values 0 to 15. Written entirely in the indicative, and the layout, the ordering and the reserved values are carried by the Wire Formats, Encoding Rules and Constants tables of rfc/short/rfc3032.md. Its two sites both state the disposition of an Explicit NULL label and are excluded below. Ze's shim encoder is the 3-octet BGP NLRI form of internal/core/bgp/nlri.WriteLabelStack, which carries the Label and the S bit and no TTL; the 4-octet on-wire entry is built by the kernel or by VPP. | | `2.2` | Determining the Network Layer Protocol | 6 | walked | Determining the Network Layer Protocol. The document's protocol-identification rules: the LSR that pops the last label must be able to identify the network layer protocol, the identity must be inferable from the bottom label and possibly the network layer header, a first-pushed label and every label that replaces it must meet that criterion, and a packet that cannot be forwarded whose protocol cannot be identified is silently discarded. Sites 2.2:2, 2.2:3 and 2.2:6 carry the three obligations rfc/short/rfc3032.md declares as RFC3032-2.2-1, RFC3032-2.2-2 and RFC3032-2.2-3 and are mapped below; 2.2:1 and 2.2:4 restate two of them and 2.2:5 sits inside a desirability construction. | | `2.3` | Generating ICMP Messages for Labeled IP Packets | 3 | walked | Generating ICMP Messages for Labeled IP Packets. States the two conditions that must hold for an LSR to send an ICMP packet to the source of a labeled IP packet, points at section 2.2 for the first, at 2.3.1 and 2.3.2 for the second, and records that in some cases the second does not hold at all and no ICMP message can be generated. All three sites bind the LSR that originates ICMP for a labeled packet and are excluded below. | | `2.3.1` | Tunneling through a Transit Routing Domain | 1 | walked | Tunneling through a Transit Routing Domain. A deployment discussion: when external routes are not leaked into a transit domain's interior routers, only an ASBR knows how to route to an arbitrary source, and injecting default into the IGP lets an unlabeled ICMP packet reach a router with full routing information. It offers a network design, not an implementation rule; its one site is excluded below. | | `2.3.2` | Tunneling Private Addresses through a Public Backbone | 1 | walked | Tunneling Private Addresses through a Public Backbone. The alternative when the source address cannot be routed at all: copy the label stack from the original packet onto the ICMP message and label switch it, so the message leaves the MPLS domain where the source is routable. Its one site directs the LSR that builds such an ICMP message and is excluded below. | | `2.4` | Processing the Time to Live Field | 0 | walked | Processing the Time to Live Field. A heading with no body text of its own; sections 2.4.1 to 2.4.4 carry the content. | | `2.4.1` | Definitions | 0 | walked | Definitions. Defines the incoming TTL as the TTL field of the top label stack entry on receipt, and the outgoing TTL as the larger of one less than the incoming TTL and zero. Definitions, not directives. | | `2.4.2` | Protocol-independent rules | 2 | walked | Protocol-independent rules. Two capitalised MUST-level sites, mapped below to RFC3032-2.4.2-1 and RFC3032-2.4.2-3. Two further declared rows are read from the same paragraphs and are listed as unsourced ids: RFC3032-2.4.2-2 is the second clause of the sentence site 2.4.2:1 quotes, 'nor may the label stack be stripped off and the packet forwarded as an unlabeled packet', which the splitter keeps in one site because it is one sentence; RFC3032-2.4.2-4 is the MAY that follows, 'the packet MAY be simply discarded, or it may be passed to the appropriate "ordinary" network layer for error processing'. The closing paragraph, that the outgoing TTL is a function solely of the incoming TTL and that a non-top entry's TTL has no significance, is indicative. | | `2.4.3` | IP-dependent rules | 1 | walked | IP-dependent rules. Defines the IP TTL as the IPv4 TTL or the IPv6 Hop Limit, states the one capitalised MUST mapped below to RFC3032-2.4.3-1, and then the SHOULD listed as an unsourced id: 'the value of the IP TTL field SHOULD BE replaced with the outgoing TTL value' when a pop empties the stack, which in IPv4 also requires modifying the header checksum. It closes by recognizing that an administration may prefer a single IPv4 TTL decrement across an MPLS domain, which is an observation and not a directive. | | `2.4.4` | Translating Between Different Encapsulations | 0 | walked | Translating Between Different Encapsulations. States where the incoming and outgoing TTL values come from when one side of the LSR uses a different encapsulation, such as LC-ATM, and defers to that encapsulation's own procedures. Indicative throughout, and no site. | | `3` | Fragmentation and Path MTU Discovery | 1 | walked | Fragmentation and Path MTU Discovery. Introduces the problem: a received packet can be too large for its output link, and pushing labels can make a packet that fitted no longer fit. It then states what the section provides, namely rules that let Path MTU Discovery hosts and IPv6 hosts avoid fragmentation, and discusses which hosts are at risk. Its one site is a parenthetical remark about likelihood and is excluded below. | | `3.1` | Terminology | 0 | walked | Terminology. Defines Frame Payload, Conventional Maximum Frame Payload Size, True Maximum Frame Payload Size, Effective Maximum Frame Payload Size for Labeled Packets, Initially Labeled IP Datagram and Previously Labeled IP Datagram. Definitions, not directives, and no site. | | `3.2` | Maximum Initially Labeled IP Datagram Size | 1 | walked | Maximum Initially Labeled IP Datagram Size. One SHOULD, listed as the unsourced id below: an LSR that can receive an unlabeled IP datagram, add a label stack and forward the result 'SHOULD support a configuration parameter known as the "Maximum Initially Labeled IP Datagram Size"'. The rest of the section states what a zero and a positive setting do, and its one site is the positive setting's fragment-before-labeling behavior, which sits inside that SHOULD and is excluded below. | | `3.3` | not stated | 2 | walked | When are Labeled IP Datagrams Too Big? Two capitalised MUST-level sites, mapped below to RFC3032-3.3-2 and RFC3032-3.3-1. Its MAY, 'A labeled IP datagram whose size exceeds the Conventional Maximum Frame Payload Size ... MAY be considered to be "too big"', is the unsourced id below. | | `3.4` | Processing Labeled IPv4 Datagrams which are Too Big | 4 | walked | Processing Labeled IPv4 Datagrams which are Too Big. The five-step algorithm an LSR runs on a too-big labeled IPv4 datagram: strip the stack, compute N, fragment and relabel when DF is clear, and when DF is set do not forward, build an ICMP Destination Unreachable with the Fragmentation Required code and a Next-Hop MTU of the Effective Maximum Frame Payload Size less N, and transmit it if possible. Sites 3.4:1, 3.4:2 and 3.4:3 carry the three MUSTs and are mapped below to RFC3032-3.4-1, RFC3032-3.4-2 and RFC3032-3.4-3. Site 3.4:4 is the ICMP Code field value and is excluded below. The opening MAY, 'the LSR MAY silently discard the datagram' when DF is not set, is the unsourced id. | | `3.5` | Processing Labeled IPv6 Datagrams which are Too Big | 2 | walked | Processing Labeled IPv6 Datagrams which are Too Big. The IPv6 algorithm: strip the stack, compute N, send an ICMP Packet Too Big and discard when the datagram exceeds 1280 bytes or has no fragment header, otherwise fragment, relabel and forward. Its two capitalised MUST-level sites are mapped below to RFC3032-3.5-1 and RFC3032-3.5-2, and every remaining step is an unnumbered clause of the algorithm those two ids gate. | | `3.6` | Implications with respect to Path MTU Discovery | 3 | walked | Implications with respect to Path MTU Discovery. States what the too-big rules mean for RFC 1191 Path MTU Discovery, then the tunnel case: when an ICMP message cannot be forwarded out of an MPLS tunnel to the source, the transmitting endpoint must know the tunnel MTU and must send the ICMP Destination Unreachable itself. Sites 3.6:2 and 3.6:3 carry those two obligations and are mapped below to RFC3032-3.6-1 and RFC3032-3.6-2; site 3.6:1 is the conditional lead-in and is excluded. The MAY that offers Path MTU Discovery through the tunnel as the way to learn the tunnel MTU is the unsourced id. | | `4` | Transporting Labeled Packets over PPP | 0 | walked | Transporting Labeled Packets over PPP. One paragraph naming what section 4 defines: the PPP Network Control Protocol for establishing and configuring label switching over PPP. No directive and no site. | | `4.1` | Introduction | 2 | walked | Introduction. Names PPP's three components and states the link-establishment sequence: LCP first, then the MPLS Control Protocol, then labeled packets once MPLSCP reaches the Opened state. Its two sites are excluded below, the first to RFC 1661 and the second to the MPLS-over-PPP role. | | `4.2` | A PPP Network Control Protocol for MPLS | 0 | walked | A PPP Network Control Protocol for MPLS. Defines MPLSCP as LCP with five exceptions: negotiated frame modifications, PPP Protocol field 0x8281, only Codes 1 through 7, the timeout advice, and no configuration option types. Its directives are lowercase 'should' recommendations about discarding early packets and Code-Rejecting other codes, which the site scan does not read as MUST-level and rfc/short/rfc3032.md does not declare; the values are carried by its Transport Encapsulations table. Ze runs no MPLSCP: no source file names the protocol or the 0x8281 PPP type. | | `4.3` | Sending Labeled Packets | 1 | walked | Sending Labeled Packets. States the precondition for sending labeled packets over PPP, the PPP Protocol field values 0x0281 for unicast and 0x0283 for multicast, the maximum length, and that the Information field format is the one from section 2. Its one site is the precondition and is excluded below; the values are carried by the Transport Encapsulations table of rfc/short/rfc3032.md. | | `4.4` | Label Switching Control Protocol Configuration Options | 0 | walked | Label Switching Control Protocol Configuration Options. One sentence: there are no configuration options. No site. | | `5` | Transporting Labeled Packets over LAN Media | 0 | walked | Transporting Labeled Packets over LAN Media. Value assignment: one labeled packet per frame, the label stack entries immediately precede the network layer header and follow every data link header including 802.1Q, ethertype 0x8847 for unicast and 0x8848 for multicast, usable with either the ethernet or the 802.3 LLC/SNAP encapsulation. Indicative throughout, and the values are carried by the Transport Encapsulations and Constants tables of rfc/short/rfc3032.md. | | `6` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records that label values 0-15 have special meaning, that 0-3 are specified in section 2.1, and that 4-15 may be assigned by IANA on IETF Consensus. Binds IANA, not a speaker. | | `7` | Security Considerations | 0 | walked | Security Considerations. States that the encapsulation raises no security issue not already present in the MPLS architecture or in the encapsulated network layer protocol, then names two inherited considerations: a fixed-offset security check fails against a variable-size encapsulation, and the label stack carries no identity of the label writer, so accepting labels from untrusted sources can route packets illegitimately. No countermeasure is directed at a speaker and no site. | | `8` | Intellectual Property | 0 | walked | Intellectual Property. The IETF's standard statement about intellectual property rights covering the technology: the IETF takes no position on validity or scope, and points a reader at BCP 11 and the IETF Executive Director. No obligation on an implementation and no site. | | `9` | Authors' Addresses | 0 | walked | Authors' Addresses. Postal and electronic addresses for the eight authors. No site. | | `10` | not stated | 0 | skipped (references) | References: RFC 3031, RFC 2119, RFC 792, RFC 1191, RFC 2113, RFC 1661, RFC 2460, RFC 1885, and the ATM and Frame Relay label switching documents. | | `11` | Full Copyright Statement | 1 | walked | Full Copyright Statement. The Internet Society boilerplate governing copying and translation of the document, and, because no numbered heading follows it, the Acknowledgement block that closes the document. Walked rather than skipped because the prose scan attributes one site here; that site is boilerplate and is excluded below. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `front:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the LSR data-link transmit path: the sentence says an LSR must support an encoding technique that, given a label stack and a network layer packet, PRODUCES a labeled packet on a data link. Ze produces no labeled packet on a wire. It is an MPLS control plane: it programs AF_MPLS swap and pop entries and label-imposing routes through fibkernel.handleMPLSEntry (internal/plugins/fib/kernel/mpls.go), reads the kernel forwarding table for `show mpls forwarding` (internal/component/mpls.dumpMPLSRoutes), and its only label encoder is the 3-octet BGP NLRI form of internal/core/bgp/nlri.WriteLabelStack, which is RFC 8277 and carries no TTL. The 4-octet on-wire entry this sentence demands is built by the kernel AF_MPLS datapath or by VPP. This is the Abstract's copy of the sentence that reappears as site 1:1. | In order to transmit a labeled packet on a particular data link, an LSR must support an encoding technique which, given a label stack and a network layer packet, produces a labeled packet. | | `front:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The Abstract's copy of the section 1 obligation that additional label stack entries use this encoding when the top entries use another. Site 1:2 maps the section 1 sentence to RFC3032-1-1; this one restates it in the Abstract with no new obligation. | On some data links, the label at the top of the stack may be encoded in a different manner, but the techniques described here MUST be used to encode the remainder of the label stack. | | `1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the LSR data-link transmit path, the same role as site front:1: the LSR must support an encoding technique that produces a labeled packet from a label stack and a network layer packet. Ze builds no labeled packet for transmission; the kernel AF_MPLS datapath or VPP does, and ze only programs the entries that drive it (internal/plugins/fib/kernel/mpls.go, addMPLSSwap and the rich-route push path). | In order to transmit a labeled packet on a particular data link, an LSR must support an encoding technique which, given a label stack and a network layer packet, produces a labeled packet. | | `2.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the LSR label-disposition role: the sentence says what an IPv4 Explicit NULL label at the bottom of the stack INDICATES to the LSR that receives it, namely that the stack is popped and forwarding then follows the IPv4 header. Popping a stack and forwarding on the exposed IP header is a per-packet action of the kernel AF_MPLS datapath or VPP; no ze function forwards a labeled packet. Ze's own use of label 0 is control-plane signaling of the value (internal/plugins/ospf/sr.ExplicitNullV4, internal/plugins/ldp/wire.ExplicitNull), and the value assignment itself is carried by the Constants table of rfc/short/rfc3032.md rather than by any gated row. | It indicates that the label stack must be popped, and the forwarding of the packet must then be based on the IPv4 header. | | `2.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The IPv6 Explicit NULL counterpart of site 2.1:1, binding the same LSR label-disposition role: pop the stack, forward on the IPv6 header. That disposition is performed by the kernel AF_MPLS datapath or VPP. Ze signals the value from its control plane (internal/plugins/ospf/sr.ExplicitNullV6) and forwards no labeled packet. | It indicates that the label stack must be popped, and the forwarding of the packet must then be based on the IPv6 header. | | `2.2:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates the protocol-identification obligation as a consequence: the LSR that pops the last label must be able to identify the network layer protocol. The document then says how, in the sentence site 2.2:2 quotes, which is mapped to RFC3032-2.2-1. Same obligation, stated once as the need and once as the mechanism. | The LSR which pops the last label off the stack must therefore be able to identify the packet's network layer protocol. | | `2.2:4` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Extends the first-label criterion of site 2.2:3 to every label that replaces it in transit: 'the new value must also be one which meets the same criteria'. It restates RFC3032-2.2-2, which site 2.2:3 maps, for the swap case and adds no separate obligation. | Furthermore, whenever that label is replaced by another label value during a packet's transit, the new value must also be one which meets the same criteria. | | `2.2:5` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The enclosing construction makes it advisory: 'Adherence to these conditions does not necessarily enable intermediate nodes to identify a packet's network layer protocol. Under ordinary conditions, this is not necessary, but there are error conditions under which it is desirable. For instance, if an intermediate LSR determines that a labeled packet is undeliverable, it may be desirable for that LSR to generate error messages which are specific to the packet's network layer.' The site's own sentence keeps that frame ('So if intermediate nodes are to be able to generate protocol-specific error messages'), so the lowercase 'must' states what follows from a desirable capability rather than an obligation on every speaker, and rfc/short/rfc3032.md declares no row for it. | So if intermediate nodes are to be able to generate protocol-specific error messages for labeled packets, all labels in the stack must meet the criteria specified above for labels which appear at the bottom of the stack. | | `2.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the LSR that ORIGINATES an ICMP message for a labeled IP packet: the sentence states the two conditions that must hold for that LSR to build the ICMP packet and have it reach the IP source, and sites 2.3:2 and 2.3:3 are those two conditions. Ze originates no ICMP for a forwarded packet: TTL expiry and too-big handling for labeled packets are per-packet actions of the kernel AF_MPLS datapath or VPP, and no ze function builds an ICMP message on a forwarding path. The producer that would act as it if ze did is ze's MPLS code, which reads the kernel's label forwarding entries (`dumpMPLSRoutes`, `internal/component/mpls/forwarding_linux.go`) and codes labeled NLRI. It switches no packet and builds no ICMP message. | In order for a particular LSR to be able to generate an ICMP packet and have that packet sent to the source of the IP packet, two conditions must hold: | | `2.3:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The first of the two conditions on the ICMP-originating LSR named by site 2.3:1: it must be possible for that LSR to determine that a particular labeled packet is an IP packet. Ze inspects no labeled packet and originates no ICMP; the kernel AF_MPLS datapath or VPP does both. The producer that would act as it if ze did is ze's MPLS code, which reads the kernel's label forwarding entries (`dumpMPLSRoutes`, `internal/component/mpls/forwarding_linux.go`) and codes labeled NLRI. It switches no packet and builds no ICMP message. | 1. it must be possible for that LSR to determine that a particular labeled packet is an IP packet; | | `2.3:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The second condition on the same ICMP-originating LSR: it must be possible for that LSR to route to the packet's IP source address. Ze originates no ICMP for a forwarded packet, so it never plays the role this condition qualifies. The producer that would act as it if ze did is ze's MPLS code, which reads the kernel's label forwarding entries (`dumpMPLSRoutes`, `internal/component/mpls/forwarding_linux.go`) and codes labeled NLRI. It switches no packet and builds no ICMP message. | 2. it must be possible for that LSR to route to the packet's IP source address. | | `2.3.1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the sentence describes what a deployment choice would ACHIEVE, not what an implementation does. Its subject is the ASBRs injecting default into the IGP, and the lowercase 'must' sits in a relative clause qualifying which packets are affected ('any unlabeled packet which must leave the domain (such as an ICMP packet)'), so it describes a packet rather than directing a speaker. The paragraph offers one network design and the next paragraph names its limits. | (N.B.: this does NOT require that there be a "default" carried by BGP.) This would then ensure that any unlabeled packet which must leave the domain (such as an ICMP packet) gets sent to a router which has full routing information. | | `2.3.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the LSR that builds an ICMP message for a labeled packet and label switches it: the sentence says how to copy the label stack, exactly for the label values and with the ICMP header's TTL for the TTL fields. Ze builds no such message. ICMP origination on the MPLS forwarding path belongs to the kernel AF_MPLS datapath or VPP, and no ze function copies a label stack onto an ICMP message. The producer that would act as it if ze did is ze's MPLS code, which reads the kernel's label forwarding entries (`dumpMPLSRoutes`, `internal/component/mpls/forwarding_linux.go`) and codes labeled NLRI. It switches no packet and builds no ICMP message. | When copying the label stack from the original packet to the ICMP message, the label values must be copied exactly, but the TTL values in the label stack should be set to the TTL value that is placed in the IP header of the ICMP message. | | `3:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the sentence is a parenthetical estimate of LIKELIHOOD, '(Even so, fragmentation is not likely unless the packet must traverse an ethernet of some sort between the time it first gets labeled and the time it gets unlabeled.)', closing a paragraph about hosts that send 1500-byte datagrams within a classful network number. The lowercase 'must' qualifies the path a packet takes, and the sentence directs nobody. | (Even so, fragmentation is not likely unless the packet must traverse an ethernet of some sort between the time it first gets labeled and the time it gets unlabeled.) | | `3.2:1` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The enclosing construction is a SHOULD: 'Every LSR which is capable of a) receiving an unlabeled IP datagram, b) adding a label stack to the datagram, and c) forwarding the resulting labeled packet, SHOULD support a configuration parameter known as the "Maximum Initially Labeled IP Datagram Size", which can be set to a non-negative value.' This site is the parameter's effect when it is set to a positive value ('If it is set to a positive value, it is used in the following way'), so its lowercase 'must' describes the behavior of an optional parameter rather than an independent obligation. rfc/short/rfc3032.md declares the enclosing SHOULD as RFC3032-3.2-1, listed in this section's unsourced ids. | a) an unlabeled IP datagram is received, and b) that datagram does not have the DF bit set in its IP header, and c) that datagram needs to be labeled before being forwarded, and d) the size of the datagram (before labeling) exceeds the value of the parameter, then a) the datagram must be broken into fragments, each of whose size is no greater than the value of the parameter, and b) each fragment must be labeled and then forwarded. | | `3.4:4` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the keyword is inside a quoted VALUE NAME, not a directive. The step reads 'set its Code field [3] to "Fragmentation Required and DF Set"', and 'Fragmentation Required and DF Set' is the name of ICMP Destination Unreachable code 4 as RFC 792 and RFC 1191 spell it. The case-insensitive prose scan sees 'Required' inside that quoted name. The step itself is one clause of the algorithm gated by RFC3032-3.4-1, which site 3.4:1 maps. | i. set its Code field [3] to "Fragmentation Required and DF Set", | | `3.6:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the sentence is the conditional lead-in that scopes the two bulleted obligations, and it ends in a colon rather than stating one. Its lowercase 'must' sits in a relative clause describing the packets ('packets that must go through the tunnel, but are too large to pass through the tunnel unfragmented'), and the obligations it introduces are sites 3.6:2 and 3.6:3, mapped below. | If it is not possible to forward an ICMP message from within an MPLS "tunnel" to a packet's source address, but the network configuration makes it possible for the LSR at the transmitting end of the tunnel to receive packets that must go through the tunnel, but are too large to pass through the tunnel unfragmented, then: | | `4.1:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 1661, the Point-to-Point Protocol, which this section cites as [6] and whose link-establishment procedure the sentence restates as background: each end sends LCP packets to configure and test the data link before communications are established. It is not an MPLS rule, and rfc/short/rfc3032.md declares no row for it. Ze does implement LCP under RFC 1661, in internal/component/l2tp/ppp, for L2TP and PPPoE subscriber access; what it does not implement is this section's MPLSCP, which sites 4.1:2 and 4.3:1 govern. | In order to establish communications over a point-to-point link, each end of the PPP link must first send LCP packets to configure and test the data link. | | `4.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the MPLS-over-PPP LSR role, the PPP endpoint that runs the MPLS Control Protocol so labeled packets can be transmitted on the link. Ze does not play it: no source file names MPLSCP or the 0x8281 PPP protocol type, and ze's PPP implementation (internal/component/l2tp/ppp) carries L2TP and PPPoE subscriber sessions, never labeled packets. Its MPLS forwarding entries are programmed into the kernel AF_MPLS datapath or VPP, which owns whatever link encapsulation the packets then take. | After the link has been established and optional facilities have been negotiated as needed by the LCP, PPP must send "MPLS Control Protocol" packets to enable the transmission of labeled packets. | | `4.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same MPLS-over-PPP LSR role as site 4.1:2: before labeled packets are communicated, PPP must reach the Network-Layer Protocol phase and MPLSCP must reach the Opened state. Ze negotiates no MPLSCP and sends no labeled packet over a PPP link. The producer that would act as it if ze did is ze's MPLS code, which reads the kernel's label forwarding entries (`dumpMPLSRoutes`, `internal/component/mpls/forwarding_linux.go`) and codes labeled NLRI. It switches no packet and builds no ICMP message. | Before any labeled packets may be communicated, PPP must reach the Network-Layer Protocol phase, and the MPLS Control Protocol must reach the Opened state. | | `11:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: this is the Internet Society's Full Copyright Statement, which the derivation attributes to section 11. The sentence governs how the DOCUMENT may be copied and translated, saying it may not be modified except as needed for developing Internet standards or for translation. It states nothing about an implementation. | However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English. | ## Superseded No document obsoletes RFC 3032, so its obligations are stated where they were written. --- ### Page: RFC 3101 - The OSPF Not-So-Stubby Area (NSSA) Option https://ze-software.net/quality/rfc-compliance/rfc3101/ # RFC 3101 - The OSPF Not-So-Stubby Area (NSSA) Option Experimental. Every requirement this repository extracted from RFC 3101, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 17 of 17 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 17 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 17 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 17 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 20.0% | 13 of 65 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 17 | of 21 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 17 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 17 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 17 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 17 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 17 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 21 | | Gated MUST-level | 17 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 65 | | Tagged units | 65 | | Recorded audit verdicts | 0 | | Discrimination records | 13 | | Summary | `rfc/short/rfc3101.md` | | Requirement shard | `rfc/requirements/rfc3101.md` | | RFC text | `rfc/full/rfc3101.txt` | ## Enrolment Enrolled: OSPF NSSA (RFC 3101): 13 MET (N/E-bit Hello negotiation, Type-7 origination/flood-scope, P-bit boundary policy, ASBR E-bit, Type-3 import, translator election, Type-7->Type-5 translation, highest-RID duplicate suppression) + 2 gap (install-side default P-gate, unconditional default into every NSSA) ## What the public ledger says **Status:** Experimental **What the ledger says is covered** Type-7 origination/flooding, redistribution into NSSA, N/E-bit Hello negotiation, Router-LSA Nt/E/B flags, translator election (Nt-bit candidates, highest-RID, always/never roles, stability grace), Type-7 to Type-5 translation with FA/metric/tag preservation and highest-RID duplicate suppression, source preference, Type-3 summary import policy. For both address families: mandatory border-router defaults with no operator gate, no-summary defaults through the summary path (Type-3 for OSPFv2, Inter-Area-Prefix for OSPFv3), no summary-LSA default where summary routes ARE imported (Section 2.7 MUST NOT), an internal router's `default-originate` held inert in a no-summary NSSA (Section 1.3 mutual exclusivity), and the P-bit and suppressed-summary-import gates on installing a received Type-7 default, each gate proven permissive on a router that is not an NSSA border router. **What the ledger says remains** The Section 2.4 default-route origination dispatches on address family in `applyNSSADefaults` ([`internal/plugins/ospf/nssa.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa.go)), so an OSPFv3 NSSA border router originates the 0x2007 NSSA-LSA that RFC 5340 Section 4.4.3.7 defines rather than the OSPFv2 0x0007. Both halves are unit-proven in each family, both families reach the operator's `show ospf database nssa-external` subview under [`test/ospf/ospf-nssa-abr-default.ci`](https://github.com/ze-software/ze/blob/main/test/ospf/ospf-nssa-abr-default.ci) and [`test/ospfv3/ospfv3-nssa-abr-default.ci`](https://github.com/ze-software/ze/blob/main/test/ospfv3/ospfv3-nssa-abr-default.ci), and OSPFv2 origination is additionally proven against FRR by `test/interop/scenarios/ospf-stub-nssa-frr`. Two things are still owed, tracked by [`plan/immediate/spec-ospf-rfc3101-nssa-defaults.md`](https://github.com/ze-software/ze/blob/main/plan/immediate/spec-ospf-rfc3101-nssa-defaults.md): OSPFv3 has no interop scenario, so no peer daemon has read a Ze OSPFv3 NSSA default, and neither family has an interop scenario for the two install-side gates; and three sites compute NSSA border-router status independently, so the advertised Router-LSA B-bit and the originated default can disagree across a backbone transition. Section 2.2 Type-7 address-range aggregation into one Type-5 stays unimplemented; it is a MAY. Note that the [`rfc/short/rfc3101.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc3101.md) checklist cannot record an address-family difference: its requirement ids carry no address-family dimension, so a tagged test on either path satisfies it for both. Same OSPF experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 17 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **17** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (17):** [`RFC3101-2.1-1`](#rfc3101-2.1-1), [`RFC3101-2.1-2`](#rfc3101-2.1-2), [`RFC3101-x-1`](#rfc3101-x-1), [`RFC3101-2.3-1`](#rfc3101-2.3-1), [`RFC3101-2.3-2`](#rfc3101-2.3-2), [`RFC3101-2.4-1`](#rfc3101-2.4-1), [`RFC3101-2.4-2`](#rfc3101-2.4-2), [`RFC3101-2.4-3`](#rfc3101-2.4-3), [`RFC3101-2.4-4`](#rfc3101-2.4-4), [`RFC3101-2.4-5`](#rfc3101-2.4-5), [`RFC3101-2.5-1`](#rfc3101-2.5-1), [`RFC3101-3.1-1`](#rfc3101-3.1-1), [`RFC3101-2.7-1`](#rfc3101-2.7-1), [`RFC3101-3.1-2`](#rfc3101-3.1-2), [`RFC3101-3.2-1`](#rfc3101-3.2-1), [`RFC3101-3.2-2`](#rfc3101-3.2-2), [`RFC3101-2.7-3`](#rfc3101-2.7-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3101-2.1-1` | Verify N-bit and E-bit in received Hellos match the area type before adjacency (Section 2.1) | MUST | 2.1 | **positive:** `unit/verify` [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L46). **negative:** `unit/verify` [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L68) | | `RFC3101-2.1-2` | Refuse adjacency unless both routers agree on the N-bit (Section 2.1) | MUST | 2.1 | **positive:** `unit/verify` [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L48). **negative:** `unit/verify` [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L59) | | `RFC3101-x-1` | Keep the E-bit clear whenever the N-bit is set (Appendix A) | MUST | x | **positive:** `unit/verify` [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L50). **negative:** `unit/verify` [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L70) | | `RFC3101-2.3-1` | Originate Type-7 NSSA-LSAs with LS Type value 7 (Section 2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestOSPFType7Origination`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L33). **negative:** `unit/verify` [`TestOSPFType7Origination`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L49) | | `RFC3101-2.3-2` | Flood Type-7 LSAs only within the originating NSSA (Section 2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestOSPFType7FloodScope`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L86). **negative:** `unit/verify` [`TestOSPFType7FloodScope`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L92) | | `RFC3101-2.4-1` | Set the P-bit on Type-7 LSAs an NSSA internal ASBR wants in the transit topology (Section 2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestOSPFType7Origination`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L35). **negative:** `unit/verify` [`TestOSPFType7Origination`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L57) | | `RFC3101-2.4-2` | Ensure a non-zero forwarding address whenever the P-bit is set; otherwise do not originate the Type-7 LSA (Section 2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestOSPFNSSAPBitBoundaryPolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_pbit_test.go#L32). **negative:** `unit/verify` [`TestOSPFNSSAPBitBoundaryPolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_pbit_test.go#L24). **negative:** `unit/verify` [`TestOSPFv3NSSAInternalRouterDefaultNeedsForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L171) | | `RFC3101-2.4-3` | Clear the P-bit on a Type-7 LSA when the same network is also originated as a Type-5 LSA (Section 2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestOSPFNSSAPBitBoundaryPolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_pbit_test.go#L34). **negative:** `unit/verify` [`TestOSPFNSSAPBitBoundaryPolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_pbit_test.go#L42) | | `RFC3101-2.4-4` | Clear the P-bit on a Type-7 default LSA originated by an NSSA border router; install a Type-7 default only if its P-bit is set (Section 2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestOSPFNSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L78). **positive:** `unit/verify` [`TestOSPFNSSABorderRouterDefaultsEveryArea`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L37). **positive:** `unit/verify` [`TestOSPFv3NSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L81). **positive:** `unit/verify` [`TestOSPFv3NSSABorderRouterOriginatesDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L71). **negative:** `unit/verify` [`TestOSPFNSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L111). **negative:** `unit/verify` [`TestOSPFNSSANonBorderRouterInstallsPClearDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L138). **negative:** `unit/verify` [`TestOSPFv3NSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L90). **negative:** `unit/verify` [`TestOSPFv3NSSADefaultPBitFollowsForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L191). **negative:** `unit/verify` [`TestOSPFv3NSSANonBorderRouterInstallsPClearDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L112) | | `RFC3101-2.4-5` | Originate a default-destination LSA into every directly attached NSSA (Section 2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestOSPFNSSABorderRouterDefaultsEveryArea`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L31). **positive:** `unit/verify` [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L128). **positive:** `unit/verify` [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L103). **positive:** `unit/verify` [`TestOSPFv3NSSABorderRouterOriginatesDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L62). **negative:** `unit/verify` [`TestOSPFNSSAInternalRouterOriginatesNoBorderDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L52). **negative:** `unit/verify` [`TestOSPFv3NSSAInternalRouterDefaultNeedsForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L173). **positive:** `interop/nightly` [`checkNSSADefault`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1263) | | `RFC3101-2.5-1` | Ignore Type-7 default LSAs on an NSSA border router that suppresses Type-3 summary import (Section 2.5) | MUST | 2.5 | **positive:** `unit/verify` [`TestOSPFNSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L94). **positive:** `unit/verify` [`TestOSPFv3NSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L96). **negative:** `unit/verify` [`TestOSPFNSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L80). **negative:** `unit/verify` [`TestOSPFNSSANonBorderRouterInstallsPClearDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L140). **negative:** `unit/verify` [`TestOSPFv3NSSANonBorderRouterInstallsPClearDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L114) | | `RFC3101-3.1-1` | Set the E-bit in Type-1 router-LSAs of directly attached non-stub areas (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOSPFASBRBitFromNSSAType7`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/origination_external_test.go#L90). **negative:** `unit/verify` [`TestOSPFASBRBitFromNSSAType7`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/origination_external_test.go#L76) | | `RFC3101-2.7-1` | Support optional import of summary routes into NSSAs as Type-3 summary-LSAs (Section 2.7) | MUST | 2.7 | **positive:** `unit/verify` [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L85). **negative:** `unit/verify` [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L96) | | `RFC3101-3.1-2` | Elect the translator as the reachable NSSA border router with Nt set or the highest Router ID (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOSPFNSSANonCandidateDoesNotWedge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L235). **positive:** `unit/verify` [`TestOSPFNSSATranslatorElection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L34). **negative:** `unit/verify` [`TestOSPFNSSANoTranslateWhenNotElected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L215). **negative:** `unit/verify` [`TestOSPFNSSATranslatorElection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L37) | | `RFC3101-3.2-1` | In translation, set the advertising router to the translator's Router ID and preserve mask, path type, metric, forwarding address, and route tag (Section 3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestOSPFNSSATranslation`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L83). **negative:** `unit/verify` [`TestOSPFNSSAPbitNotTranslated`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L143). **negative:** `unit/verify` [`TestOSPFNSSATranslation`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L97) | | `RFC3101-3.2-2` | Suppress duplicate translation: translate only if this router has the highest Router ID among translators advertising a functionally equivalent Type-5 LSA (Section 3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestOSPFHigherRIDType5Exists`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_higher_rid_test.go#L36). **positive:** `unit/verify` [`TestOSPFNSSAHigherRIDType5Suppresses`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L118). **negative:** `unit/verify` [`TestOSPFHigherRIDType5Exists`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_higher_rid_test.go#L28). **negative:** `unit/verify` [`TestOSPFNSSAHigherRIDType5Suppresses`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L128) | | `RFC3101-x-2` | Honor the TranslatorStabilityInterval (default 40 s) before relinquishing translator duties (Appendix D) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3101-2.4-6` | Originate Type-4 summary-LSAs into an NSSA (Section 2.4) | SHOULD NOT | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC3101-2.7-2` | Originate a Type-3 summary-LSA as the NSSA default when summary import is disabled (no-summary NSSA) (Section 2.7) | SHOULD | 2.7 | **positive:** `unit/verify` [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L126). **positive:** `unit/verify` [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L101). **positive:** `unit/verify` [`TestOSPFv3NSSANoSummaryDefaultUsesSummaryLSA`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L105). **negative:** `unit/verify` [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L149). **negative:** `unit/verify` [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L90). **negative:** `unit/verify` [`TestOSPFv3NSSANoSummaryDefaultUsesSummaryLSA`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L99) | | `RFC3101-2.7-3` | Originate the NSSA default as a Type-3 summary-LSA when summary routes ARE imported (Section 2.7) | MUST NOT | 2.7 | **positive:** `unit/verify` [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L151). **positive:** `unit/verify` [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L92). **positive:** `unit/verify` [`TestOSPFv3NSSANoSummaryDefaultUsesSummaryLSA`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L117). **negative:** `unit/verify` [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L130). **negative:** `unit/verify` [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L105). **negative:** `unit/verify` [`TestOSPFv3NSSANoSummaryDefaultUsesSummaryLSA`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L108) | | `RFC3101-2.2-1` | Aggregate Type-7 routes into one Type-5 LSA per configured Type-7 address range, with a 0.0.0.0 forwarding address (Section 2.2, Section 3.2) | MAY | 2.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 3101 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3101-2.1-1`](#rfc3101-2.1-1) Verify N-bit and E-bit in received Hellos match the area type before adjacency (Section 2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L68) | unit/verify | unproven | | positive | [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L46) | unit/verify | unproven | ### [`RFC3101-2.1-2`](#rfc3101-2.1-2) Refuse adjacency unless both routers agree on the N-bit (Section 2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L59) | unit/verify | unproven | | positive | [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L48) | unit/verify | unproven | ### [`RFC3101-x-1`](#rfc3101-x-1) Keep the E-bit clear whenever the N-bit is set (Appendix A) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L70) | unit/verify | unproven | | positive | [`TestOSPFNSSANbitMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/iface/hello_nssa_test.go#L50) | unit/verify | unproven | ### [`RFC3101-2.3-1`](#rfc3101-2.3-1) Originate Type-7 NSSA-LSAs with LS Type value 7 (Section 2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFType7Origination`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L49) | unit/verify | unproven | | positive | [`TestOSPFType7Origination`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L33) | unit/verify | unproven | ### [`RFC3101-2.3-2`](#rfc3101-2.3-2) Flood Type-7 LSAs only within the originating NSSA (Section 2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFType7FloodScope`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L92) | unit/verify | unproven | | positive | [`TestOSPFType7FloodScope`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L86) | unit/verify | unproven | ### [`RFC3101-2.4-1`](#rfc3101-2.4-1) Set the P-bit on Type-7 LSAs an NSSA internal ASBR wants in the transit topology (Section 2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFType7Origination`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L57) | unit/verify | unproven | | positive | [`TestOSPFType7Origination`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_test.go#L35) | unit/verify | unproven | ### [`RFC3101-2.4-2`](#rfc3101-2.4-2) Ensure a non-zero forwarding address whenever the P-bit is set; otherwise do not originate the Type-7 LSA (Section 2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSAPBitBoundaryPolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_pbit_test.go#L24) | unit/verify | unproven | | negative | [`TestOSPFv3NSSAInternalRouterDefaultNeedsForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L171) | unit/verify | unproven | | positive | [`TestOSPFNSSAPBitBoundaryPolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_pbit_test.go#L32) | unit/verify | unproven | ### [`RFC3101-2.4-3`](#rfc3101-2.4-3) Clear the P-bit on a Type-7 LSA when the same network is also originated as a Type-5 LSA (Section 2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSAPBitBoundaryPolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_pbit_test.go#L42) | unit/verify | unproven | | positive | [`TestOSPFNSSAPBitBoundaryPolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_pbit_test.go#L34) | unit/verify | unproven | ### [`RFC3101-2.4-4`](#rfc3101-2.4-4) Clear the P-bit on a Type-7 default LSA originated by an NSSA border router; install a Type-7 default only if its P-bit is set (Section 2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFv3NSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L90) | unit/verify | revert, verified | | negative | [`TestOSPFv3NSSANonBorderRouterInstallsPClearDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L112) | unit/verify | revert, verified | | negative | [`TestOSPFv3NSSADefaultPBitFollowsForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L191) | unit/verify | unproven | | negative | [`TestOSPFNSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L111) | unit/verify | unproven | | negative | [`TestOSPFNSSANonBorderRouterInstallsPClearDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L138) | unit/verify | revert, verified | | positive | [`TestOSPFNSSABorderRouterDefaultsEveryArea`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L37) | unit/verify | unproven | | positive | [`TestOSPFv3NSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L81) | unit/verify | revert, verified | | positive | [`TestOSPFv3NSSABorderRouterOriginatesDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L71) | unit/verify | unproven | | positive | [`TestOSPFNSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L78) | unit/verify | unproven | ### [`RFC3101-2.4-5`](#rfc3101-2.4-5) Originate a default-destination LSA into every directly attached NSSA (Section 2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSAInternalRouterOriginatesNoBorderDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L52) | unit/verify | unproven | | negative | [`TestOSPFv3NSSAInternalRouterDefaultNeedsForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L173) | unit/verify | unproven | | positive | [`checkNSSADefault`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1263) | interop/nightly | unproven | | positive | [`TestOSPFNSSABorderRouterDefaultsEveryArea`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L31) | unit/verify | unproven | | positive | [`TestOSPFv3NSSABorderRouterOriginatesDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L62) | unit/verify | unproven | | positive | [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L128) | unit/verify | unproven | | positive | [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L103) | unit/verify | unproven | ### [`RFC3101-2.5-1`](#rfc3101-2.5-1) Ignore Type-7 default LSAs on an NSSA border router that suppresses Type-3 summary import (Section 2.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFv3NSSANonBorderRouterInstallsPClearDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L114) | unit/verify | revert, verified | | negative | [`TestOSPFNSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L80) | unit/verify | unproven | | negative | [`TestOSPFNSSANonBorderRouterInstallsPClearDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L140) | unit/verify | revert, verified | | positive | [`TestOSPFv3NSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_install_gate_v6_test.go#L96) | unit/verify | revert, verified | | positive | [`TestOSPFNSSABorderRouterDefaultPBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external_nssa_test.go#L94) | unit/verify | unproven | ### [`RFC3101-3.1-1`](#rfc3101-3.1-1) Set the E-bit in Type-1 router-LSAs of directly attached non-stub areas (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFASBRBitFromNSSAType7`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/origination_external_test.go#L76) | unit/verify | unproven | | positive | [`TestOSPFASBRBitFromNSSAType7`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/origination_external_test.go#L90) | unit/verify | unproven | ### [`RFC3101-2.7-1`](#rfc3101-2.7-1) Support optional import of summary routes into NSSAs as Type-3 summary-LSAs (Section 2.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L96) | unit/verify | unproven | | positive | [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L85) | unit/verify | unproven | ### [`RFC3101-3.1-2`](#rfc3101-3.1-2) Elect the translator as the reachable NSSA border router with Nt set or the highest Router ID (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSANoTranslateWhenNotElected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L215) | unit/verify | unproven | | negative | [`TestOSPFNSSATranslatorElection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L37) | unit/verify | unproven | | positive | [`TestOSPFNSSANonCandidateDoesNotWedge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L235) | unit/verify | unproven | | positive | [`TestOSPFNSSATranslatorElection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L34) | unit/verify | unproven | ### [`RFC3101-3.2-1`](#rfc3101-3.2-1) In translation, set the advertising router to the translator's Router ID and preserve mask, path type, metric, forwarding address, and route tag (Section 3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFNSSAPbitNotTranslated`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L143) | unit/verify | unproven | | negative | [`TestOSPFNSSATranslation`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L97) | unit/verify | unproven | | positive | [`TestOSPFNSSATranslation`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_test.go#L83) | unit/verify | unproven | ### [`RFC3101-3.2-2`](#rfc3101-3.2-2) Suppress duplicate translation: translate only if this router has the highest Router ID among translators advertising a functionally equivalent Type-5 LSA (Section 3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFHigherRIDType5Exists`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_higher_rid_test.go#L28) | unit/verify | unproven | | negative | [`TestOSPFNSSAHigherRIDType5Suppresses`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L128) | unit/verify | unproven | | positive | [`TestOSPFHigherRIDType5Exists`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/nssa_higher_rid_test.go#L36) | unit/verify | unproven | | positive | [`TestOSPFNSSAHigherRIDType5Suppresses`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa_ac14_16_test.go#L118) | unit/verify | unproven | ### [`RFC3101-2.7-2`](#rfc3101-2.7-2) Originate a Type-3 summary-LSA as the NSSA default when summary import is disabled (no-summary NSSA) (Section 2.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFv3NSSANoSummaryDefaultUsesSummaryLSA`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L99) | unit/verify | unproven | | negative | [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L149) | unit/verify | unproven | | negative | [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L90) | unit/verify | unproven | | positive | [`TestOSPFv3NSSANoSummaryDefaultUsesSummaryLSA`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L105) | unit/verify | unproven | | positive | [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L126) | unit/verify | unproven | | positive | [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L101) | unit/verify | unproven | ### [`RFC3101-2.7-3`](#rfc3101-2.7-3) Originate the NSSA default as a Type-3 summary-LSA when summary routes ARE imported (Section 2.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFv3NSSANoSummaryDefaultUsesSummaryLSA`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L108) | unit/verify | revert, verified | | negative | [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L130) | unit/verify | revert, verified | | negative | [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L105) | unit/verify | revert, verified | | positive | [`TestOSPFv3NSSANoSummaryDefaultUsesSummaryLSA`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_nssa_default_test.go#L117) | unit/verify | revert, verified | | positive | [`TestOSPFNSSANoSummaryDefaultInjection`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L151) | unit/verify | revert, verified | | positive | [`TestOSPFNSSAType3SummaryImport`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/area_type_test.go#L92) | unit/verify | revert, verified | ## Extraction sign-off No extraction sign-off exists for RFC 3101, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 3101, so its obligations are stated where they were written. --- ### Page: RFC 3209 - RSVP-TE: Extensions to RSVP for LSP Tunnels https://ze-software.net/quality/rfc-compliance/rfc3209/ # RFC 3209 - RSVP-TE: Extensions to RSVP for LSP Tunnels Experimental. Every requirement this repository extracted from RFC 3209, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 38.5% | 5 of 13 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 46.2% | 6 of 13 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 13 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 16 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 13 | of 15 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 13 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 13 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 13 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 13 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 15.4% | 2 of 13 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 13 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 15 | | Gated MUST-level | 13 | | Not applicable, so out of scope | 0 | | Declared gaps | 2 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 16 | | Tagged units | 16 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3209.md` | | Requirement shard | `rfc/requirements/rfc3209.md` | | RFC text | `rfc/full/rfc3209.txt` | ## Enrolment Enrolled: RSVP-TE (RFC 3209): PATH/RESV signaling, ERO, LABEL, SE make-before-break, soft-state; 5 MET + 6 single-polarity positive + 2 gap (ERO strict-hop adjacency check, IP Router Alert option) ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** PATH and RESV signaling, ERO routing, bandwidth admission, SE-style make-before-break, soft-state refresh/expiry, teardown. **What the ledger says remains** Two MUST gaps gated in [`rfc/short/rfc3209.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc3209.md): [`RFC3209-4.3.4.1-1`](#rfc3209-4.3.4.1-1) -- the transit next-hop selector (engine.go) ignores the ERO L-bit and does no adjacency check, so a non-adjacent strict hop is not rejected; and RFC3209-x-1 -- the raw IP transport (transport_linux.go) sends PATH without the IP Router Alert option (same transport gap as RFC 2205). Cross-vendor interop remains constrained by available open daemons. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 5 | one part of the gated population | | Annotated instead of tested | 8 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **13** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (5):** [`RFC3209-4.1-1`](#rfc3209-4.1-1), [`RFC3209-4.1-2`](#rfc3209-4.1-2), [`RFC3209-4.3.4-1`](#rfc3209-4.3.4-1), [`RFC3209-6-1`](#rfc3209-6-1), [`RFC3209-2.5-1`](#rfc3209-2.5-1) **Annotated instead of tested (8):** [`RFC3209-4.6.1-1`](#rfc3209-4.6.1-1), [`RFC3209-4.6.2-1`](#rfc3209-4.6.2-1), [`RFC3209-4.6.1-2`](#rfc3209-4.6.1-2), [`RFC3209-4.2-1`](#rfc3209-4.2-1), [`RFC3209-4.3.4.1-1`](#rfc3209-4.3.4.1-1), [`RFC3209-4.1-3`](#rfc3209-4.1-3), [`RFC3209-6-2`](#rfc3209-6-2), [`RFC3209-x-1`](#rfc3209-x-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3209-4.6.1-1` | SESSION Object reserved field MUST be zero (S4.6.1, Wire Format) | MUST | 4.6.1 | **positive:** `unit/verify` [`TestRSVPSessionObjectEncoding`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L338). **negative:** no negative test. **{single-polarity}:** the encoder writes the SESSION reserved octet as 0 on every SESSION it emits, but the decoder never reads that octet, so a non-zero-reserved reject test is not meaningful (internal/plugins/rsvpte/wire.go:238, :243) | | `RFC3209-4.6.2-1` | SENDER_TEMPLATE Object reserved field MUST be zero (S4.6.2, Wire Format) | MUST | 4.6.2 | **positive:** `unit/verify` [`TestRSVPSenderTemplateReservedZeroOnSend`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L368). **negative:** no negative test. **{single-polarity}:** the encoder zeroes the 2-byte reserved field before LSP ID on every SENDER_TEMPLATE, and decodeSenderTemplate never inspects it (internal/plugins/rsvpte/wire.go:270, :275-276) | | `RFC3209-4.6.1-2` | SESSION C-Type 7 (LSP_TUNNEL_IPv4) MUST be used for RSVP-TE LSP tunnels (S4.6.1) | MUST | 4.6.1 | **positive:** `unit/verify` [`TestRSVPSessionObjectEncoding`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L337). **negative:** no negative test. **{single-polarity}:** encodeSessionIPv4 always stamps C-Type 7, but DecodeMessage dispatches SESSION by Class-Num only and never rejects a wrong C-Type, so no negative decode-reject exists (internal/plugins/rsvpte/wire.go:240, :746-752) | | `RFC3209-4.2-1` | LABEL_REQUEST object MUST be present in PATH messages to request label allocation (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestBuildPathRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/build_test.go#L45). **negative:** no negative test. **{single-polarity}:** buildPath unconditionally appends a LABEL_REQUEST to every PATH it composes, and handlePath does not reject a received PATH lacking one, so only the positive is meaningful (internal/plugins/rsvpte/build.go:92) | | `RFC3209-4.1-1` | LABEL object MUST be present in RESV messages; each hop allocates and reports its label upstream (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestBuildResvRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/build_test.go#L75). **negative:** `unit/verify` [`TestEngineResvWithoutLabelRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/engine_test.go#L364) | | `RFC3209-4.1-2` | Upper 12 bits of the LABEL value MUST be zero for MPLS (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRSVPLabelObject`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L296). **negative:** `unit/verify` [`TestRSVPLabelObject`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L297) | | `RFC3209-4.3.4-1` | ERO processing: transit node MUST remove itself (first subobject) from ERO before forwarding PATH (S4.3.4) | MUST | 4.3.4 | **positive:** `unit/verify` [`TestEngineTransitForwarding`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/engine_test.go#L195). **negative:** `unit/verify` [`TestEngineTransitNoUsableERONextHop`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/engine_test.go#L403) | | `RFC3209-4.3.4.1-1` | ERO strict hop: next hop MUST be directly connected; reject PATH if not adjacent (S4.3.4.1) | MUST | 4.3.4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** nextHopFromERO takes rem[0] as next hop regardless of the L (loose/strict) bit and performs no adjacency check; the Loose flag is decoded and only used for display/config, so a non-adjacent strict hop is neither validated nor rejected (internal/plugins/rsvpte/wire.go:455, :467, engine.go:407-416) | | `RFC3209-4.1-3` | Reserved label values 0-15: only label 3 (Implicit NULL) MAY be allocated for penultimate hop popping (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestLSPTableAllocateSkipsReservedLabels`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/fsm_test.go#L70). **negative:** no negative test. **{single-polarity}:** the local label allocator starts at firstDynamicLabel=1000 and wraps back to 1000, so ze never hands out any label in 0-15 and there is no receive path that would allocate a reserved label (internal/plugins/rsvpte/fsm.go:184, :205, :215-217) | | `RFC3209-6-1` | Make-before-break: old and new LSPs MUST have the same SESSION (same Tunnel Endpoint, Tunnel ID, Extended Tunnel ID); only LSP ID differs (S6) | MUST | 6 | **positive:** `unit/verify` [`TestEngineMakeBeforeBreak`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/reroute_test.go#L41). **negative:** `unit/verify` [`TestSEAdmissionDistinctSessionsDoNotShare`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/admission_se_test.go#L35) | | `RFC3209-6-2` | SE (Shared Explicit) style MUST be used for make-before-break rerouting (S6) | MUST | 6 | **positive:** `unit/verify` [`TestBuildResvRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/build_test.go#L78). **negative:** no negative test. **{single-polarity}:** every RESV ze originates carries STYLE = Shared Explicit (18) and admission always applies SE sharing semantics; ze never emits a Fixed-Filter style for an LSP tunnel (internal/plugins/rsvpte/engine.go:272, build.go:126, wire.go:680) | | `RFC3209-x-1` | PATH messages MUST include the Router Alert IP option (Transport) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{gap}:** the raw IPv4 socket (proto 46) is opened and used for Sendto with no IP_ROUTER_ALERT socket option and no per-packet IP option, so emitted PATH datagrams carry no Router Alert (the same transport gap as RFC2205-x-1) (internal/plugins/rsvpte/transport_linux.go:35-58, :60-69) | | `RFC3209-2.5-1` | Both PATH and RESV messages MUST be refreshed periodically for soft-state maintenance (S2.5) | MUST | 2.5 | **positive:** `unit/verify` [`TestRefreshResendsPathAndResv`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/softstate_test.go#L56). **negative:** `unit/verify` [`TestRefreshDoesNotStampEgressPSB`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/softstate_test.go#L21) | | `RFC3209-x-2` | Admission control failure SHOULD generate PathErr with Error Code 1 (Admission Control) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3209-4.4-1` | RRO MAY be included in PATH and RESV to record the actual path taken (S4.4) | MAY | 4.4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3209-4.3.4.1-1`](#rfc3209-4.3.4.1-1) ERO strict hop: next hop MUST be directly connected; reject PATH if not adjacent (S4.3.4.1) | {gap}, no test | nextHopFromERO takes rem[0] as next hop regardless of the L (loose/strict) bit and performs no adjacency check; the Loose flag is decoded and only used for display/config, so a non-adjacent strict hop is neither validated nor rejected (internal/plugins/rsvpte/wire.go:455, :467, engine.go:407-416) | | [`RFC3209-x-1`](#rfc3209-x-1) PATH messages MUST include the Router Alert IP option (Transport) | {gap}, no test | the raw IPv4 socket (proto 46) is opened and used for Sendto with no IP_ROUTER_ALERT socket option and no per-packet IP option, so emitted PATH datagrams carry no Router Alert (the same transport gap as RFC2205-x-1) (internal/plugins/rsvpte/transport_linux.go:35-58, :60-69) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3209-4.6.1-1`](#rfc3209-4.6.1-1) SESSION Object reserved field MUST be zero (S4.6.1, Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRSVPSessionObjectEncoding`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L338) | unit/verify | unproven | ### [`RFC3209-4.6.2-1`](#rfc3209-4.6.2-1) SENDER_TEMPLATE Object reserved field MUST be zero (S4.6.2, Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRSVPSenderTemplateReservedZeroOnSend`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L368) | unit/verify | unproven | ### [`RFC3209-4.6.1-2`](#rfc3209-4.6.1-2) SESSION C-Type 7 (LSP_TUNNEL_IPv4) MUST be used for RSVP-TE LSP tunnels (S4.6.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRSVPSessionObjectEncoding`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L337) | unit/verify | unproven | ### [`RFC3209-4.2-1`](#rfc3209-4.2-1) LABEL_REQUEST object MUST be present in PATH messages to request label allocation (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestBuildPathRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/build_test.go#L45) | unit/verify | unproven | ### [`RFC3209-4.1-1`](#rfc3209-4.1-1) LABEL object MUST be present in RESV messages; each hop allocates and reports its label upstream (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEngineResvWithoutLabelRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/engine_test.go#L364) | unit/verify | unproven | | positive | [`TestBuildResvRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/build_test.go#L75) | unit/verify | unproven | ### [`RFC3209-4.1-2`](#rfc3209-4.1-2) Upper 12 bits of the LABEL value MUST be zero for MPLS (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRSVPLabelObject`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L297) | unit/verify | unproven | | positive | [`TestRSVPLabelObject`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/wire_test.go#L296) | unit/verify | unproven | ### [`RFC3209-4.3.4-1`](#rfc3209-4.3.4-1) ERO processing: transit node MUST remove itself (first subobject) from ERO before forwarding PATH (S4.3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEngineTransitNoUsableERONextHop`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/engine_test.go#L403) | unit/verify | unproven | | positive | [`TestEngineTransitForwarding`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/engine_test.go#L195) | unit/verify | unproven | ### [`RFC3209-4.3.4.1-1`](#rfc3209-4.3.4.1-1) ERO strict hop: next hop MUST be directly connected; reject PATH if not adjacent (S4.3.4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3209-4.3.4.1-1, so no unit is bound to it. ### [`RFC3209-4.1-3`](#rfc3209-4.1-3) Reserved label values 0-15: only label 3 (Implicit NULL) MAY be allocated for penultimate hop popping (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestLSPTableAllocateSkipsReservedLabels`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/fsm_test.go#L70) | unit/verify | unproven | ### [`RFC3209-6-1`](#rfc3209-6-1) Make-before-break: old and new LSPs MUST have the same SESSION (same Tunnel Endpoint, Tunnel ID, Extended Tunnel ID); only LSP ID differs (S6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSEAdmissionDistinctSessionsDoNotShare`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/admission_se_test.go#L35) | unit/verify | unproven | | positive | [`TestEngineMakeBeforeBreak`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/reroute_test.go#L41) | unit/verify | unproven | ### [`RFC3209-6-2`](#rfc3209-6-2) SE (Shared Explicit) style MUST be used for make-before-break rerouting (S6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestBuildResvRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/build_test.go#L78) | unit/verify | unproven | ### [`RFC3209-x-1`](#rfc3209-x-1) PATH messages MUST include the Router Alert IP option (Transport) Audit verdict: not audited: no reader has judged these tests No test carries RFC3209-x-1, so no unit is bound to it. ### [`RFC3209-2.5-1`](#rfc3209-2.5-1) Both PATH and RESV messages MUST be refreshed periodically for soft-state maintenance (S2.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRefreshDoesNotStampEgressPSB`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/softstate_test.go#L21) | unit/verify | unproven | | positive | [`TestRefreshResendsPathAndResv`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/softstate_test.go#L56) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 3209, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 3209, so its obligations are stated where they were written. --- ### Page: RFC 3579 - RADIUS (Remote Authentication Dial In User Service) Support For Extensible Authentication Protocol (EAP) https://ze-software.net/quality/rfc-compliance/rfc3579/ # RFC 3579 - RADIUS (Remote Authentication Dial In User Service) Support For Extensible Authentication Protocol (EAP) Partial. Every requirement this repository extracted from RFC 3579, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 54.8% | 17 of 31 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 31 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 31 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 94.3% | 33 of 35 tagged units, 2 escaped and 0 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 31 | of 50 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 31 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 31 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 31 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 31 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 45.2% | 14 of 31 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 31 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 50 | | Gated MUST-level | 31 | | Not applicable, so out of scope | 0 | | Declared gaps | 14 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 35 | | Tagged units | 35 | | Recorded audit verdicts | 0 | | Discrimination records | 35 | | Summary | `rfc/short/rfc3579.md` | | Requirement shard | `rfc/requirements/rfc3579.md` | | RFC text | `rfc/full/rfc3579.txt` | ## Enrolment Enrolled: RADIUS/EAP, ze in the NAS (RADIUS client) role: 31 MUST-level requirements. Ze runs the conversation this document describes, for operator login. `internal/component/radius/dict.go` declares EAP-Message at 79, `SignMessageAuthenticator` (`internal/component/radius/packet.go`) signs every EAP-bearing Access-Request beside the two verifiers that were there first, and `authenticateEAP` (`internal/component/radius/authenticator_eap.go`) answers each Access-Challenge instead of rejecting it. The obligations addressed to the RADIUS authentication server, to a RADIUS proxy and to a security server are excluded in `rfc/extraction/rfc3579.json` as `binds-another-role`: ze binds an ephemeral UDP socket in `NewClient` and `(*Client).readLoop` dispatches a datagram only against a waiter `(*Client).Exchange` registered for its own outstanding request, so no Access-Request receive path exists anywhere in the tree. ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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`, [`internal/component/radius/eap.go`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap.go)), signs every EAP-bearing Access-Request (`SignMessageAuthenticator`, [`internal/component/radius/packet.go`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet.go)), silently discards a reply whose Message-Authenticator does not verify (`(*Client).dispatchResponse`, [`internal/component/radius/client.go`](https://github.com/ze-software/ze/blob/main/internal/component/radius/client.go)), and answers each Access-Challenge with the peer's EAP-Response and the challenge's State unmodified (`authenticateEAP`, [`internal/component/radius/authenticator_eap.go`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap.go)). 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. **What the ledger says remains** 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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 17 | one part of the gated population | | Annotated instead of tested | 14 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **31** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (17):** [`RFC3579-1-1`](#rfc3579-1-1), [`RFC3579-1.2-1`](#rfc3579-1.2-1), [`RFC3579-2.1-1`](#rfc3579-2.1-1), [`RFC3579-2.1-3`](#rfc3579-2.1-3), [`RFC3579-2.2-1`](#rfc3579-2.2-1), [`RFC3579-2.6.3-1`](#rfc3579-2.6.3-1), [`RFC3579-2.6.3-2`](#rfc3579-2.6.3-2), [`RFC3579-2.6.4-1`](#rfc3579-2.6.4-1), [`RFC3579-3-1`](#rfc3579-3-1), [`RFC3579-3.1-1`](#rfc3579-3.1-1), [`RFC3579-3.1-2`](#rfc3579-3.1-2), [`RFC3579-3.1-3`](#rfc3579-3.1-3), [`RFC3579-3.1-4`](#rfc3579-3.1-4), [`RFC3579-3.2-1`](#rfc3579-3.2-1), [`RFC3579-3.3-1`](#rfc3579-3.3-1), [`RFC3579-3.3-2`](#rfc3579-3.3-2), [`RFC3579-4.3.6-2`](#rfc3579-4.3.6-2) **Annotated instead of tested (14):** [`RFC3579-1-2`](#rfc3579-1-2), [`RFC3579-1.2-2`](#rfc3579-1.2-2), [`RFC3579-2.1-2`](#rfc3579-2.1-2), [`RFC3579-2.2-2`](#rfc3579-2.2-2), [`RFC3579-2.6.3-3`](#rfc3579-2.6.3-3), [`RFC3579-2.6.5-1`](#rfc3579-2.6.5-1), [`RFC3579-4.2-1`](#rfc3579-4.2-1), [`RFC3579-4.2-2`](#rfc3579-4.2-2), [`RFC3579-4.2-3`](#rfc3579-4.2-3), [`RFC3579-4.3.4-1`](#rfc3579-4.3.4-1), [`RFC3579-4.3.6-1`](#rfc3579-4.3.6-1), [`RFC3579-4.3.6-3`](#rfc3579-4.3.6-3), [`RFC3579-4.3.6-4`](#rfc3579-4.3.6-4), [`RFC3579-4.3.6-5`](#rfc3579-4.3.6-5) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3579-1-1` | "a NAS that is unable to offer EAP service MUST NOT implement the RADIUS attributes for EAP" (§1) | MUST NOT | 1 - Introduction | **positive:** `unit/verify` [`TestRFC2869DictionaryCoversTheServicesZeOffers`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L110). **negative:** `unit/verify` [`TestRFC2869DictionaryDeclaresNoAttributeForAnUnofferedService`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L150) | | `RFC3579-1-2` | "A NAS MUST treat a RADIUS Access-Accept requesting an unavailable service as an Access-Reject instead" (§1) | MUST | 1 - Introduction | **positive:** no positive test. **negative:** no negative test. **{gap}:** `(*radiusAuthenticator).result` rejects an Access-Accept whose Service-Type is not Login-User (`AcceptedServiceType`, internal/component/radius/attr.go), which is the RFC 2865 form of this rule and covers the EAP path too, because authenticateEAP returns through that same function. What stays untested is this document's own form of the rule, an Accept concluding an EAP conversation ze could not run: `parseAuthMethod` refuses an unknown auth-method at config load, so ze only ever runs a method it offers and the branch has no input | | `RFC3579-1.2-1` | A displayable message "MUST NOT affect operation of the protocol" (§1.2) | MUST NOT | 1.2 - Terminology | **positive:** `unit/verify` [`TestRadiusAdminEapNotificationIsAnsweredAndLogged`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L359). **negative:** `unit/verify` [`TestRadiusAdminEapNotificationDoesNotSteerTheLogin`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L396) | | `RFC3579-1.2-2` | "The message encoding MUST follow the UTF-8 transformation format [RFC2279]" (§1.2) | MUST | 1.2 - Terminology | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze now DECODES a displayable message -- `(*PeerSession).notificationResponse` (internal/core/eap/peer.go) carries the Type-2 Request's text out and `processEAPMessage` (internal/component/radius/authenticator_eap.go) logs it -- and encodes none. This sentence binds whoever encodes the message, and ze writes no Reply-Message and sends no Notification Request, so nothing here chooses an encoding to assert | | `RFC3579-2.1-1` | "Reception of a RADIUS Access-Reject packet MUST result in the NAS denying access to the authenticating peer" (§2.1, restated later in §2.1) | MUST | 2.1 - Protocol Overview | **positive:** `unit/verify` [`TestRadiusAdminEapAccessRejectDeniesAccess`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L430). **negative:** `unit/verify` [`TestRadiusAdminEapAccessRejectDeniesEvenCarryingEAPSuccess`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L455) | | `RFC3579-2.1-2` | "The NAS MUST NOT \\"manufacture\\" a Success or Failure packet as the result of a timeout" (§2.1) | MUST NOT | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test. **{gap}:** every EAP packet ze puts on the RADIUS wire came out of `(*eap.PeerSession).Process` (internal/core/eap/peer.go), and a timeout returns an error from `(*Client).SendToServers` that `authenticateEAP` passes up, so no branch manufactures a Success or Failure. Nothing tags the prohibition, because a test would have to observe a packet no code path can build | | `RFC3579-2.1-3` | "if the NAS initially sends an EAP-Request/Identity message to the peer, the NAS MUST copy the contents of the Type-Data field of the EAP-Response/Identity received from the peer into the User-Name attribute and MUST include the Type-Data field of the EAP-Response/Identity in the User-Name attribute in every subsequent Access-Request" (§2.1) | MUST | 2.1 - Protocol Overview | **positive:** `unit/verify` [`TestRadiusAdminEapUserNameIsThePeerIdentity`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L74). **negative:** `unit/verify` [`TestRadiusAdminEapUserNameRidesEverySubsequentRequest`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L109) | | `RFC3579-2.2-1` | "the NAS MUST validate the EAP header fields (Code, Identifier, Length) prior to forwarding an EAP packet to or from the RADIUS server" (§2.2) | MUST | 2.2 - Invalid Packets | **positive:** `unit/verify` [`TestRadiusAdminEapReadsTheServerEAPHeaderFields`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L152). **negative:** `unit/verify` [`TestRadiusAdminEapRefusesAMalformedServerEAPHeader`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L190) | | `RFC3579-2.2-2` | On an Access-Challenge carrying Error-Cause 202, "a new EAP-Response packet, if available, MUST be sent to the RADIUS server within an Access-Request, and the EAP-Message attribute(s) included within the Access-Challenge are silently discarded" (§2.2) | MUST | 2.2 - Invalid Packets | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze reads no Error-Cause attribute, so an Access-Challenge carrying 202 is answered like any other. The MUST is conditional -- "If so" refers to additional EAP-Response packets received matching the current Identifier -- and ze holds no such queue: it is its own peer, and `(*eap.PeerSession).Process` (internal/core/eap/peer.go) returns exactly one Response per Request. So the antecedent never holds and there is no second Response to send | | `RFC3579-2.6.3-1` | "The NAS MUST make its access control decision based solely on the RADIUS Packet Type (Access-Accept/Access-Reject)" (§2.6.3) | MUST | 2.6.3 - Conflicting Messages | **positive:** `unit/verify` [`TestRadiusAdminEapDecisionFollowsTheRadiusCode`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L586). **negative:** `unit/verify` [`TestRadiusAdminEapAcceptWithEapFailureStillAuthorizes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L863) | | `RFC3579-2.6.3-2` | "The access control decision MUST NOT be based on the contents of the EAP packet encapsulated in one or more EAP-Message attributes, if present" (§2.6.3) | MUST NOT | 2.6.3 - Conflicting Messages | **positive:** `unit/verify` [`TestRadiusAdminEapAcceptWithEapFailureStillAuthorizes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L867). **negative:** `unit/verify` [`TestRadiusAdminEapDecisionFollowsTheRadiusCode`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L589) | | `RFC3579-2.6.3-3` | "the NAS MUST NOT \\"manufacture\\" EAP packets in order to correct contradictory messages that it receives" (§2.6.3) | MUST NOT | 2.6.3 - Conflicting Messages | **positive:** no positive test. **negative:** no negative test. **{gap}:** `authenticateEAP` (internal/component/radius/authenticator_eap.go) sends only what `(*eap.PeerSession).Process` returned, and on a contradictory reply it logs the peer's objection and drops it rather than correcting it. Nothing tags the prohibition, because a test would have to observe a manufactured packet no code path can build | | `RFC3579-2.6.4-1` | "the NAS MUST first process the attributes, including the EAP-Message attribute(s), prior to processing the Accept/Reject indication" (§2.6.4) | MUST | 2.6.4 - Priority | **positive:** `unit/verify` [`TestRadiusAdminEapProcessesTheEAPMessageBeforeTheCode`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L230). **negative:** `unit/verify` [`TestRadiusAdminEapProcessesAnUnparseableEAPMessageBeforeTheCode`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L260) | | `RFC3579-2.6.5-1` | "Reply-Message attribute(s) MUST NOT be included in any RADIUS message containing an EAP-Message attribute" (§2.6.5) | MUST NOT | 2.6.5 - Displayable Messages | **positive:** no positive test. **negative:** no negative test. **{gap}:** `eapCredential` (internal/component/radius/authenticator_eap.go) builds an EAP-bearing Access-Request from the EAP-Message run, the Message-Authenticator and the server's State, and appends no Reply-Message; `exchange` adds Service-Type, NAS-Identifier, User-Name and NAS-IP-Address, none of them Reply-Message. The exclusion holds by construction and no test tags it | | `RFC3579-3-1` | "either NAS-Identifier, NAS-IP-Address or NAS-IPv6-Address attributes MUST be included" in an Access-Request (§3) | MUST | 3 - Attributes | **positive:** `unit/verify` [`TestRadiusAdminEapAccessRequestNamesTheNAS`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L292). **negative:** `unit/verify` [`TestRadiusAdminEapNamesTheNASWithoutASourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L327) | | `RFC3579-3.1-1` | "If multiple EAP-Message attributes are contained within an Access-Request or Access-Challenge packet, they MUST be in order and they MUST be consecutive attributes" (§3.1) | MUST | 3.1 - EAP-Message | **positive:** `unit/verify` [`TestEAPMessageConcatenatesOnTheWayIn`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap_test.go#L135). **positive:** `unit/verify` [`TestEAPMessageSplitsAtTheAttributeLimit`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap_test.go#L51). **negative:** `unit/verify` [`TestEAPMessageKeepsTheRunConsecutive`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap_test.go#L95) | | `RFC3579-3.1-2` | "Multiple EAP packets MUST NOT be encoded within EAP-Message attributes contained within a single Access-Challenge, Access-Accept, Access-Reject or Access-Request packet" (§3.1) | MUST NOT | 3.1 - EAP-Message | **positive:** `unit/verify` [`TestRadiusAdminEapOneEAPPacketPerRadiusPacket`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L548). **negative:** `unit/verify` [`TestEAPMessageRefusesASecondRun`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap_test.go#L166) | | `RFC3579-3.1-3` | "the Message-Authenticator attribute MUST be used to protect all Access-Request, Access-Challenge, Access-Accept, and Access-Reject packets containing an EAP-Message attribute" (§3.1, restated §3.2, §3.3 Note 1 and §4.3.2) | MUST | 3.1 - EAP-Message | **positive:** `unit/verify` [`TestRadiusAdminEapAccessRequestIsSignedAndCarriesEAPMessage`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L375). **negative:** `unit/verify` [`TestEAPRequestWithoutMessageAuthenticatorIsRefused`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L732) | | `RFC3579-3.1-4` | "A NAS supporting the EAP-Message attribute MUST calculate the correct value of the Message-Authenticator and MUST silently discard the packet if it does not match the value sent" (§3.1, restated for any RADIUS client receiving an Access-Accept, Access-Reject or Access-Challenge at §3.2) | MUST | 3.1 - EAP-Message | **positive:** `unit/verify` [`TestRadiusAdminEapVerifiedChallengeIsAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L898). **negative:** `unit/verify` [`TestRadiusAdminEapDiscardsUnauthenticatedChallenge`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L484) | | `RFC3579-3.2-1` | "Message-Authenticator = HMAC-MD5 (Type, Identifier, Length, Request Authenticator, Attributes)", keyed with the shared secret, with the signature string "considered to be sixteen octets of zero" and the value inserted before the Response Authenticator is calculated (§3.2) | MUST | 3.2 - Message-Authenticator | **positive:** `unit/verify` [`TestSignMessageAuthenticatorMatchesRFC3579`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L438). **negative:** `unit/verify` [`TestSignMessageAuthenticatorZeroesTheSignatureField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L472) | | `RFC3579-3.3-1` | "The EAP-Message and Message-Authenticator attributes specified in this document MUST NOT be present in an Accounting-Request" (§3.3) | MUST NOT | 3.3 - Table of Attributes | **positive:** `unit/verify` [`TestAccountingRequestRefusesEAPAttributes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L691). **negative:** `unit/verify` [`TestAccountingRequestRefusesEAPAttributes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L688) | | `RFC3579-3.3-2` | "An Access-Request that contains either a User-Password or CHAP-Password or ARAP-Password or one or more EAP-Message attributes MUST NOT contain more than one type of those four attributes" (§3.3 Note 1) | MUST NOT | 3.3 - Table of Attributes | **positive:** `unit/verify` [`TestRadiusAdminEapAccessRequestIsSignedAndCarriesEAPMessage`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L379). **negative:** `unit/verify` [`TestAccessRequestRefusesTwoCredentialTypes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L938) | | `RFC3579-4.2-1` | Where RADIUS runs over IPsec ESP with a non-null transform and no shared secret is configured, "a shared secret of zero length MUST be assumed" (§4.2) | MUST | 4.2 - Security Protocol | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze offers no way to run RADIUS over an IPsec SA and its RADIUS server configuration always carries a secret (`internal/component/radius/config.go`), so the zero-length case is unreachable; spec-radius-admin-eap does not add it | | `RFC3579-4.2-2` | "When IPsec ESP is used with RADIUS, per-packet authentication, integrity and replay protection MUST be used. 3DES-CBC MUST be supported as an encryption transform" (§4.2) | MUST | 4.2 - Security Protocol | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze has no RADIUS-over-IPsec profile, so nothing selects a transform for RADIUS traffic; spec-radius-admin-eap does not add it | | `RFC3579-4.2-3` | "HMAC-SHA1-96 MUST be supported as an authentication transform" (§4.2) | MUST | 4.2 - Security Protocol | **positive:** no positive test. **negative:** no negative test. **{gap}:** same absent RADIUS-over-IPsec profile; ze negotiates no IPsec SA for RADIUS traffic; spec-radius-admin-eap does not add it | | `RFC3579-4.3.4-1` | "the RADIUS shared secret used by a NAS supporting EAP MUST NOT be reused by a NAS utilizing the User-Password attribute" (§4.3.4) | MUST NOT | 4.3.4 - Known Plaintext Attacks | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze holds both the admin backend's secret and the subscriber backend's secret and compares neither, so a configuration reusing one secret across an EAP NAS and a PAP NAS is accepted in silence; spec-radius-admin-eap | | `RFC3579-4.3.6-1` | "Should the NAS not be able to negotiate EAP, or should the EAP-Request sent by the NAS be of a different EAP type than what is expected, the authenticating peer MUST disconnect" (§4.3.6) | MUST | 4.3.6 - Negotiation Attacks | **positive:** no positive test. **negative:** no negative test. **{gap}:** `(*eap.PeerSession).Process` (internal/core/eap/peer.go) NAKs toward its one configured method rather than answering a Request of another type, and a server that insists ends the exchange with an error that `authenticateEAP` returns, which ends the login. What is untested is that ending read as this section's disconnect | | `RFC3579-4.3.6-2` | "An authenticating peer expecting EAP to be negotiated for a session MUST NOT negotiate a weaker method, such as CHAP or PAP" (§4.3.6) | MUST NOT | 4.3.6 - Negotiation Attacks | **positive:** `unit/verify` [`TestRadiusAdminEapNeverSendsAPasswordCredential`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L483). **negative:** `unit/verify` [`TestRadiusAdminEapDoesNotDowngradeAfterARejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L515) | | `RFC3579-4.3.6-3` | "if any peers of the NAS MUST do EAP, then the NAS MUST attempt to negotiate EAP for every session" (§4.3.6) | MUST | 4.3.6 - Negotiation Attacks | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's PPP NAS offers PAP, CHAP-MD5 and MS-CHAPv2 and never EAP (`internal/component/l2tp/ppp`), so it cannot attempt to negotiate EAP for any session; spec-radius-admin-eap | | `RFC3579-4.3.6-4` | "The authenticating peer MUST refuse to renegotiate authentication, even if the renegotiation is from CHAP to EAP" (§4.3.6, restated in the same section for an EAP-capable peer) | MUST | 4.3.6 - Negotiation Attacks | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's admin login has no renegotiation path, and its PPP NAS never negotiates EAP, so nothing refuses a renegotiation; spec-radius-admin-eap | | `RFC3579-4.3.6-5` | Where EAP was negotiated and the RADIUS server or proxy does not support it, "a PPP NAS MUST send an LCP-Terminate and disconnect the peer" (§4.3.6) | MUST | 4.3.6 - Negotiation Attacks | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's PPP NAS never negotiates EAP, so the branch that would send LCP-Terminate on an EAP-incapable server does not exist; spec-radius-admin-eap | | `RFC3579-1.2-3` | An implementation that silently discards a packet "SHOULD provide the capability of logging the error, including the contents of the silently discarded packet, and SHOULD record the event in a statistics counter" (§1.2) | SHOULD | 1.2 - Terminology | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.1-4` | "Once EAP has been negotiated, the NAS SHOULD send an initial EAP-Request message to the authenticating peer" (§2.1) | SHOULD | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.1-7` | "After a suitable number of timeouts have elapsed, the NAS SHOULD instead end the EAP conversation" (§2.1) | SHOULD | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.1-8` | On an EAP-Response/Nak "the NAS SHOULD send Access-Request encapsulating the received EAP-Response/Nak" (§2.1) | SHOULD | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.1-9` | Where the peer identity cannot be determined from the EAP-Response, "the User-Name attribute SHOULD be determined by another means" (§2.1) | SHOULD | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.1-11` | Where EAP-unaware peers are common, the EAP-Start technique "SHOULD NOT be employed by default" (§2.1) | SHOULD NOT | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.2-3` | A NAS receiving an Access-Challenge with Error-Cause 202 "SHOULD discard the EAP-Response packet most recently transmitted to the RADIUS server and check whether additional EAP-Response packets have been received matching the current Identifier value" (§2.2) | SHOULD | 2.2 - Invalid Packets | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.6.5-2` | "a NAS receiving a Reply-Message attribute from the RADIUS server SHOULD silently discard the attribute, rather than attempting to translate it to an EAP Notification Request" (§2.6.5) | SHOULD | 2.6.5 - Displayable Messages | **positive:** no positive test. **negative:** no negative test | | `RFC3579-3-2` | "The NAS-Port or NAS-Port-Id attributes SHOULD be included by the NAS in Access-Request packets" (§3) | SHOULD | 3 - Attributes | **positive:** no positive test. **negative:** no negative test | | `RFC3579-3.1-5` | "When RADIUS is used to enable EAP authentication, Access-Request, Access-Challenge, Access-Accept, and Access-Reject packets SHOULD contain one or more EAP-Message attributes" (§3.1) | SHOULD | 3.1 - EAP-Message | **positive:** no positive test. **negative:** no negative test | | `RFC3579-3.1-6` | "Access-Challenge, Access-Accept, or Access-Reject packets including EAP-Message attribute(s) without a Message-Authenticator attribute SHOULD be silently discarded by the NAS" (§3.1) | SHOULD | 3.1 - EAP-Message | **positive:** no positive test. **negative:** no negative test | | `RFC3579-4.2-4` | "implementations of this specification SHOULD support IPsec [RFC2401] along with IKE [RFC2409] for key management", and IPsec ESP with a non-null encryption transform and authentication "SHOULD be used" (§4.2) | SHOULD | 4.2 - Security Protocol | **positive:** no positive test. **negative:** no negative test | | `RFC3579-4.2-5` | "AES-CBC SHOULD be supported" and "SHOULD be offered as a preferred encryption transform if supported", and "DES-CBC SHOULD NOT be used as the encryption transform" (§4.2) | SHOULD | 4.2 - Security Protocol | **positive:** no positive test. **negative:** no negative test | | `RFC3579-4.3.2-1` | "the Message-Authenticator attribute SHOULD be used in Access-Request packets that do not have a User-Password attribute, in order to establish the identity of the NAS sending the request" (§4.3.2) | SHOULD | 4.3.2 - Spoofing and Hijacking | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2-1` | "A NAS MAY authenticate local peers while at the same time acting as a pass-through for non-local peers and authentication methods it does not implement locally" (§2) | MAY | 2 - RADIUS Support for EAP | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.1-5` | "A NAS MAY be configured to initiate with a default authentication method" (§2.1) | MAY | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.1-6` | "the NAS MAY act as a pass-through, encapsulating the EAP-Response within EAP-Message attribute(s) sent to the RADIUS server within a RADIUS Access-Request packet" (§2.1) | MAY | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test | | `RFC3579-2.1-10` | "on detecting the presence of the peer, the NAS MAY send an Access-Request packet to the RADIUS server containing an EAP-Message attribute signifying EAP-Start" (§2.1) | MAY | 2.1 - Protocol Overview | **positive:** no positive test. **negative:** no negative test | | `RFC3579-3.2-2` | The Message-Authenticator attribute "MAY be used to authenticate and integrity-protect Access-Requests in order to prevent spoofing" and "MAY be used in any Access-Request" (§3.2) | MAY | 3.2 - Message-Authenticator | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3579-1-2`](#rfc3579-1-2) "A NAS MUST treat a RADIUS Access-Accept requesting an unavailable service as an Access-Reject instead" (§1) | {gap}, no test | `(*radiusAuthenticator).result` rejects an Access-Accept whose Service-Type is not Login-User (`AcceptedServiceType`, internal/component/radius/attr.go), which is the RFC 2865 form of this rule and covers the EAP path too, because authenticateEAP returns through that same function. What stays untested is this document's own form of the rule, an Accept concluding an EAP conversation ze could not run: `parseAuthMethod` refuses an unknown auth-method at config load, so ze only ever runs a method it offers and the branch has no input | | [`RFC3579-1.2-2`](#rfc3579-1.2-2) "The message encoding MUST follow the UTF-8 transformation format [RFC2279]" (§1.2) | {gap}, no test | ze now DECODES a displayable message -- `(*PeerSession).notificationResponse` (internal/core/eap/peer.go) carries the Type-2 Request's text out and `processEAPMessage` (internal/component/radius/authenticator_eap.go) logs it -- and encodes none. This sentence binds whoever encodes the message, and ze writes no Reply-Message and sends no Notification Request, so nothing here chooses an encoding to assert | | [`RFC3579-2.1-2`](#rfc3579-2.1-2) "The NAS MUST NOT \\"manufacture\\" a Success or Failure packet as the result of a timeout" (§2.1) | {gap}, no test | every EAP packet ze puts on the RADIUS wire came out of `(*eap.PeerSession).Process` (internal/core/eap/peer.go), and a timeout returns an error from `(*Client).SendToServers` that `authenticateEAP` passes up, so no branch manufactures a Success or Failure. Nothing tags the prohibition, because a test would have to observe a packet no code path can build | | [`RFC3579-2.2-2`](#rfc3579-2.2-2) On an Access-Challenge carrying Error-Cause 202, "a new EAP-Response packet, if available, MUST be sent to the RADIUS server within an Access-Request, and the EAP-Message attribute(s) included within the Access-Challenge are silently discarded" (§2.2) | {gap}, no test | ze reads no Error-Cause attribute, so an Access-Challenge carrying 202 is answered like any other. The MUST is conditional -- "If so" refers to additional EAP-Response packets received matching the current Identifier -- and ze holds no such queue: it is its own peer, and `(*eap.PeerSession).Process` (internal/core/eap/peer.go) returns exactly one Response per Request. So the antecedent never holds and there is no second Response to send | | [`RFC3579-2.6.3-3`](#rfc3579-2.6.3-3) "the NAS MUST NOT \\"manufacture\\" EAP packets in order to correct contradictory messages that it receives" (§2.6.3) | {gap}, no test | `authenticateEAP` (internal/component/radius/authenticator_eap.go) sends only what `(*eap.PeerSession).Process` returned, and on a contradictory reply it logs the peer's objection and drops it rather than correcting it. Nothing tags the prohibition, because a test would have to observe a manufactured packet no code path can build | | [`RFC3579-2.6.5-1`](#rfc3579-2.6.5-1) "Reply-Message attribute(s) MUST NOT be included in any RADIUS message containing an EAP-Message attribute" (§2.6.5) | {gap}, no test | `eapCredential` (internal/component/radius/authenticator_eap.go) builds an EAP-bearing Access-Request from the EAP-Message run, the Message-Authenticator and the server's State, and appends no Reply-Message; `exchange` adds Service-Type, NAS-Identifier, User-Name and NAS-IP-Address, none of them Reply-Message. The exclusion holds by construction and no test tags it | | [`RFC3579-4.2-1`](#rfc3579-4.2-1) Where RADIUS runs over IPsec ESP with a non-null transform and no shared secret is configured, "a shared secret of zero length MUST be assumed" (§4.2) | {gap}, no test | ze offers no way to run RADIUS over an IPsec SA and its RADIUS server configuration always carries a secret (`internal/component/radius/config.go`), so the zero-length case is unreachable; spec-radius-admin-eap does not add it | | [`RFC3579-4.2-2`](#rfc3579-4.2-2) "When IPsec ESP is used with RADIUS, per-packet authentication, integrity and replay protection MUST be used. 3DES-CBC MUST be supported as an encryption transform" (§4.2) | {gap}, no test | ze has no RADIUS-over-IPsec profile, so nothing selects a transform for RADIUS traffic; spec-radius-admin-eap does not add it | | [`RFC3579-4.2-3`](#rfc3579-4.2-3) "HMAC-SHA1-96 MUST be supported as an authentication transform" (§4.2) | {gap}, no test | same absent RADIUS-over-IPsec profile; ze negotiates no IPsec SA for RADIUS traffic; spec-radius-admin-eap does not add it | | [`RFC3579-4.3.4-1`](#rfc3579-4.3.4-1) "the RADIUS shared secret used by a NAS supporting EAP MUST NOT be reused by a NAS utilizing the User-Password attribute" (§4.3.4) | {gap}, no test | ze holds both the admin backend's secret and the subscriber backend's secret and compares neither, so a configuration reusing one secret across an EAP NAS and a PAP NAS is accepted in silence; spec-radius-admin-eap | | [`RFC3579-4.3.6-1`](#rfc3579-4.3.6-1) "Should the NAS not be able to negotiate EAP, or should the EAP-Request sent by the NAS be of a different EAP type than what is expected, the authenticating peer MUST disconnect" (§4.3.6) | {gap}, no test | `(*eap.PeerSession).Process` (internal/core/eap/peer.go) NAKs toward its one configured method rather than answering a Request of another type, and a server that insists ends the exchange with an error that `authenticateEAP` returns, which ends the login. What is untested is that ending read as this section's disconnect | | [`RFC3579-4.3.6-3`](#rfc3579-4.3.6-3) "if any peers of the NAS MUST do EAP, then the NAS MUST attempt to negotiate EAP for every session" (§4.3.6) | {gap}, no test | ze's PPP NAS offers PAP, CHAP-MD5 and MS-CHAPv2 and never EAP (`internal/component/l2tp/ppp`), so it cannot attempt to negotiate EAP for any session; spec-radius-admin-eap | | [`RFC3579-4.3.6-4`](#rfc3579-4.3.6-4) "The authenticating peer MUST refuse to renegotiate authentication, even if the renegotiation is from CHAP to EAP" (§4.3.6, restated in the same section for an EAP-capable peer) | {gap}, no test | ze's admin login has no renegotiation path, and its PPP NAS never negotiates EAP, so nothing refuses a renegotiation; spec-radius-admin-eap | | [`RFC3579-4.3.6-5`](#rfc3579-4.3.6-5) Where EAP was negotiated and the RADIUS server or proxy does not support it, "a PPP NAS MUST send an LCP-Terminate and disconnect the peer" (§4.3.6) | {gap}, no test | ze's PPP NAS never negotiates EAP, so the branch that would send LCP-Terminate on an EAP-incapable server does not exist; spec-radius-admin-eap | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3579-1-1`](#rfc3579-1-1) "a NAS that is unable to offer EAP service MUST NOT implement the RADIUS attributes for EAP" (§1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC2869DictionaryDeclaresNoAttributeForAnUnofferedService`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L150) | unit/verify | no-break escape (declaration-only), which is not a proof, verified | | positive | [`TestRFC2869DictionaryCoversTheServicesZeOffers`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2869_unoffered_service_attributes_test.go#L110) | unit/verify | no-break escape (declaration-only), which is not a proof, verified | ### [`RFC3579-1-2`](#rfc3579-1-2) "A NAS MUST treat a RADIUS Access-Accept requesting an unavailable service as an Access-Reject instead" (§1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-1-2, so no unit is bound to it. ### [`RFC3579-1.2-1`](#rfc3579-1.2-1) A displayable message "MUST NOT affect operation of the protocol" (§1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapNotificationDoesNotSteerTheLogin`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L396) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapNotificationIsAnsweredAndLogged`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L359) | unit/verify | revert, verified | ### [`RFC3579-1.2-2`](#rfc3579-1.2-2) "The message encoding MUST follow the UTF-8 transformation format [RFC2279]" (§1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-1.2-2, so no unit is bound to it. ### [`RFC3579-2.1-1`](#rfc3579-2.1-1) "Reception of a RADIUS Access-Reject packet MUST result in the NAS denying access to the authenticating peer" (§2.1, restated later in §2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapAccessRejectDeniesEvenCarryingEAPSuccess`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L455) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapAccessRejectDeniesAccess`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L430) | unit/verify | revert, verified | ### [`RFC3579-2.1-2`](#rfc3579-2.1-2) "The NAS MUST NOT \"manufacture\" a Success or Failure packet as the result of a timeout" (§2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-2.1-2, so no unit is bound to it. ### [`RFC3579-2.1-3`](#rfc3579-2.1-3) "if the NAS initially sends an EAP-Request/Identity message to the peer, the NAS MUST copy the contents of the Type-Data field of the EAP-Response/Identity received from the peer into the User-Name attribute and MUST include the Type-Data field of the EAP-Response/Identity in the User-Name attribute in every subsequent Access-Request" (§2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapUserNameRidesEverySubsequentRequest`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L109) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapUserNameIsThePeerIdentity`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L74) | unit/verify | revert, verified | ### [`RFC3579-2.2-1`](#rfc3579-2.2-1) "the NAS MUST validate the EAP header fields (Code, Identifier, Length) prior to forwarding an EAP packet to or from the RADIUS server" (§2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapRefusesAMalformedServerEAPHeader`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L190) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapReadsTheServerEAPHeaderFields`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L152) | unit/verify | revert, verified | ### [`RFC3579-2.2-2`](#rfc3579-2.2-2) On an Access-Challenge carrying Error-Cause 202, "a new EAP-Response packet, if available, MUST be sent to the RADIUS server within an Access-Request, and the EAP-Message attribute(s) included within the Access-Challenge are silently discarded" (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-2.2-2, so no unit is bound to it. ### [`RFC3579-2.6.3-1`](#rfc3579-2.6.3-1) "The NAS MUST make its access control decision based solely on the RADIUS Packet Type (Access-Accept/Access-Reject)" (§2.6.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapAcceptWithEapFailureStillAuthorizes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L863) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapDecisionFollowsTheRadiusCode`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L586) | unit/verify | revert, verified | ### [`RFC3579-2.6.3-2`](#rfc3579-2.6.3-2) "The access control decision MUST NOT be based on the contents of the EAP packet encapsulated in one or more EAP-Message attributes, if present" (§2.6.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapDecisionFollowsTheRadiusCode`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L589) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapAcceptWithEapFailureStillAuthorizes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L867) | unit/verify | revert, verified | ### [`RFC3579-2.6.3-3`](#rfc3579-2.6.3-3) "the NAS MUST NOT \"manufacture\" EAP packets in order to correct contradictory messages that it receives" (§2.6.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-2.6.3-3, so no unit is bound to it. ### [`RFC3579-2.6.4-1`](#rfc3579-2.6.4-1) "the NAS MUST first process the attributes, including the EAP-Message attribute(s), prior to processing the Accept/Reject indication" (§2.6.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapProcessesAnUnparseableEAPMessageBeforeTheCode`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L260) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapProcessesTheEAPMessageBeforeTheCode`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L230) | unit/verify | revert, verified | ### [`RFC3579-2.6.5-1`](#rfc3579-2.6.5-1) "Reply-Message attribute(s) MUST NOT be included in any RADIUS message containing an EAP-Message attribute" (§2.6.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-2.6.5-1, so no unit is bound to it. ### [`RFC3579-3-1`](#rfc3579-3-1) "either NAS-Identifier, NAS-IP-Address or NAS-IPv6-Address attributes MUST be included" in an Access-Request (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapNamesTheNASWithoutASourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L327) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapAccessRequestNamesTheNAS`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L292) | unit/verify | revert, verified | ### [`RFC3579-3.1-1`](#rfc3579-3.1-1) "If multiple EAP-Message attributes are contained within an Access-Request or Access-Challenge packet, they MUST be in order and they MUST be consecutive attributes" (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEAPMessageKeepsTheRunConsecutive`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap_test.go#L95) | unit/verify | revert, verified | | positive | [`TestEAPMessageConcatenatesOnTheWayIn`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap_test.go#L135) | unit/verify | revert, verified | | positive | [`TestEAPMessageSplitsAtTheAttributeLimit`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap_test.go#L51) | unit/verify | revert, verified | ### [`RFC3579-3.1-2`](#rfc3579-3.1-2) "Multiple EAP packets MUST NOT be encoded within EAP-Message attributes contained within a single Access-Challenge, Access-Accept, Access-Reject or Access-Request packet" (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEAPMessageRefusesASecondRun`](https://github.com/ze-software/ze/blob/main/internal/component/radius/eap_test.go#L166) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapOneEAPPacketPerRadiusPacket`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L548) | unit/verify | revert, verified | ### [`RFC3579-3.1-3`](#rfc3579-3.1-3) "the Message-Authenticator attribute MUST be used to protect all Access-Request, Access-Challenge, Access-Accept, and Access-Reject packets containing an EAP-Message attribute" (§3.1, restated §3.2, §3.3 Note 1 and §4.3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEAPRequestWithoutMessageAuthenticatorIsRefused`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L732) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapAccessRequestIsSignedAndCarriesEAPMessage`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L375) | unit/verify | revert, verified | ### [`RFC3579-3.1-4`](#rfc3579-3.1-4) "A NAS supporting the EAP-Message attribute MUST calculate the correct value of the Message-Authenticator and MUST silently discard the packet if it does not match the value sent" (§3.1, restated for any RADIUS client receiving an Access-Accept, Access-Reject or Access-Challenge at §3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapDiscardsUnauthenticatedChallenge`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L484) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapVerifiedChallengeIsAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L898) | unit/verify | revert, verified | ### [`RFC3579-3.2-1`](#rfc3579-3.2-1) "Message-Authenticator = HMAC-MD5 (Type, Identifier, Length, Request Authenticator, Attributes)", keyed with the shared secret, with the signature string "considered to be sixteen octets of zero" and the value inserted before the Response Authenticator is calculated (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSignMessageAuthenticatorZeroesTheSignatureField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L472) | unit/verify | revert, verified | | positive | [`TestSignMessageAuthenticatorMatchesRFC3579`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L438) | unit/verify | revert, verified | ### [`RFC3579-3.3-1`](#rfc3579-3.3-1) "The EAP-Message and Message-Authenticator attributes specified in this document MUST NOT be present in an Accounting-Request" (§3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAccountingRequestRefusesEAPAttributes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L688) | unit/verify | revert, verified | | positive | [`TestAccountingRequestRefusesEAPAttributes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L691) | unit/verify | revert, verified | ### [`RFC3579-3.3-2`](#rfc3579-3.3-2) "An Access-Request that contains either a User-Password or CHAP-Password or ARAP-Password or one or more EAP-Message attributes MUST NOT contain more than one type of those four attributes" (§3.3 Note 1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAccessRequestRefusesTwoCredentialTypes`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L938) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapAccessRequestIsSignedAndCarriesEAPMessage`](https://github.com/ze-software/ze/blob/main/internal/component/radius/authenticator_eap_test.go#L379) | unit/verify | revert, verified | ### [`RFC3579-4.2-1`](#rfc3579-4.2-1) Where RADIUS runs over IPsec ESP with a non-null transform and no shared secret is configured, "a shared secret of zero length MUST be assumed" (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-4.2-1, so no unit is bound to it. ### [`RFC3579-4.2-2`](#rfc3579-4.2-2) "When IPsec ESP is used with RADIUS, per-packet authentication, integrity and replay protection MUST be used. 3DES-CBC MUST be supported as an encryption transform" (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-4.2-2, so no unit is bound to it. ### [`RFC3579-4.2-3`](#rfc3579-4.2-3) "HMAC-SHA1-96 MUST be supported as an authentication transform" (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-4.2-3, so no unit is bound to it. ### [`RFC3579-4.3.4-1`](#rfc3579-4.3.4-1) "the RADIUS shared secret used by a NAS supporting EAP MUST NOT be reused by a NAS utilizing the User-Password attribute" (§4.3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-4.3.4-1, so no unit is bound to it. ### [`RFC3579-4.3.6-1`](#rfc3579-4.3.6-1) "Should the NAS not be able to negotiate EAP, or should the EAP-Request sent by the NAS be of a different EAP type than what is expected, the authenticating peer MUST disconnect" (§4.3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-4.3.6-1, so no unit is bound to it. ### [`RFC3579-4.3.6-2`](#rfc3579-4.3.6-2) "An authenticating peer expecting EAP to be negotiated for a session MUST NOT negotiate a weaker method, such as CHAP or PAP" (§4.3.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRadiusAdminEapDoesNotDowngradeAfterARejection`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L515) | unit/verify | revert, verified | | positive | [`TestRadiusAdminEapNeverSendsAPasswordCredential`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc3579_nas_obligations_test.go#L483) | unit/verify | revert, verified | ### [`RFC3579-4.3.6-3`](#rfc3579-4.3.6-3) "if any peers of the NAS MUST do EAP, then the NAS MUST attempt to negotiate EAP for every session" (§4.3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-4.3.6-3, so no unit is bound to it. ### [`RFC3579-4.3.6-4`](#rfc3579-4.3.6-4) "The authenticating peer MUST refuse to renegotiate authentication, even if the renegotiation is from CHAP to EAP" (§4.3.6, restated in the same section for an EAP-capable peer) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-4.3.6-4, so no unit is bound to it. ### [`RFC3579-4.3.6-5`](#rfc3579-4.3.6-5) Where EAP was negotiated and the RADIUS server or proxy does not support it, "a PPP NAS MUST send an LCP-Terminate and disconnect the peer" (§4.3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3579-4.3.6-5, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, plan/spec-radius-admin-eap.md phase 2, rfc3579 | | Signed off | 2026-09-03 | | Register | rfc2119 | | Source | rfc/full/rfc3579.txt | | Source fingerprint | af4e5a944b99ad93 | | Record | rfc/extraction/rfc3579.json | | Mapped sentences | 30 | | Declined as scope | 26 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, copyright notice, Abstract and table of contents. The Abstract says what the memo adds to RADIUS and binds no speaker. | | `1` | Introduction | 3 | walked | Introduction. States what RADIUS and EAP are, and carries the RFC 2865 rule that a NAS must not implement the attributes for a service it cannot offer, in its EAP form. | | `1.1` | Specification of Requirements | 0 | walked | Specification of Requirements. The RFC 2119 key-words paragraph. No obligation of its own. | | `1.2` | Terminology | 2 | walked | Terminology. Defines authenticator, peer, authentication server, silently discard, displayable message, NAS, service and session. Two of those definitions carry obligations, sites 1.2:1 and 1.2:2; 'silently discard' carries two SHOULDs captured as RFC3579-1.2-3. | | `2` | RADIUS Support for EAP | 0 | walked | RADIUS Support for EAP. Says why the pass-through architecture exists and that a NAS may mix local authentication with pass-through, which is the MAY captured as RFC3579-2-1. | | `2.1` | Protocol Overview | 8 | walked | Protocol Overview. The conversation itself: who sends the first EAP-Request, how the NAS encapsulates a Response, what ends the conversation, EAP-Start, and the User-Name copying rule that lets a non-EAP-aware proxy forward the request. | | `2.2` | Invalid Packets | 3 | walked | Invalid Packets. The NAS header validation, the Error-Cause 202 branch, and the RADIUS server's fatal and non-fatal error handling. The DoS advice at the end is written as 'it is advisable' and 'is recommended' in lower case, and binds nobody. | | `2.3` | Retransmission | 0 | walked | Retransmission. Describes how Session-Timeout in an Access-Challenge sets the EAP retransmission timer for that one EAP-Request. Indicative prose, no obligation. | | `2.4` | Fragmentation | 1 | walked | Fragmentation. One obligation, on the RADIUS server, not to exceed a Framed-MTU the NAS supplied. The NAS half is written as 'may be included'. | | `2.5` | Alternative Uses | 1 | walked | Alternative Uses. RADIUS-encapsulated EAP between a RADIUS server and a security server. One obligation, on the RADIUS server. | | `2.6` | Usage Guidelines | 0 | walked | Usage Guidelines. A heading over 2.6.1 to 2.6.5 with no body text of its own. | | `2.6.1` | Identifier Space | 1 | walked | Identifier Space. One obligation, on RADIUS server implementations, to keep the EAP Identifier spaces of distinct sessions apart. | | `2.6.2` | Role Reversal | 1 | walked | Role Reversal. Says role reversal is not supported, with the obligation on the RADIUS server to reject an Access-Request encapsulating an EAP-Request, and a SHOULD on the same actor about the Nak it includes. | | `2.6.3` | Conflicting Messages | 3 | walked | Conflicting Messages. Three NAS obligations, all captured, plus the SHOULD list of combinations a RADIUS server is told not to send. | | `2.6.4` | Priority | 1 | walked | Priority. One NAS obligation about the order in which attributes and the Accept/Reject indication are processed. | | `2.6.5` | Displayable Messages | 2 | walked | Displayable Messages. The RADIUS server's encapsulation obligation, the exclusion of Reply-Message from any EAP-bearing message, and the NAS SHOULD to discard a Reply-Message rather than translate it. | | `3` | Attributes | 2 | walked | Attributes. The NAS identification attributes an Access-Request carries, and the RADIUS server's obligation to echo User-Name. | | `3.1` | EAP-Message | 6 | walked | EAP-Message. The attribute format, the ordering and single-EAP-packet rules, the Message-Authenticator protection rule, and the calculate-and-discard obligations on each of the two roles. | | `3.2` | Message-Authenticator | 3 | walked | Message-Authenticator. The attribute format and the HMAC-MD5 construction. The construction itself is written without an RFC 2119 keyword, so the site scan does not see it, and it is declared here as an unsourced id: 'MUST calculate the correct value' at sites 3.1:4 and 3.1:6 has no meaning without the formula this section states. | | `3.3` | Table of Attributes | 1 | walked | Table of Attributes. One obligation in the body, that neither attribute appears in an Accounting-Request. The table rows, its three notes and its legend are the section '0' the site scan derives. | | `0` | not stated | 3 | walked | The Section 3.3 Table of Attributes itself: the per-packet-type counts, Notes 1 to 3, and the legend that defines the cell values. Note 1 carries two obligations and the legend carries none. | | `4` | Security Considerations | 0 | walked | Security Considerations. A heading over 4.1 to 4.3 with no body text of its own. | | `4.1` | Security Requirements | 0 | walked | Security Requirements. The ten-item threat list and the statement that confidentiality, data origin authentication, integrity and replay protection are needed. Written in indicative prose with no keyword. | | `4.2` | Security Protocol | 4 | walked | Security Protocol. The IPsec profile for RADIUS: the transforms that must be supported, the zero-length shared secret where ESP with a non-null transform carries the traffic, and the IKE mode and identity payload advice. | | `4.3` | Security Issues | 0 | walked | Security Issues. A heading and the list of the ten vulnerabilities 4.3.1 to 4.3.10 expand. | | `4.3.1` | Privacy Issues | 0 | walked | Privacy Issues. One SHOULD, to protect RADIUS with IPsec ESP, restating Section 4.2's advice. | | `4.3.2` | Spoofing and Hijacking | 1 | walked | Spoofing and Hijacking. The SHOULD to use Message-Authenticator in an Access-Request with no User-Password, captured as RFC3579-4.3.2-1, and the restatement of the Section 3.1 protection rule. | | `4.3.3` | Dictionary Attacks | 0 | walked | Dictionary Attacks. Quotes RFC 2865's shared-secret advice and recommends IPsec ESP. The quoted SHOULD belongs to RFC 2865. | | `4.3.4` | Known Plaintext Attacks | 1 | walked | Known Plaintext Attacks. Explains the User-Password keystream reuse problem and states one obligation, that an EAP NAS's shared secret is not reused by a NAS using User-Password. | | `4.3.5` | Replay Attacks | 0 | walked | Replay Attacks. Describes what RADIUS does and does not give for replay protection. Indicative prose, no obligation. | | `4.3.6` | Negotiation Attacks | 8 | walked | Negotiation Attacks. The downgrade rules: what the authenticating peer must do when EAP was expected, what the NAS must attempt, and what the RADIUS server or proxy must answer. | | `4.3.7` | Impersonation | 1 | walked | Impersonation. Quotes RFC 2865 Section 3 on choosing the shared secret by source address, and states SHOULDs on RADIUS proxies. Ze is neither the RADIUS server nor a proxy. | | `4.3.8` | Man in the Middle Attacks | 0 | walked | Man in the Middle Attacks. One SHOULD, that EAP methods incorporate their own per-packet integrity protection. That obligation is on the EAP method, which RFC 3748 and the method's own RFC govern. | | `4.3.9` | Separation of Authenticator and Authentication Server | 0 | walked | Separation of Authenticator and Authentication Server. Explains what mutual authentication means when the two are on different hosts, and that the MSK must reach the authenticator. The key transport mechanism is stated to be out of scope for this document. | | `4.3.10` | Multiple Databases | 0 | walked | Multiple Databases. Recommends consolidating the security server's and the RADIUS server's user stores. No obligation, and the actor is the deployment. | | `5` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. 'This specification does not create any new registries, or define any new RADIUS attributes or values.' | | `6` | References | 0 | skipped (references) | References. A heading over 6.1 and 6.2. | | `6.1` | not stated | 0 | skipped (references) | Normative References: RFC 1321, 2104, 2119, 2279, 2284, 2401, 2406, 2409, 2486, 2865, 2988, 3162, 3280 and 3576. | | `6.2` | not stated | 0 | skipped (references) | Informative References, and the non-RFC citations IEEE802, IEEE8021X, MD5Attack, Masters and NASREQ. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The sentence opens 'As noted in [RFC2865]' and restates RFC 2865 Section 1.1. The obligation belongs to RFC 2865, whose summary carries it as RFC2865-1.1-1. | As noted in [RFC2865], a Network Access Server (NAS) that does not implement a given service MUST NOT implement the RADIUS attributes for that service. | | `2.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The sentence names 'a RADIUS server compliant with this specification and wishing to authenticate with EAP' as the actor. | On receiving a valid Access-Request packet containing EAP-Message attribute(s), a RADIUS server compliant with this specification and wishing to authenticate with EAP MUST respond with an Access-Challenge packet containing EAP-Message attribute(s). | | `2.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is the RADIUS server that does not support EAP. | If the RADIUS server does not support EAP or does not wish to authenticate with EAP, it MUST respond with an Access-Reject. | | `2.1:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'the local RADIUS server' deciding a realm from the peer identity. | If the realm is determined based on the peer identity, the local RADIUS server MUST respond with a RADIUS Access-Challenge including an EAP-Message attribute encapsulating an EAP-Request/Identity packet. | | `2.1:7` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The Access-Reject is sent by the RADIUS server that does not support the EAP-Message attribute. The NAS half of the same paragraph, denying access on that reject, is site 2.1:8. | If an Access-Request is sent to a RADIUS server which does not support the EAP-Message attribute, then an Access-Reject MUST be sent in response. | | `2.1:8` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates site 2.1:3, 'Reception of a RADIUS Access-Reject packet MUST result in the NAS denying access to the authenticating peer', for the particular reject an EAP-incapable server sends. It adds no obligation RFC3579-2.1-1 does not already carry. | On receiving an Access-Reject, the NAS MUST deny access to the authenticating peer. | | `2.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'A RADIUS server determining that a fatal error has occurred'. | A RADIUS server determining that a fatal error has occurred MUST send an Access-Reject containing an EAP-Message attribute encapsulating EAP-Failure. | | `2.4:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'A RADIUS server having received a Framed-MTU attribute'. The NAS half of Section 2.4, supplying Framed-MTU, is written as 'may be included' and states no obligation. | A RADIUS server having received a Framed-MTU attribute in an Access-Request packet MUST NOT send any subsequent packet in this EAP conversation containing EAP-Message attributes whose values, when concatenated, exceed the length specified by the Framed-MTU value, taking the link type (specified by the NAS-Port-Type attribute) into account. | | `2.5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. Section 2.5 describes a RADIUS server talking to a security server, and the actor is the RADIUS server adding the missing Access-Accept attributes. | This means that the RADIUS server MUST add these attributes prior to sending an Access-Accept message to the NAS. | | `2.6.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'RADIUS server implementations' keeping EAP Identifier spaces apart per session. | RADIUS server implementations MUST be able to distinguish between EAP packets with the same Identifier existing within distinct sessions, originating on the same NAS. | | `2.6.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is the RADIUS server refusing a role-reversed Access-Request that encapsulates an EAP-Request. | A RADIUS server MUST respond to an Access-Request encapsulating an EAP-Request with an Access-Reject. | | `2.6.5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is the RADIUS server sending a displayable message to a NAS. | When sending a displayable message to a NAS during an EAP conversation, the RADIUS server MUST encapsulate displayable messages within EAP-Message/EAP-Request/Notification attribute(s). | | `3:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is the RADIUS server echoing User-Name into its Access-Accept. | In order to permit forwarding of the Access-Reply by EAP-unaware proxies, if a User-Name attribute was included in an Access-Request, the RADIUS server MUST include the User-Name attribute in subsequent Access-Accept packets. | | `3.1:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'A RADIUS server supporting the EAP-Message attribute'. The NAS form of the same check is site 3.1:6. | A RADIUS server supporting the EAP-Message attribute MUST calculate the correct value of the Message-Authenticator and MUST silently discard the packet if it does not match the value sent. | | `3.1:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'A RADIUS server not supporting the EAP-Message attribute'. | A RADIUS server not supporting the EAP-Message attribute MUST return an Access-Reject if it receives an Access-Request containing an EAP-Message attribute. | | `3.2:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates site 3.1:3 from the Message-Authenticator side: the attribute is mandatory in any of the four packet types that includes an EAP-Message. It adds no obligation RFC3579-3.1-3 does not already carry. | It MUST be used in any Access-Request, Access-Accept, Access-Reject or Access-Challenge that includes an EAP-Message attribute. | | `3.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'A RADIUS server receiving an Access-Request with a Message-Authenticator attribute present'. | A RADIUS server receiving an Access-Request with a Message-Authenticator attribute present MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent. | | `3.2:3` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates site 3.1:6 for a RADIUS client rather than for a NAS supporting EAP-Message: recompute the Message-Authenticator of a received Access-Accept, Access-Reject or Access-Challenge and silently discard on a mismatch. One behaviour, one code path in ze (verifyResponseMessageAuthenticator), so it maps to the same requirement. | A RADIUS client receiving an Access-Accept, Access-Reject or Access-Challenge with a Message-Authenticator attribute present MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent. | | `0:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Note 1 restates site 3.1:3, 'If any packet type contains an EAP-Message attribute it MUST also contain a Message-Authenticator'. It adds no obligation RFC3579-3.1-3 does not already carry. | If any packet type contains an EAP-Message attribute it MUST also contain a Message-Authenticator. | | `0:3` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The legend of the Section 3.3 table, defining what the cells 0, 0+, 0-1, 1 and 1+ mean. The keywords describe the notation, not a speaker's behaviour; the obligations the table expresses are carried by Note 1 and by the attribute sections. | 0 This attribute MUST NOT be present. 0+ Zero or more instances of this attribute MAY be present. 0-1 Zero or one instance of this attribute MAY be present. 1 Exactly one instance of this attribute MUST be present. 1+ One or more of these attributes MUST be present. | | `4.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'a RADIUS server that cannot know whether incoming traffic is IPsec-protected'. The client-side half of the same paragraph is site 4.2:1. | However, a RADIUS server that cannot know whether incoming traffic is IPsec-protected MUST be configured with a non-null RADIUS shared secret. | | `4.3.2:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The Security Issues restatement of site 3.1:3, 'the Message-Authenticator attribute MUST be used in all RADIUS packets containing an EAP-Message attribute'. It adds no obligation RFC3579-3.1-3 does not already carry. | To provide stronger security, the Message-Authenticator attribute MUST be used in all RADIUS packets containing an EAP-Message attribute. | | `4.3.6:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is the RADIUS server answering a CHAP Access-Request where EAP is required. | However, if CHAP has been negotiated but EAP is required, the RADIUS server MUST respond with an Access-Reject, rather than an Access-Challenge/EAP-Message/EAP-Request packet. | | `4.3.6:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS authentication server. Ze never acts as one: NewClient (internal/component/radius/client.go) binds an ephemeral UDP socket with no service port, and (*Client).readLoop dispatches a datagram only against a waiter that (*Client).Exchange registered for one of ze's own outstanding requests, so no Access-Request receive path exists in the tree. If ze ran a RADIUS server the listener would live beside that producer. The actor is 'the server or proxy' that does not support EAP. Ze is neither a RADIUS server nor a RADIUS proxy: it forwards no other party's RADIUS packet. | If EAP is negotiated but is not supported by the RADIUS proxy or server, then the server or proxy MUST respond with an Access-Reject. | | `4.3.6:8` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates site 4.3.6:5 one paragraph later, for an EAP-capable peer rather than for the peer generally. It adds no obligation RFC3579-4.3.6-4 does not already carry. | An EAP-capable authenticating peer MUST refuse to renegotiate the authentication protocol if EAP had initially been negotiated. | | `4.3.7:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The sentence is the body of a block quotation introduced by '[RFC2865] Section 3 states:'. The obligation belongs to RFC 2865, whose summary is rfc/short/rfc2865.md, and it binds the RADIUS server in any case. | A RADIUS server MUST use the source IP address of the RADIUS UDP packet to decide which shared secret to use, so that RADIUS requests can be proxied. | ## Superseded No document obsoletes RFC 3579, so its obligations are stated where they were written. --- ### Page: RFC 3623 - Graceful OSPF Restart https://ze-software.net/quality/rfc-compliance/rfc3623/ # RFC 3623 - Graceful OSPF Restart Experimental. Every requirement this repository extracted from RFC 3623, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 30.8% | 4 of 13 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 38.5% | 5 of 13 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 13 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 13 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 13 | of 26 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 2 | of 13 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 15.4% | 2 of 13 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 13 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 13 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 15.4% | 2 of 13 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 13 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 26 | | Gated MUST-level | 13 | | Not applicable, so out of scope | 2 | | Declared gaps | 2 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 13 | | Tagged units | 13 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3623.md` | | Requirement shard | `rfc/requirements/rfc3623.md` | | RFC text | `rfc/full/rfc3623.txt` | ## Enrolment Enrolled: Graceful OSPF Restart (RFC 3623): restarter + helper roles; 4 MET (Grace-LSA mandatory TLVs, shared-media type-3, unplanned toggle) + 5 single-polarity positive + 2 gap (virtual-link V-bit, changed-retx-list refusal) + 2 not-applicable ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** - Restarter (planned + opt-in unplanned) and helper behavior - Grace-LSA Opaque type-3 body codec shared with RFC 5187. **What the ledger says remains** Two MUST gaps gated in [`rfc/short/rfc3623.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc3623.md): the helper does not preserve the transit-area V-bit when helping over a virtual link ([`RFC3623-3-1`](#rfc3623-3-1)), and helper entry does not refuse on a changed retransmission-list LSA ([`RFC3623-3.1-1`](#rfc3623-3.1-1), onGraceReceived is hardcoded permissive, mitigated by the Section 3.2 strict-LSA-checking exit). Experimental pending deployment hardening. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 9 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **13** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`RFC3623-A-2`](#rfc3623-a-2), [`RFC3623-A-3`](#rfc3623-a-3), [`RFC3623-A-4`](#rfc3623-a-4), [`RFC3623-5-1`](#rfc3623-5-1) **Annotated instead of tested (9):** [`RFC3623-A-1`](#rfc3623-a-1), [`RFC3623-A-5`](#rfc3623-a-5), [`RFC3623-2.1-1`](#rfc3623-2.1-1), [`RFC3623-5-2`](#rfc3623-5-2), [`RFC3623-5-3`](#rfc3623-5-3), [`RFC3623-5-4`](#rfc3623-5-4), [`RFC3623-3-1`](#rfc3623-3-1), [`RFC3623-3.1-1`](#rfc3623-3.1-1), [`RFC3623-3.2-1`](#rfc3623-3.2-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3623-A-1` | Additional Grace-LSA TLVs must be described in an Internet Draft and subject to OSPF WG expert review (§A) | MUST | A | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze originates only the three RFC-defined TLVs (types 1/2/3) and adds none, and its decoder ignores unrecognized types, so the IETF-process obligation for additional TLVs binds no ze behavior (internal/plugins/ospf/packet/grace_lsa.go:44, :64) | | `RFC3623-A-2` | Grace Period TLV (type 1) must always appear in a grace-LSA (§A) | MUST | A | **positive:** `unit/verify` [`TestGraceLSARoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L20). **negative:** `unit/verify` [`TestGraceLSADecodeMissingMandatory`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L75) | | `RFC3623-A-3` | Graceful restart reason TLV (type 2) must always appear in a grace-LSA (§A) | MUST | A | **positive:** `unit/verify` [`TestGraceLSARoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L24). **negative:** `unit/verify` [`TestGraceLSADecodeMissingMandatory`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L72) | | `RFC3623-A-4` | IP interface address TLV (type 3) required on broadcast, NBMA and Point-to-MultiPoint segments (§A, §3.1) | MUST | A | **positive:** `unit/verify` [`TestGraceLSAv4BodyBuild`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_lsa_test.go#L18). **negative:** `unit/verify` [`TestGraceLSAv4BodyBuild`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_lsa_test.go#L22) | | `RFC3623-A-5` | DoNotAge is never set in a grace-LSA, even over a demand circuit (§A) | MUST | A | **positive:** `unit/verify` [`TestGraceLSANeverSetsDoNotAge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc3623_gr_positive_test.go#L62). **negative:** no negative test. **{single-polarity}:** Grace-LSAs originate through OriginateOpaque, whose input struct has no DoNotAge field and starts LS age at 0 with normal aging, so the bit is never set and no negative behavior exists to test (internal/plugins/ospf/lsdb/opaque_as.go:73, gr_restarter.go:314, lsdb/entry.go:87) | | `RFC3623-2.1-1` | Before reload, ensure forwarding table(s) are up-to-date and remain in place across the restart (§2.1) | MUST | 2.1 | **positive:** `unit/verify` [`TestPrepareRestartRetainsFIB`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc3623_gr_positive_test.go#L91). **negative:** no negative test. **{single-polarity}:** prepareRestart raises gracefulStop so suppressInstall makes the ensuing engine stop skip RemoveAll and retain the pre-restart FIB; the meaningful assertion is retention, with no negative behavior the requirement forbids (internal/plugins/ospf/gr_restarter.go:49, gr.go:235) | | `RFC3623-5-1` | An implementation providing recovery from unplanned outages must allow the operator to turn the option off (§5) | MUST | 5 | **positive:** `unit/verify` [`TestUnplannedGraceBeforeHello`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_unplanned_test.go#L40). **negative:** `unit/verify` [`TestUnplannedDisabledByDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_unplanned_test.go#L16) | | `RFC3623-5-2` | On unplanned restart, grace-LSAs must be originated and sent before any OSPF Hello packets; on broadcast networks flooded to AllSPFRouters 224.0.0.5 (§5) | MUST | 5 | **positive:** `unit/verify` [`TestUnplannedGraceLSAFloodsToAllSPFRouters`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc3623_gr_positive_test.go#L151). **negative:** no negative test. **{single-polarity}:** maybeUnplannedRestart enters in-restart then originates one Grace-LSA per interface before interface Hellos begin, and link-local LSAs flood to AllSPFRouters via the standard link-scope path (internal/plugins/ospf/gr_restarter.go:105, lsdb_flooding_test.go:47) | | `RFC3623-5-3` | On unplanned restart, grace-LSAs are encapsulated in Link State Update packets and sent out all interfaces (§5) | MUST | 5 | **positive:** `unit/verify` [`TestUnplannedGraceLSAPerActiveInterface`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc3623_gr_positive_test.go#L193). **negative:** no negative test. **{single-polarity}:** grOriginateGraceLSAs walks every active (non-passive) interface and floods each Grace-LSA via OriginateOpaque's standard LSU flooding (internal/plugins/ospf/gr_restarter.go:296, :315) | | `RFC3623-5-4` | On unplanned restart, the restart reason in grace-LSAs must be set to 0 (unknown) or 3 (switch to redundant control processor) (§5) | MUST | 5 | **positive:** `unit/verify` [`TestUnplannedGraceBeforeHello`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_unplanned_test.go#L58). **negative:** no negative test. **{single-polarity}:** grUnplannedReason returns the constant 3 (redundant control processor), an in-range unplanned reason, and there is no receive-side reason rejection to test negatively (internal/plugins/ospf/gr_restarter.go:88, gr.go:36) | | `RFC3623-3-1` | When helping over a virtual link, the helper must continue to set bit V in its router-LSA for the transit area (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the helper role keeps X advertised in Router/Network-LSAs but neither gr_helper.go nor gr.go has any virtual-link/transit-area handling, so the transit-area V-bit is not preserved while helping over a virtual link (internal/plugins/ospf/gr_helper.go:279) | | `RFC3623-3.1-1` | Helper must refuse to enter helper mode if LSAs on X's retransmission list have changed content (not periodic refreshes) (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the helper entry decision has an lsdb-changed branch, but the production caller hardcodes lsdbUnchanged=true, so no production path refuses entry on a changed retransmission-list LSA; the permissive entry is only mitigated by the Section 3.2 strict-checking exit (internal/plugins/ospf/gr_helper.go:40, :128) | | `RFC3623-3.2-1` | If Y aggregated adjacencies on entering helper mode, it must exit helper mode for all adjacencies with X when any one exit event occurs (§3.2) | MUST | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze tracks one helper session per (interface, router) and never aggregates adjacencies across segments, so the aggregated exit-all obligation binds a mode ze does not play (internal/plugins/ospf/gr.go:71) | | `RFC3623-2.1-2` | The grace period should not exceed LSRefreshTime (1800 seconds) (§2.1) | SHOULD NOT | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-2.1-3` | Retransmit grace-LSAs until acknowledged (standard OSPF reliable flooding) (§2.1) | SHOULD | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-2.1-4` | After sending grace-LSAs, store the restart fact and grace-period length in non-volatile storage (§2.1) | SHOULD | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-2.3-1` | On exit, reoriginate router-LSAs for all attached areas (§2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-2.3-2` | On exit, reoriginate network-LSAs on all segments where it is Designated Router (§2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-2.3-3` | On exit, remove remnant pre-restart forwarding entries that are no longer valid (§2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-2.3-4` | On exit, flush received self-originated LSAs that are no longer valid (§2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-2.3-5` | On exit, flush any grace-LSAs the router originated (§2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-2.1-5` | Preserve cryptographic sequence numbers (or the clock generating them) in non-volatile storage across restarts (§2.1) | MAY | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-5-5` | Send grace-LSAs multiple times on unplanned restart to improve delivery (§5) | MAY | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-3.1-2` | Helper may disallow graceful restart with X on other network segments (§3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-3.1-3` | Helper may aggregate adjacencies, entering helper mode only when checks pass for all adjacencies with X (§3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3623-3.2-2` | Provide a configuration option to disable LSDB changes from terminating graceful restart (increases loop/black-hole risk) (§3.2) | MAY | 3.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3623-A-1`](#rfc3623-a-1) Additional Grace-LSA TLVs must be described in an Internet Draft and subject to OSPF WG expert review (§A) | no test | no test carries this requirement id; annotated {not-applicable}: ze originates only the three RFC-defined TLVs (types 1/2/3) and adds none, and its decoder ignores unrecognized types, so the IETF-process obligation for additional TLVs binds no ze behavior (internal/plugins/ospf/packet/grace_lsa.go:44, :64) | | [`RFC3623-3-1`](#rfc3623-3-1) When helping over a virtual link, the helper must continue to set bit V in its router-LSA for the transit area (§3) | {gap}, no test | the helper role keeps X advertised in Router/Network-LSAs but neither gr_helper.go nor gr.go has any virtual-link/transit-area handling, so the transit-area V-bit is not preserved while helping over a virtual link (internal/plugins/ospf/gr_helper.go:279) | | [`RFC3623-3.1-1`](#rfc3623-3.1-1) Helper must refuse to enter helper mode if LSAs on X's retransmission list have changed content (not periodic refreshes) (§3.1) | {gap}, no test | the helper entry decision has an lsdb-changed branch, but the production caller hardcodes lsdbUnchanged=true, so no production path refuses entry on a changed retransmission-list LSA; the permissive entry is only mitigated by the Section 3.2 strict-checking exit (internal/plugins/ospf/gr_helper.go:40, :128) | | [`RFC3623-3.2-1`](#rfc3623-3.2-1) If Y aggregated adjacencies on entering helper mode, it must exit helper mode for all adjacencies with X when any one exit event occurs (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze tracks one helper session per (interface, router) and never aggregates adjacencies across segments, so the aggregated exit-all obligation binds a mode ze does not play (internal/plugins/ospf/gr.go:71) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3623-A-1`](#rfc3623-a-1) Additional Grace-LSA TLVs must be described in an Internet Draft and subject to OSPF WG expert review (§A) Audit verdict: not audited: no reader has judged these tests No test carries RFC3623-A-1, so no unit is bound to it. ### [`RFC3623-A-2`](#rfc3623-a-2) Grace Period TLV (type 1) must always appear in a grace-LSA (§A) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGraceLSADecodeMissingMandatory`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L75) | unit/verify | unproven | | positive | [`TestGraceLSARoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L20) | unit/verify | unproven | ### [`RFC3623-A-3`](#rfc3623-a-3) Graceful restart reason TLV (type 2) must always appear in a grace-LSA (§A) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGraceLSADecodeMissingMandatory`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L72) | unit/verify | unproven | | positive | [`TestGraceLSARoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L24) | unit/verify | unproven | ### [`RFC3623-A-4`](#rfc3623-a-4) IP interface address TLV (type 3) required on broadcast, NBMA and Point-to-MultiPoint segments (§A, §3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGraceLSAv4BodyBuild`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_lsa_test.go#L22) | unit/verify | unproven | | positive | [`TestGraceLSAv4BodyBuild`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_lsa_test.go#L18) | unit/verify | unproven | ### [`RFC3623-A-5`](#rfc3623-a-5) DoNotAge is never set in a grace-LSA, even over a demand circuit (§A) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestGraceLSANeverSetsDoNotAge`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc3623_gr_positive_test.go#L62) | unit/verify | unproven | ### [`RFC3623-2.1-1`](#rfc3623-2.1-1) Before reload, ensure forwarding table(s) are up-to-date and remain in place across the restart (§2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestPrepareRestartRetainsFIB`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc3623_gr_positive_test.go#L91) | unit/verify | unproven | ### [`RFC3623-5-1`](#rfc3623-5-1) An implementation providing recovery from unplanned outages must allow the operator to turn the option off (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestUnplannedDisabledByDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_unplanned_test.go#L16) | unit/verify | unproven | | positive | [`TestUnplannedGraceBeforeHello`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_unplanned_test.go#L40) | unit/verify | unproven | ### [`RFC3623-5-2`](#rfc3623-5-2) On unplanned restart, grace-LSAs must be originated and sent before any OSPF Hello packets; on broadcast networks flooded to AllSPFRouters 224.0.0.5 (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestUnplannedGraceLSAFloodsToAllSPFRouters`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc3623_gr_positive_test.go#L151) | unit/verify | unproven | ### [`RFC3623-5-3`](#rfc3623-5-3) On unplanned restart, grace-LSAs are encapsulated in Link State Update packets and sent out all interfaces (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestUnplannedGraceLSAPerActiveInterface`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc3623_gr_positive_test.go#L193) | unit/verify | unproven | ### [`RFC3623-5-4`](#rfc3623-5-4) On unplanned restart, the restart reason in grace-LSAs must be set to 0 (unknown) or 3 (switch to redundant control processor) (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestUnplannedGraceBeforeHello`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_unplanned_test.go#L58) | unit/verify | unproven | ### [`RFC3623-3-1`](#rfc3623-3-1) When helping over a virtual link, the helper must continue to set bit V in its router-LSA for the transit area (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC3623-3-1, so no unit is bound to it. ### [`RFC3623-3.1-1`](#rfc3623-3.1-1) Helper must refuse to enter helper mode if LSAs on X's retransmission list have changed content (not periodic refreshes) (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3623-3.1-1, so no unit is bound to it. ### [`RFC3623-3.2-1`](#rfc3623-3.2-1) If Y aggregated adjacencies on entering helper mode, it must exit helper mode for all adjacencies with X when any one exit event occurs (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3623-3.2-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 3623, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 3623, so its obligations are stated where they were written. --- ### Page: RFC 3630 - Traffic Engineering (TE) Extensions to OSPF Version 2 https://ze-software.net/quality/rfc-compliance/rfc3630/ # RFC 3630 - Traffic Engineering (TE) Extensions to OSPF Version 2 Experimental. Every requirement this repository extracted from RFC 3630, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 20.0% | 1 of 5 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 5 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 5 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 5 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 2 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 5 | of 10 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 4 | of 5 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 80.0% | 4 of 5 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 5 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 5 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 5 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 10 | | Gated MUST-level | 5 | | Not applicable, so out of scope | 4 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 2 | | Tagged units | 2 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3630.md` | | Requirement shard | `rfc/requirements/rfc3630.md` | | RFC text | `rfc/full/rfc3630.txt` | ## Enrolment Enrolled: OSPF Traffic Engineering (TE) LSA -- the Type 10 area-local Opaque LSA (Opaque type 1). Five MUST-level requirements gated: one tested, four {not-applicable}. RFC3630-1-1 (non-TE-capable nodes flood TE LSAs as ordinary Type-10 area-local Opaque LSAs) is covered by TestRFC3630NonTECapableFloodsTELSAByScope in internal/plugins/ospf/lsdb/rfc3630_te_test.go: positive, the consumer-agnostic LSDB carrier floods a Type-10 opaque type-1 (packet.TEOpaqueType) LSA to a Flood-eligible same-area neighbor purely by area scope with no TE code in the path (producer eligibleInterface default iface.AreaID==area at flooding.go:401, floodExcept at flooding.go:318 whose only opaque gate is the RFC 5250 O-bit at flooding.go:369, never a TE-type check); negative, the same area-local LSA is NOT flooded out an interface in a different area, bounding it exactly like any other Type-10 opaque LSA. RFC3630-6-1..6-4 (top-level and sub-TLV Type ranges 32768-32777 experimental / 32778-65535 reserved pending a Standards Track RFC) are {not-applicable}: ze is an implementation, not the IANA registry nor an RFC author, so it neither allocates nor documents these type ranges. The 2.4.1/2.5.7/3/2.5.4 SHOULDs and MAYs are not gated. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** TE LSA body and sub-TLV support. **What the ledger says remains:** Same OSPF experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 1 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **5** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (1):** [`RFC3630-1-1`](#rfc3630-1-1) **Annotated instead of tested (4):** [`RFC3630-6-1`](#rfc3630-6-1), [`RFC3630-6-2`](#rfc3630-6-2), [`RFC3630-6-3`](#rfc3630-6-3), [`RFC3630-6-4`](#rfc3630-6-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3630-1-1` | Non-TE capable nodes must flood TE LSAs as any other type 10 (area-local scope) Opaque LSAs (§1) -- the ext-1 opaque carrier floods Type 10 by scope regardless of any TE consumer (Ze: spec-ospf-ext-2) | MUST | 1 | **positive:** `unit/verify` [`TestRFC3630NonTECapableFloodsTELSAByScope`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc3630_te_test.go#L23). **negative:** `unit/verify` [`TestRFC3630NonTECapableFloodsTELSAByScope`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc3630_te_test.go#L28) | | `RFC3630-6-1` | Top-level Types in the range 32768-32777 are for experimental use, must not be mentioned by RFCs (§6) | MUST NOT | 6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is an implementation, not an RFC author; it neither authors RFCs nor mentions the experimental top-level Type range 32768-32777 | | `RFC3630-6-2` | Top-level Types 32778-65535: before any assignment, there must be a Standards Track RFC specifying IANA Considerations covering the range (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is an implementation, not the IANA registry nor an RFC author; it neither assigns nor documents the reserved top-level Type range 32778-65535 | | `RFC3630-6-3` | Sub-TLV Types in the range 32768-32777 are for experimental use, must not be mentioned by RFCs (§6) | MUST NOT | 6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is an implementation, not an RFC author; it neither authors RFCs nor mentions the experimental sub-TLV Type range 32768-32777 | | `RFC3630-6-4` | Sub-TLV Types 32778-65535: before any assignment, there must be a Standards Track RFC specifying IANA Considerations covering the range (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is an implementation, not the IANA registry nor an RFC author; it neither assigns nor documents the reserved sub-TLV Type range 32778-65535 | | `RFC3630-2.4.1-1` | If a router advertises BGP routes with the BGP next hop attribute set to the BGP router ID, the Router Address should be the same as the BGP router ID (§2.4.1) | SHOULD | 2.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3630-2.5.7-1` | Maximum Reservable Bandwidth should be user-configurable; default value should be the Maximum Bandwidth (§2.5.7) -- Ze: `max-reservable-bandwidth` leaf, defaulting to `max-bandwidth` (applyTELinkAttributes) | SHOULD | 2.5.7 | **positive:** no positive test. **negative:** no negative test | | `RFC3630-3-1` | Origination of Traffic Engineering LSAs should be rate-limited to at most one every MinLSInterval (§3) -- Ze reuses the carrier's MinLSInterval rate-limit (OriginateSelf); a pull-model unchanged body floods nothing | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC3630-2.5.4-1` | An implementation may choose not to send the Remote Interface IP Address sub-TLV for Multi-access links (§2.5.4) -- Ze omits sub-TLV 4 on multi-access links | MAY | 2.5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC3630-3-2` | An implementation may set thresholds (e.g., a bandwidth change threshold) that trigger immediate flooding (§3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3630-6-1`](#rfc3630-6-1) Top-level Types in the range 32768-32777 are for experimental use, must not be mentioned by RFCs (§6) | no test | no test carries this requirement id; annotated {not-applicable}: ze is an implementation, not an RFC author; it neither authors RFCs nor mentions the experimental top-level Type range 32768-32777 | | [`RFC3630-6-2`](#rfc3630-6-2) Top-level Types 32778-65535: before any assignment, there must be a Standards Track RFC specifying IANA Considerations covering the range (§6) | no test | no test carries this requirement id; annotated {not-applicable}: ze is an implementation, not the IANA registry nor an RFC author; it neither assigns nor documents the reserved top-level Type range 32778-65535 | | [`RFC3630-6-3`](#rfc3630-6-3) Sub-TLV Types in the range 32768-32777 are for experimental use, must not be mentioned by RFCs (§6) | no test | no test carries this requirement id; annotated {not-applicable}: ze is an implementation, not an RFC author; it neither authors RFCs nor mentions the experimental sub-TLV Type range 32768-32777 | | [`RFC3630-6-4`](#rfc3630-6-4) Sub-TLV Types 32778-65535: before any assignment, there must be a Standards Track RFC specifying IANA Considerations covering the range (§6) | no test | no test carries this requirement id; annotated {not-applicable}: ze is an implementation, not the IANA registry nor an RFC author; it neither assigns nor documents the reserved sub-TLV Type range 32778-65535 | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3630-1-1`](#rfc3630-1-1) Non-TE capable nodes must flood TE LSAs as any other type 10 (area-local scope) Opaque LSAs (§1) -- the ext-1 opaque carrier floods Type 10 by scope regardless of any TE consumer (Ze: spec-ospf-ext-2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3630NonTECapableFloodsTELSAByScope`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc3630_te_test.go#L28) | unit/verify | unproven | | positive | [`TestRFC3630NonTECapableFloodsTELSAByScope`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/rfc3630_te_test.go#L23) | unit/verify | unproven | ### [`RFC3630-6-1`](#rfc3630-6-1) Top-level Types in the range 32768-32777 are for experimental use, must not be mentioned by RFCs (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3630-6-1, so no unit is bound to it. ### [`RFC3630-6-2`](#rfc3630-6-2) Top-level Types 32778-65535: before any assignment, there must be a Standards Track RFC specifying IANA Considerations covering the range (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3630-6-2, so no unit is bound to it. ### [`RFC3630-6-3`](#rfc3630-6-3) Sub-TLV Types in the range 32768-32777 are for experimental use, must not be mentioned by RFCs (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3630-6-3, so no unit is bound to it. ### [`RFC3630-6-4`](#rfc3630-6-4) Sub-TLV Types 32778-65535: before any assignment, there must be a Standards Track RFC specifying IANA Considerations covering the range (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC3630-6-4, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 3630, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 3630, so its obligations are stated where they were written. --- ### Page: RFC 3748 - Extensible Authentication Protocol (EAP) https://ze-software.net/quality/rfc-compliance/rfc3748/ # RFC 3748 - Extensible Authentication Protocol (EAP) Supported in IPsec. Every requirement this repository extracted from RFC 3748, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 54.1% | 33 of 61 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 8.2% | 5 of 61 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 61 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 36.4% | 28 of 77 tagged units, 0 escaped and 6 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 61 | of 67 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 8 | of 61 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 13.1% | 8 of 61 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 61 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 61 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 24.6% | 15 of 61 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 61 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported in IPsec | | Enrolment | Enrolled | | Requirements | 67 | | Gated MUST-level | 61 | | Not applicable, so out of scope | 8 | | Declared gaps | 0 | | Gated with no test | 15 | | Nightly-only evidence | 0 | | Test tags | 77 | | Tagged units | 77 | | Recorded audit verdicts | 0 | | Discrimination records | 34 | | Summary | `rfc/short/rfc3748.md` | | Requirement shard | `rfc/requirements/rfc3748.md` | | RFC text | `rfc/full/rfc3748.txt` | ## Enrolment Enrolled: Extensible Authentication Protocol / EAP (RFC 3748): IKEv2-carried EAP peer + co-located authenticator (EAP-TLS, EAP-MSCHAPv2, local termination, no pass-through). Both roles are gated: the packet format and its length handling, the Identifier and Type rules that bind a Request to its Response, one method per conversation, method completion -> Success/Failure, the 4-octet terminal packet, the peer's end of an unsuccessful method, and four silent discards -- a Code outside 1-4 by either role, and by the peer a canned Success, a Success the method does not permit yet, and a Failure after both ends indicated success. A non-Response carrying a DEFINED Code is still answered with EAP-Failure; a Code outside 1-4 is discarded, which corrects a row that claimed the first behavior for every non-Response (2026-09-01). Lower-layer ordering, error detection, MTU and duplicate detection are the IKEv2 carrier's, pass-through and Expanded Types are out of scope, and no EMSK is derived. ## What the public ledger says **Status:** Supported in IPsec **What the ledger says is covered** 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`, [`internal/core/eap/peer.go`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer.go)). 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/component/ike/engine/eap_auth.go`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/eap_auth.go)). **What the ledger says remains** 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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 33 | one part of the gated population | | Annotated instead of tested | 13 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 15 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **61** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (33):** [`RFC3748-4-1`](#rfc3748-4-1), [`RFC3748-4-2`](#rfc3748-4-2), [`RFC3748-2-2`](#rfc3748-2-2), [`RFC3748-2.1-1`](#rfc3748-2.1-1), [`RFC3748-2.1-2`](#rfc3748-2.1-2), [`RFC3748-2.1-3`](#rfc3748-2.1-3), [`RFC3748-4.2-2`](#rfc3748-4.2-2), [`RFC3748-4-4`](#rfc3748-4-4), [`RFC3748-4.1-3`](#rfc3748-4.1-3), [`RFC3748-4.1-4`](#rfc3748-4.1-4), [`RFC3748-4.1-5`](#rfc3748-4.1-5), [`RFC3748-4.2-5`](#rfc3748-4.2-5), [`RFC3748-4.2-6`](#rfc3748-4.2-6), [`RFC3748-4-5`](#rfc3748-4-5), [`RFC3748-4.2-7`](#rfc3748-4.2-7), [`RFC3748-4.2-8`](#rfc3748-4.2-8), [`RFC3748-4.2-9`](#rfc3748-4.2-9), [`RFC3748-4.1-10`](#rfc3748-4.1-10), [`RFC3748-4.1-11`](#rfc3748-4.1-11), [`RFC3748-2.1-4`](#rfc3748-2.1-4), [`RFC3748-5-1`](#rfc3748-5-1), [`RFC3748-5-2`](#rfc3748-5-2), [`RFC3748-5.2-1`](#rfc3748-5.2-1), [`RFC3748-5.2-2`](#rfc3748-5.2-2), [`RFC3748-5.3.1-1`](#rfc3748-5.3.1-1), [`RFC3748-5.3.1-2`](#rfc3748-5.3.1-2), [`RFC3748-5.3.1-3`](#rfc3748-5.3.1-3), [`RFC3748-5.3.1-4`](#rfc3748-5.3.1-4), [`RFC3748-5.4-1`](#rfc3748-5.4-1), [`RFC3748-5.4-2`](#rfc3748-5.4-2), [`RFC3748-5.1-2`](#rfc3748-5.1-2), [`RFC3748-7.5-1`](#rfc3748-7.5-1), [`RFC3748-7.10-4`](#rfc3748-7.10-4) **Annotated instead of tested (13):** [`RFC3748-2-1`](#rfc3748-2-1), [`RFC3748-4.1-1`](#rfc3748-4.1-1), [`RFC3748-4.1-2`](#rfc3748-4.1-2), [`RFC3748-4.2-1`](#rfc3748-4.2-1), [`RFC3748-4.2-4`](#rfc3748-4.2-4), [`RFC3748-2.3-1`](#rfc3748-2.3-1), [`RFC3748-3.1-1`](#rfc3748-3.1-1), [`RFC3748-3.1-2`](#rfc3748-3.1-2), [`RFC3748-3.1-3`](#rfc3748-3.1-3), [`RFC3748-3.1-4`](#rfc3748-3.1-4), [`RFC3748-5.7-1`](#rfc3748-5.7-1), [`RFC3748-7.10-1`](#rfc3748-7.10-1), [`RFC3748-7.10-2`](#rfc3748-7.10-2) **No test and no annotation (15):** [`RFC3748-2-3`](#rfc3748-2-3), [`RFC3748-2.2-1`](#rfc3748-2.2-1), [`RFC3748-4.1-6`](#rfc3748-4.1-6), [`RFC3748-4.1-7`](#rfc3748-4.1-7), [`RFC3748-4.1-8`](#rfc3748-4.1-8), [`RFC3748-4.1-9`](#rfc3748-4.1-9), [`RFC3748-4.2-15`](#rfc3748-4.2-15), [`RFC3748-4.2-10`](#rfc3748-4.2-10), [`RFC3748-4.2-11`](#rfc3748-4.2-11), [`RFC3748-4.2-12`](#rfc3748-4.2-12), [`RFC3748-4.2-13`](#rfc3748-4.2-13), [`RFC3748-4.2-14`](#rfc3748-4.2-14), [`RFC3748-7.10-5`](#rfc3748-7.10-5), [`RFC3748-7.10-6`](#rfc3748-7.10-6), [`RFC3748-7.10-7`](#rfc3748-7.10-7) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3748-4-1` | Packets shorter than the Length field indicates MUST be silently discarded (S4) | MUST | 4 | **positive:** `unit/verify` [`TestRFC3748PacketLengthDiscard`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L73). **negative:** `unit/verify` [`TestRFC3748PacketLengthDiscard`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L80) | | `RFC3748-4-2` | Minimum EAP packet length is 4 octets (S4) | MUST | 4 | **positive:** `unit/verify` [`TestRFC3748MinimumPacketLength`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L89). **negative:** `unit/verify` [`TestRFC3748MinimumPacketLength`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L99) | | `RFC3748-2-1` | The authenticator MUST operate in lock-step: only one outstanding Request at a time (S2) | MUST | 2 | **positive:** `unit/verify` [`TestRFC3748AuthenticatorLockStep`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L167). **negative:** no negative test. **{single-polarity}:** the authenticator Session emits one packet per call -- Begin returns a single Request (internal/core/eap/eap.go:164) and each Process returns a single *Packet (eap.go:176), so a new Request cannot precede its Response and no path leaves two outstanding, giving no negative case | | `RFC3748-2-2` | The authenticator MUST receive a valid Response before sending a new Request (S2) | MUST | 2 | **positive:** `unit/verify` [`TestRFC3748AuthenticatorRequiresValidResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L208). **negative:** `unit/verify` [`TestRFC3748AuthenticatorRequiresValidResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L220) | | `RFC3748-2.1-1` | Only one authentication method (Type >= 4) per EAP conversation (S2.1) | MUST | 2.1 | **positive:** `unit/verify` [`TestRFC3748OneMethodPerConversation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L235). **negative:** `unit/verify` [`TestRFC3748OneMethodPerConversation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L242) | | `RFC3748-2.1-2` | After the method completes, the authenticator MUST send Success or Failure (S2.1) | MUST | 2.1 | **positive:** `unit/verify` [`TestRFC3748MethodCompletionSendsResult`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L267). **negative:** `unit/verify` [`TestRFC3748MethodCompletionSendsResult`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L277) | | `RFC3748-2.1-3` | The peer MUST NOT send a NAK (Type 3) after the first non-NAK Response in a conversation (S2.1, S5.3) | MUST | 2.1 | **positive:** `unit/verify` [`TestRFC3748PeerNaksBeforeItCommitsToAMethod`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L372). **negative:** `unit/verify` [`TestRFC3748PeerNaksBeforeItCommitsToAMethod`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L385) | | `RFC3748-4.1-1` | Only the authenticator retransmits on timer; the peer MUST NOT retransmit on its own timer (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC3748PeerHasNoRetransmitTimer`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L289). **negative:** no negative test. **{single-polarity}:** the EAP peer (PeerSession.Process, internal/core/eap/peer.go:97) is a synchronous request-driven transform with no timer -- it emits a Response only when handed a Request and never self-retransmits; lower-layer retransmission is IKEv2's (internal/component/ike/engine/fsm.go:138), so there is no forbidden self-timer to test negatively | | `RFC3748-4.1-2` | The peer MUST re-send its last Response when a duplicate Request arrives (S4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** duplicate-Request handling is delegated to the IKEv2 carrier -- on timeout the initiator (EAP peer) re-sends its last IKE_AUTH message, which carries its last EAP Response (sa.LastSentMsg, internal/component/ike/engine/fsm.go:138); the EAP peer state machine holds no per-Request retransmission state | | `RFC3748-4.2-1` | Success and Failure packets MUST NOT be retransmitted (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestRFC3748SuccessFailureNotRetransmitted`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L140). **negative:** no negative test. **{single-polarity}:** the authenticator Session emits Success or Failure once and then enters a terminal state where Process returns nil (internal/core/eap/eap.go:186), so the EAP layer never retransmits them; there is no negative case | | `RFC3748-4.2-2` | Success and Failure contain only Code, Identifier, and Length (4 octets, no Type field) (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestRFC3748SuccessFailureFormat`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L110). **negative:** `unit/verify` [`TestRFC3748SuccessFailureFormat`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L130) | | `RFC3748-4.2-4` | The Identifier of a Success or Failure MUST match the Identifier of the Response it answers (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestFailureIdentifierMatchesResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L24). **positive:** `unit/verify` [`TestFailureIdentifierMatchesResponseOnNAK`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L49). **positive:** `unit/verify` [`TestIdentityFailureIdentifierMatchesResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L84). **positive:** `unit/verify` [`TestSuccessIdentifierMatchesResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L153). **negative:** no negative test. **{single-polarity}:** this obligation is on the SENDER. The authenticator's terminal packets are produced by Session.failure and the result.Done arm of Session.handleMethod (internal/core/eap/eap.go), and both now stamp the answered Response's Identifier. A negative case would need a RECEIVER that discards a mismatched Success or Failure, which Section 4.2 does not require of a sender and which ze's PeerSession.Process (internal/core/eap/peer.go) does not do: it switches on request.Code alone | | `RFC3748-2.3-1` | A pass-through authenticator MUST forward packets of any Type without interpreting method-specific data (S2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authenticator always terminates the EAP method locally (eap.Session, internal/core/eap/eap.go:130); there is no AAA/RADIUS back end in the IKE engine, so it never operates as a pass-through | | `RFC3748-3.1-1` | Lower layer MUST provide in-order delivery for EAP (S3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** in-order delivery is a lower-layer obligation; ze carries EAP only inside IKEv2, whose message-ID sequencing delivers each IKE_AUTH request/response in order (RFC 7296 Section 2.3; internal/component/ike/engine/msgid.go:76), so the EAP framework code neither provides nor can violate it | | `RFC3748-3.1-2` | Lower layer MUST provide error detection (CRC, checksum, or MIC) (S3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** bit-error detection is a lower-layer obligation; ze's EAP packets travel inside the IKEv2 SK payload whose AEAD/ICV check rejects any corrupted frame on decrypt (internal/component/ike/engine/fsm.go:658), so the EAP framework code adds no CRC of its own | | `RFC3748-3.1-3` | Lower layer MUST support a minimum MTU of 1020 octets for EAP packets (S3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the 1020-octet minimum MTU is a lower-layer obligation; ze carries EAP inside IKEv2 SK payloads over UDP, which admit EAP packets far larger than 1020 octets, and EAP-TLS method data is itself fragmented in 1024-octet chunks (internal/core/eap/eap_tls.go:28) | | `RFC3748-3.1-4` | Lower layer MUST provide duplicate detection (S3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** duplicate detection is a lower-layer obligation; IKEv2 detects a duplicated message by message ID and replays the cached response (internal/component/ike/engine/msgid.go:79; responder.go:81), so the EAP framework code neither provides nor can violate it | | `RFC3748-5.7-1` | When Type = 254 (Expanded Types), Vendor-Id 0 = IETF namespace (S5.7) | MUST | 5.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze offers MD5-Challenge (4), EAP-TLS (13) and EAP-MSCHAPv2 (26); NewSession rejects every other type (NewSession, internal/core/eap/eap.go) and the codec never encodes or parses an Expanded Type (254) packet or its Vendor-Id field, so no Vendor-Id namespace rule can bind. The peer reads TypeExpandedEAP only to route a Type-254 Request to the legacy Nak that Section 5.7 prescribes for a peer not equipped to interpret it (PeerSession.naks, internal/core/eap/peer.go), and that Nak carries no Vendor-Id | | `RFC3748-7.10-1` | MSK MUST be at least 64 octets (S7.10) | MUST | 7.10 | **positive:** `unit/verify` [`TestRFC3748MSKSize`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L324). **negative:** no negative test. **{single-polarity}:** the MSK is the compile-time fixed [64]byte returned by DeriveMSK (internal/core/eap/mschapv2.go:206) and by the EAP-TLS ExportKeyingMaterial(...,64) (eap_tls.go:262), so the 64-octet floor cannot be undershot and has no negative case | | `RFC3748-7.10-2` | EMSK MUST be at least 64 octets (S7.10) | MUST | 7.10 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze derives only the MSK (exported for the IKEv2 AUTH payload) and never derives or consumes the EMSK -- no EMSK is produced anywhere in internal/core/eap, so there is no EMSK to size-check | | `RFC3748-7.10-3` | An EAP method that establishes no shared key, such as MD5-Challenge, SHOULD NOT be used with IKEv2 (RFC 7296 S2.16 states the rule; S7.10 anchors this id) | SHOULD NOT | 7.10 | **positive:** `unit/verify` [`TestRFC3748IKEv2EAPModesSelectAKeyDerivingMethod`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc3748_ikev2_method_selection_test.go#L120). **negative:** `unit/verify` [`TestRFC3748IKEv2NoAuthModeSelectsAKeylessMethod`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc3748_ikev2_method_selection_test.go#L171) | | `RFC3748-4-4` | Octets outside the range of the Length field MUST be ignored upon reception (S4, S4.1) | MUST | 4 | **positive:** `unit/verify` [`TestRFC3748LengthBoundsTypeData`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L106). **negative:** `unit/verify` [`TestRFC3748LengthBoundsTypeData`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L118) | | `RFC3748-4.1-3` | A retransmitted Request MUST carry the same Identifier value, and a new (non-retransmission) Request MUST carry one different from the previous Request (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC3748NewRequestChangesIdentifier`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L136). **negative:** `unit/verify` [`TestRFC3748NewRequestChangesIdentifier`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L147) | | `RFC3748-4.1-4` | The Identifier field of a Response MUST match that of the currently outstanding Request (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC3748ResponseEchoesRequestIdentifier`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L168). **negative:** `unit/verify` [`TestRFC3748ResponseEchoesRequestIdentifier`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L179) | | `RFC3748-4.1-5` | The Type field of a Response MUST match that of the Request, or be a legacy or Expanded NAK (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC3748ResponseTypeMatchesRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L205). **negative:** `unit/verify` [`TestRFC3748ResponseTypeMatchesRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L216) | | `RFC3748-4.2-5` | Once the method completes unsuccessfully, the peer MUST terminate the conversation and indicate failure to the lower layer (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestRFC3748PeerEndsAnUnsuccessfulConversation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L250). **negative:** `unit/verify` [`TestRFC3748PeerEndsAnUnsuccessfulConversation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L265) | | `RFC3748-4.2-6` | Where the authenticator has sent no result indication, the peer MUST NOT silently discard the Success or Failure it waits for (S4.2) | MUST NOT | 4.2 | **positive:** `unit/verify` [`TestRFC3748PeerActsOnTheTerminalPacket`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L284). **negative:** `unit/verify` [`TestRFC3748PeerActsOnTheTerminalPacket`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L296) | | `RFC3748-4-5` | EAP packets carrying a Code outside 1-4 MUST be silently discarded by both authenticators and peers (S4) | MUST | 4 | **positive:** `unit/verify` [`TestRFC3748UndefinedCodesAreSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L223). **negative:** `unit/verify` [`TestRFC3748UndefinedCodesAreSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L263) | | `RFC3748-4.2-7` | By default, an EAP peer MUST silently discard a "canned" Success packet, one sent immediately upon connection (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestRFC3748PeerDiscardsACannedSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L82). **negative:** `unit/verify` [`TestRFC3748PeerDiscardsACannedSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L103) | | `RFC3748-4.2-8` | A peer receiving a Success or Failure packet where sending one is not explicitly permitted MUST silently discard it (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestRFC3748PeerDiscardsASuccessTheMethodDoesNotPermitYet`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L132). **negative:** `unit/verify` [`TestRFC3748PeerDiscardsASuccessTheMethodDoesNotPermitYet`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L145) | | `RFC3748-4.2-9` | On the peer, after success result indications have been exchanged by both sides, a Failure packet MUST be silently discarded (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestRFC3748PeerDiscardsAFailureAfterMutualSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L178). **negative:** `unit/verify` [`TestRFC3748PeerDiscardsAFailureAfterMutualSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L199) | | `RFC3748-2-3` | The authenticator MUST NOT send a Success or Failure packet when retransmitting or when it fails to get a response from the peer (S2) | MUST NOT | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-2.2-1` | The Success, Failure, Nak Response and Notification Request/Response messages MUST NOT be used to carry data destined for delivery to other EAP methods (S2.2) | MUST NOT | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.1-6` | Additional Request packets MUST be sent until a valid Response packet is received, an optional retry counter expires, or a lower layer failure indication is received (S4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.1-7` | The peer MUST send a Response packet in reply to a valid Request packet (S4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.1-8` | Requests MUST be processed in the order that they are received, and MUST be processed to their completion before inspecting the next Request (S4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.1-9` | A single Type MUST be specified for each EAP Request or Response (S4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.1-10` | An authenticator receiving a Response whose Identifier value does not match that of the currently outstanding Request MUST silently discard the Response (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestAuthenticatorDiscardsAResponseAnsweringNoOutstandingRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L182). **negative:** `unit/verify` [`TestAuthenticatorProcessesAResponseAnsweringTheOutstandingRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L203) | | `RFC3748-4.1-11` | An EAP server receiving a Response whose Type is neither the outstanding Request's nor a legacy Nak MUST silently discard it (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestAuthenticatorDiscardsAResponseOfAnotherType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L297). **negative:** `unit/verify` [`TestAuthenticatorProcessesAResponseOfTheMethodType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L324) | | `RFC3748-2.1-4` | A peer receiving a Request of a Type other than the one under way MUST silently discard it, because an authenticator MUST NOT send a Request of a different Type before the method's final round completes (S2.1) | MUST | 2.1 | **positive:** `unit/verify` [`TestPeerDiscardsARequestOfAnotherType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L347). **negative:** `unit/verify` [`TestPeerProcessesARequestOfTheMethodType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L387) | | `RFC3748-4.2-15` | The peer MUST silently discard a Success packet that arrives after the peer has ended the session (S4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.2-10` | Success and Failure packets MUST NOT be sent by an EAP authenticator if the specification of the given method does not explicitly permit the method to finish at that point (S4.2) | MUST NOT | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.2-11` | A peer MUST allow for the circumstance that a Success or Failure packet, being unacknowledged, can be lost (S4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.2-12` | After the authenticator sends a failure result indication to the peer, regardless of the response from the peer, it MUST subsequently send a Failure packet (S4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.2-13` | After the authenticator sends a success result indication to the peer and receives a success result indication from the peer, it MUST subsequently send a Success packet (S4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.2-14` | If the peer attempts to authenticate to the authenticator and fails to do so, the authenticator MUST send a Failure packet and MUST NOT grant access by sending a Success packet (S4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-7.10-5` | The MSK and EMSK MUST NOT be used directly to protect data (S7.10) | MUST NOT | 7.10 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-7.10-6` | The EMSK MUST remain on the EAP peer and EAP server where it is derived, and MUST NOT be transported to, shared with, or used to derive keys for additional parties (S7.10) | MUST | 7.10 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-7.10-7` | EAP peers, authenticators and authentication servers MUST be prepared for situations in which one of the parties discards the key state, which remains valid on another party (S7.10) | MUST | 7.10 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-5-1` | NAK (Type 3) and Expanded NAK (Type 254) MUST NOT be sent in a Request (S5) | MUST NOT | 5 | **positive:** `unit/verify` [`TestRFC3748NoNAKInARequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L313). **negative:** `unit/verify` [`TestRFC3748NoNAKInARequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L323) | | `RFC3748-5-2` | All EAP implementations MUST support Types 1-4, which are defined in this document (S5) | MUST | 5 | **positive:** `unit/verify` [`TestRFC3748PeerSupportsTypesOneToFour`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L316). **negative:** `unit/verify` [`TestRFC3748PeerRefusesATypeOutsideOneToFour`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L387) | | `RFC3748-5.2-1` | The peer MUST respond to a Notification Request with a Notification Response, unless the EAP authentication method specification prohibits the use of Notification messages (S5.2) | MUST | 5.2 | **positive:** `unit/verify` [`TestPeerAnswersANotificationRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_notification_test.go#L89). **negative:** `unit/verify` [`TestPeerNeverNaksANotificationRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_notification_test.go#L147) | | `RFC3748-5.2-2` | A Nak Response MUST NOT be sent in response to a Notification Request (S5.2) | MUST NOT | 5.2 | **positive:** `unit/verify` [`TestPeerNeverNaksANotificationRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_notification_test.go#L153). **negative:** `unit/verify` [`TestPeerNaksAnAuthenticationTypeButNotANotification`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_notification_test.go#L179) | | `RFC3748-5.3.1-1` | Where a peer receives a Request for an unacceptable authentication Type (4-253,255), or a peer lacking support for Expanded Types receives a Request for Type 254, a Nak Response (Type 3) MUST be sent (S5.3.1, S5.7) | MUST | 5.3.1 | **positive:** `unit/verify` [`TestPeerNaksAnExpandedTypeRequestWithALegacyNak`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L259). **positive:** `unit/verify` [`TestPeerNaksAnUnacceptableAuthenticationType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L140). **negative:** `unit/verify` [`TestPeerDoesNotNakATypeItHandles`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L179) | | `RFC3748-5.3.1-2` | The Type-Data field of the Nak Response (Type 3) MUST contain one or more octets indicating the desired authentication Type(s), one octet per Type, or the value zero (0) to indicate no proposed alternative (S5.3.1) | MUST | 5.3.1 | **positive:** `unit/verify` [`TestPeerNaksAnUnacceptableAuthenticationType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L147). **negative:** `unit/verify` [`TestNakNamesTheConfiguredMethod`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L230) | | `RFC3748-5.3.1-3` | The Identifier field of a legacy Nak Response MUST match the Identifier field of the Request packet that it is sent in response to (S5.3.1) | MUST | 5.3.1 | **positive:** `unit/verify` [`TestNakIdentifierMatchesTheRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L289). **negative:** `unit/verify` [`TestNakIdentifierMatchesTheRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L304) | | `RFC3748-5.3.1-4` | The legacy Nak MUST NOT be used as a general purpose error indication, such as for communication of error messages or negotiation of method-specific parameters (S5.3.1) | MUST NOT | 5.3.1 | **positive:** `unit/verify` [`TestPeerDoesNotNakAMethodError`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L324). **negative:** `unit/verify` [`TestPeerDoesNotNakAMethodError`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L347) | | `RFC3748-5.4-1` | A Response MUST be sent in reply to an MD5-Challenge Request (S5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestRFC3748MD5ChallengeRequestDrawsAResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L117). **negative:** `unit/verify` [`TestRFC3748MD5ChallengeRequeryDrawsNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L206) | | `RFC3748-5.4-2` | EAP peer and EAP server implementations MUST support the MD5-Challenge mechanism (S5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestRFC3748MD5ChallengeSupportedByBothRoles`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L237). **negative:** `unit/verify` [`TestRFC3748MD5ChallengeIsTheConfiguredMethod`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L278) | | `RFC3748-5.1-2` | The Identity Response field MUST NOT be null terminated (S5.1) | MUST NOT | 5.1 | **positive:** `unit/verify` [`TestRFC3748IdentityResponseIsNotNullTerminated`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L366). **negative:** `unit/verify` [`TestRFC3748IdentityResponseIsNotNullTerminated`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L374) | | `RFC3748-7.5-1` | Where an EAP method employs a per-packet MIC, the peer and an authenticator not in pass-through mode MUST validate it (S7.5) | MUST | 7.5 | **positive:** `unit/verify` [`TestRFC3748EAPTLSValidatesItsPerPacketMIC`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L473). **negative:** `unit/verify` [`TestRFC3748EAPTLSValidatesItsPerPacketMIC`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L486) | | `RFC3748-7.10-4` | EAP methods deriving keys MUST provide for mutual authentication between the EAP peer and the EAP server (S7.10) | MUST | 7.10 | **positive:** `unit/verify` [`TestRFC3748KeyDerivingMethodAuthenticatesBothEnds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L505). **negative:** `unit/verify` [`TestRFC3748KeyDerivingMethodAuthenticatesBothEnds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L520) | | `RFC3748-5.1-1` | Identity (Type 1) SHOULD NOT be relied upon for authentication; it is sent in cleartext (S5.1, S7.1) | SHOULD | 5.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-7.4-1` | EAP methods used in environments subject to MITM attacks SHOULD provide mutual authentication (S7.4) | SHOULD | 7.4 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4.2-3` | Peer SHOULD treat lower-layer success as implicit EAP Success if EAP Success is lost (S4.2) | SHOULD | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-2.3-2` | The authenticator MAY act as a pass-through, forwarding EAP to a back-end server via AAA (S2.3) | MAY | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC3748-4-3` | Octets beyond the Length field MAY be ignored (S4) | MAY | 4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3748-4.1-2`](#rfc3748-4.1-2) The peer MUST re-send its last Response when a duplicate Request arrives (S4.1) | no test | no test carries this requirement id; annotated {not-applicable}: duplicate-Request handling is delegated to the IKEv2 carrier -- on timeout the initiator (EAP peer) re-sends its last IKE_AUTH message, which carries its last EAP Response (sa.LastSentMsg, internal/component/ike/engine/fsm.go:138); the EAP peer state machine holds no per-Request retransmission state | | [`RFC3748-2.3-1`](#rfc3748-2.3-1) A pass-through authenticator MUST forward packets of any Type without interpreting method-specific data (S2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authenticator always terminates the EAP method locally (eap.Session, internal/core/eap/eap.go:130); there is no AAA/RADIUS back end in the IKE engine, so it never operates as a pass-through | | [`RFC3748-3.1-1`](#rfc3748-3.1-1) Lower layer MUST provide in-order delivery for EAP (S3.1) | no test | no test carries this requirement id; annotated {not-applicable}: in-order delivery is a lower-layer obligation; ze carries EAP only inside IKEv2, whose message-ID sequencing delivers each IKE_AUTH request/response in order (RFC 7296 Section 2.3; internal/component/ike/engine/msgid.go:76), so the EAP framework code neither provides nor can violate it | | [`RFC3748-3.1-2`](#rfc3748-3.1-2) Lower layer MUST provide error detection (CRC, checksum, or MIC) (S3.1) | no test | no test carries this requirement id; annotated {not-applicable}: bit-error detection is a lower-layer obligation; ze's EAP packets travel inside the IKEv2 SK payload whose AEAD/ICV check rejects any corrupted frame on decrypt (internal/component/ike/engine/fsm.go:658), so the EAP framework code adds no CRC of its own | | [`RFC3748-3.1-3`](#rfc3748-3.1-3) Lower layer MUST support a minimum MTU of 1020 octets for EAP packets (S3.1) | no test | no test carries this requirement id; annotated {not-applicable}: the 1020-octet minimum MTU is a lower-layer obligation; ze carries EAP inside IKEv2 SK payloads over UDP, which admit EAP packets far larger than 1020 octets, and EAP-TLS method data is itself fragmented in 1024-octet chunks (internal/core/eap/eap_tls.go:28) | | [`RFC3748-3.1-4`](#rfc3748-3.1-4) Lower layer MUST provide duplicate detection (S3.1) | no test | no test carries this requirement id; annotated {not-applicable}: duplicate detection is a lower-layer obligation; IKEv2 detects a duplicated message by message ID and replays the cached response (internal/component/ike/engine/msgid.go:79; responder.go:81), so the EAP framework code neither provides nor can violate it | | [`RFC3748-5.7-1`](#rfc3748-5.7-1) When Type = 254 (Expanded Types), Vendor-Id 0 = IETF namespace (S5.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze offers MD5-Challenge (4), EAP-TLS (13) and EAP-MSCHAPv2 (26); NewSession rejects every other type (NewSession, internal/core/eap/eap.go) and the codec never encodes or parses an Expanded Type (254) packet or its Vendor-Id field, so no Vendor-Id namespace rule can bind. The peer reads TypeExpandedEAP only to route a Type-254 Request to the legacy Nak that Section 5.7 prescribes for a peer not equipped to interpret it (PeerSession.naks, internal/core/eap/peer.go), and that Nak carries no Vendor-Id | | [`RFC3748-7.10-2`](#rfc3748-7.10-2) EMSK MUST be at least 64 octets (S7.10) | no test | no test carries this requirement id; annotated {not-applicable}: ze derives only the MSK (exported for the IKEv2 AUTH payload) and never derives or consumes the EMSK -- no EMSK is produced anywhere in internal/core/eap, so there is no EMSK to size-check | | [`RFC3748-2-3`](#rfc3748-2-3) The authenticator MUST NOT send a Success or Failure packet when retransmitting or when it fails to get a response from the peer (S2) | no test | no test carries this requirement id | | [`RFC3748-2.2-1`](#rfc3748-2.2-1) The Success, Failure, Nak Response and Notification Request/Response messages MUST NOT be used to carry data destined for delivery to other EAP methods (S2.2) | no test | no test carries this requirement id | | [`RFC3748-4.1-6`](#rfc3748-4.1-6) Additional Request packets MUST be sent until a valid Response packet is received, an optional retry counter expires, or a lower layer failure indication is received (S4.1) | no test | no test carries this requirement id | | [`RFC3748-4.1-7`](#rfc3748-4.1-7) The peer MUST send a Response packet in reply to a valid Request packet (S4.1) | no test | no test carries this requirement id | | [`RFC3748-4.1-8`](#rfc3748-4.1-8) Requests MUST be processed in the order that they are received, and MUST be processed to their completion before inspecting the next Request (S4.1) | no test | no test carries this requirement id | | [`RFC3748-4.1-9`](#rfc3748-4.1-9) A single Type MUST be specified for each EAP Request or Response (S4.1) | no test | no test carries this requirement id | | [`RFC3748-4.2-15`](#rfc3748-4.2-15) The peer MUST silently discard a Success packet that arrives after the peer has ended the session (S4.2) | no test | no test carries this requirement id | | [`RFC3748-4.2-10`](#rfc3748-4.2-10) Success and Failure packets MUST NOT be sent by an EAP authenticator if the specification of the given method does not explicitly permit the method to finish at that point (S4.2) | no test | no test carries this requirement id | | [`RFC3748-4.2-11`](#rfc3748-4.2-11) A peer MUST allow for the circumstance that a Success or Failure packet, being unacknowledged, can be lost (S4.2) | no test | no test carries this requirement id | | [`RFC3748-4.2-12`](#rfc3748-4.2-12) After the authenticator sends a failure result indication to the peer, regardless of the response from the peer, it MUST subsequently send a Failure packet (S4.2) | no test | no test carries this requirement id | | [`RFC3748-4.2-13`](#rfc3748-4.2-13) After the authenticator sends a success result indication to the peer and receives a success result indication from the peer, it MUST subsequently send a Success packet (S4.2) | no test | no test carries this requirement id | | [`RFC3748-4.2-14`](#rfc3748-4.2-14) If the peer attempts to authenticate to the authenticator and fails to do so, the authenticator MUST send a Failure packet and MUST NOT grant access by sending a Success packet (S4.2) | no test | no test carries this requirement id | | [`RFC3748-7.10-5`](#rfc3748-7.10-5) The MSK and EMSK MUST NOT be used directly to protect data (S7.10) | no test | no test carries this requirement id | | [`RFC3748-7.10-6`](#rfc3748-7.10-6) The EMSK MUST remain on the EAP peer and EAP server where it is derived, and MUST NOT be transported to, shared with, or used to derive keys for additional parties (S7.10) | no test | no test carries this requirement id | | [`RFC3748-7.10-7`](#rfc3748-7.10-7) EAP peers, authenticators and authentication servers MUST be prepared for situations in which one of the parties discards the key state, which remains valid on another party (S7.10) | no test | no test carries this requirement id | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3748-4-1`](#rfc3748-4-1) Packets shorter than the Length field indicates MUST be silently discarded (S4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748PacketLengthDiscard`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L80) | unit/verify | unproven | | positive | [`TestRFC3748PacketLengthDiscard`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L73) | unit/verify | unproven | ### [`RFC3748-4-2`](#rfc3748-4-2) Minimum EAP packet length is 4 octets (S4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748MinimumPacketLength`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L99) | unit/verify | unproven | | positive | [`TestRFC3748MinimumPacketLength`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L89) | unit/verify | unproven | ### [`RFC3748-2-1`](#rfc3748-2-1) The authenticator MUST operate in lock-step: only one outstanding Request at a time (S2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC3748AuthenticatorLockStep`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L167) | unit/verify | unproven | ### [`RFC3748-2-2`](#rfc3748-2-2) The authenticator MUST receive a valid Response before sending a new Request (S2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748AuthenticatorRequiresValidResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L220) | unit/verify | unproven | | positive | [`TestRFC3748AuthenticatorRequiresValidResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L208) | unit/verify | unproven | ### [`RFC3748-2.1-1`](#rfc3748-2.1-1) Only one authentication method (Type >= 4) per EAP conversation (S2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748OneMethodPerConversation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L242) | unit/verify | unproven | | positive | [`TestRFC3748OneMethodPerConversation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L235) | unit/verify | unproven | ### [`RFC3748-2.1-2`](#rfc3748-2.1-2) After the method completes, the authenticator MUST send Success or Failure (S2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748MethodCompletionSendsResult`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L277) | unit/verify | unproven | | positive | [`TestRFC3748MethodCompletionSendsResult`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L267) | unit/verify | unproven | ### [`RFC3748-2.1-3`](#rfc3748-2.1-3) The peer MUST NOT send a NAK (Type 3) after the first non-NAK Response in a conversation (S2.1, S5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748PeerNaksBeforeItCommitsToAMethod`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L385) | unit/verify | revert, verified | | positive | [`TestRFC3748PeerNaksBeforeItCommitsToAMethod`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L372) | unit/verify | revert, verified | ### [`RFC3748-4.1-1`](#rfc3748-4.1-1) Only the authenticator retransmits on timer; the peer MUST NOT retransmit on its own timer (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC3748PeerHasNoRetransmitTimer`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L289) | unit/verify | unproven | ### [`RFC3748-4.1-2`](#rfc3748-4.1-2) The peer MUST re-send its last Response when a duplicate Request arrives (S4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.1-2, so no unit is bound to it. ### [`RFC3748-4.2-1`](#rfc3748-4.2-1) Success and Failure packets MUST NOT be retransmitted (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC3748SuccessFailureNotRetransmitted`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L140) | unit/verify | unproven | ### [`RFC3748-4.2-2`](#rfc3748-4.2-2) Success and Failure contain only Code, Identifier, and Length (4 octets, no Type field) (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748SuccessFailureFormat`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L130) | unit/verify | unproven | | positive | [`TestRFC3748SuccessFailureFormat`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L110) | unit/verify | unproven | ### [`RFC3748-4.2-4`](#rfc3748-4.2-4) The Identifier of a Success or Failure MUST match the Identifier of the Response it answers (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestFailureIdentifierMatchesResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L24) | unit/verify | unproven | | positive | [`TestFailureIdentifierMatchesResponseOnNAK`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L49) | unit/verify | unproven | | positive | [`TestIdentityFailureIdentifierMatchesResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L84) | unit/verify | unproven | | positive | [`TestSuccessIdentifierMatchesResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L153) | unit/verify | unproven | ### [`RFC3748-2.3-1`](#rfc3748-2.3-1) A pass-through authenticator MUST forward packets of any Type without interpreting method-specific data (S2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-2.3-1, so no unit is bound to it. ### [`RFC3748-3.1-1`](#rfc3748-3.1-1) Lower layer MUST provide in-order delivery for EAP (S3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-3.1-1, so no unit is bound to it. ### [`RFC3748-3.1-2`](#rfc3748-3.1-2) Lower layer MUST provide error detection (CRC, checksum, or MIC) (S3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-3.1-2, so no unit is bound to it. ### [`RFC3748-3.1-3`](#rfc3748-3.1-3) Lower layer MUST support a minimum MTU of 1020 octets for EAP packets (S3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-3.1-3, so no unit is bound to it. ### [`RFC3748-3.1-4`](#rfc3748-3.1-4) Lower layer MUST provide duplicate detection (S3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-3.1-4, so no unit is bound to it. ### [`RFC3748-5.7-1`](#rfc3748-5.7-1) When Type = 254 (Expanded Types), Vendor-Id 0 = IETF namespace (S5.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-5.7-1, so no unit is bound to it. ### [`RFC3748-7.10-1`](#rfc3748-7.10-1) MSK MUST be at least 64 octets (S7.10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC3748MSKSize`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_test.go#L324) | unit/verify | unproven | ### [`RFC3748-7.10-2`](#rfc3748-7.10-2) EMSK MUST be at least 64 octets (S7.10) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-7.10-2, so no unit is bound to it. ### [`RFC3748-7.10-3`](#rfc3748-7.10-3) An EAP method that establishes no shared key, such as MD5-Challenge, SHOULD NOT be used with IKEv2 (RFC 7296 S2.16 states the rule; S7.10 anchors this id) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748IKEv2NoAuthModeSelectsAKeylessMethod`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc3748_ikev2_method_selection_test.go#L171) | unit/verify | revert, verified | | positive | [`TestRFC3748IKEv2EAPModesSelectAKeyDerivingMethod`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc3748_ikev2_method_selection_test.go#L120) | unit/verify | revert, verified | ### [`RFC3748-4-4`](#rfc3748-4-4) Octets outside the range of the Length field MUST be ignored upon reception (S4, S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748LengthBoundsTypeData`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L118) | unit/verify | unproven | | positive | [`TestRFC3748LengthBoundsTypeData`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L106) | unit/verify | unproven | ### [`RFC3748-4.1-3`](#rfc3748-4.1-3) A retransmitted Request MUST carry the same Identifier value, and a new (non-retransmission) Request MUST carry one different from the previous Request (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748NewRequestChangesIdentifier`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L147) | unit/verify | unproven | | positive | [`TestRFC3748NewRequestChangesIdentifier`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L136) | unit/verify | unproven | ### [`RFC3748-4.1-4`](#rfc3748-4.1-4) The Identifier field of a Response MUST match that of the currently outstanding Request (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748ResponseEchoesRequestIdentifier`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L179) | unit/verify | unproven | | positive | [`TestRFC3748ResponseEchoesRequestIdentifier`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L168) | unit/verify | unproven | ### [`RFC3748-4.1-5`](#rfc3748-4.1-5) The Type field of a Response MUST match that of the Request, or be a legacy or Expanded NAK (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748ResponseTypeMatchesRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L216) | unit/verify | revert, verified | | positive | [`TestRFC3748ResponseTypeMatchesRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L205) | unit/verify | revert, verified | ### [`RFC3748-4.2-5`](#rfc3748-4.2-5) Once the method completes unsuccessfully, the peer MUST terminate the conversation and indicate failure to the lower layer (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748PeerEndsAnUnsuccessfulConversation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L265) | unit/verify | unproven | | positive | [`TestRFC3748PeerEndsAnUnsuccessfulConversation`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L250) | unit/verify | unproven | ### [`RFC3748-4.2-6`](#rfc3748-4.2-6) Where the authenticator has sent no result indication, the peer MUST NOT silently discard the Success or Failure it waits for (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748PeerActsOnTheTerminalPacket`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L296) | unit/verify | unproven | | positive | [`TestRFC3748PeerActsOnTheTerminalPacket`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L284) | unit/verify | unproven | ### [`RFC3748-4-5`](#rfc3748-4-5) EAP packets carrying a Code outside 1-4 MUST be silently discarded by both authenticators and peers (S4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748UndefinedCodesAreSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L263) | unit/verify | revert, verified | | positive | [`TestRFC3748UndefinedCodesAreSilentlyDiscarded`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L223) | unit/verify | revert, verified | ### [`RFC3748-4.2-7`](#rfc3748-4.2-7) By default, an EAP peer MUST silently discard a "canned" Success packet, one sent immediately upon connection (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748PeerDiscardsACannedSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L103) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | | positive | [`TestRFC3748PeerDiscardsACannedSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L82) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | ### [`RFC3748-4.2-8`](#rfc3748-4.2-8) A peer receiving a Success or Failure packet where sending one is not explicitly permitted MUST silently discard it (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748PeerDiscardsASuccessTheMethodDoesNotPermitYet`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L145) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | | positive | [`TestRFC3748PeerDiscardsASuccessTheMethodDoesNotPermitYet`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L132) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | ### [`RFC3748-4.2-9`](#rfc3748-4.2-9) On the peer, after success result indications have been exchanged by both sides, a Failure packet MUST be silently discarded (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748PeerDiscardsAFailureAfterMutualSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L199) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | | positive | [`TestRFC3748PeerDiscardsAFailureAfterMutualSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L178) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | ### [`RFC3748-2-3`](#rfc3748-2-3) The authenticator MUST NOT send a Success or Failure packet when retransmitting or when it fails to get a response from the peer (S2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-2-3, so no unit is bound to it. ### [`RFC3748-2.2-1`](#rfc3748-2.2-1) The Success, Failure, Nak Response and Notification Request/Response messages MUST NOT be used to carry data destined for delivery to other EAP methods (S2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-2.2-1, so no unit is bound to it. ### [`RFC3748-4.1-6`](#rfc3748-4.1-6) Additional Request packets MUST be sent until a valid Response packet is received, an optional retry counter expires, or a lower layer failure indication is received (S4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.1-6, so no unit is bound to it. ### [`RFC3748-4.1-7`](#rfc3748-4.1-7) The peer MUST send a Response packet in reply to a valid Request packet (S4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.1-7, so no unit is bound to it. ### [`RFC3748-4.1-8`](#rfc3748-4.1-8) Requests MUST be processed in the order that they are received, and MUST be processed to their completion before inspecting the next Request (S4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.1-8, so no unit is bound to it. ### [`RFC3748-4.1-9`](#rfc3748-4.1-9) A single Type MUST be specified for each EAP Request or Response (S4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.1-9, so no unit is bound to it. ### [`RFC3748-4.1-10`](#rfc3748-4.1-10) An authenticator receiving a Response whose Identifier value does not match that of the currently outstanding Request MUST silently discard the Response (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAuthenticatorProcessesAResponseAnsweringTheOutstandingRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L203) | unit/verify | unproven | | positive | [`TestAuthenticatorDiscardsAResponseAnsweringNoOutstandingRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_identifier_test.go#L182) | unit/verify | unproven | ### [`RFC3748-4.1-11`](#rfc3748-4.1-11) An EAP server receiving a Response whose Type is neither the outstanding Request's nor a legacy Nak MUST silently discard it (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAuthenticatorProcessesAResponseOfTheMethodType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L324) | unit/verify | unproven | | positive | [`TestAuthenticatorDiscardsAResponseOfAnotherType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L297) | unit/verify | unproven | ### [`RFC3748-2.1-4`](#rfc3748-2.1-4) A peer receiving a Request of a Type other than the one under way MUST silently discard it, because an authenticator MUST NOT send a Request of a different Type before the method's final round completes (S2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPeerProcessesARequestOfTheMethodType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L387) | unit/verify | unproven | | positive | [`TestPeerDiscardsARequestOfAnotherType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_discard_test.go#L347) | unit/verify | revert, verified | ### [`RFC3748-4.2-15`](#rfc3748-4.2-15) The peer MUST silently discard a Success packet that arrives after the peer has ended the session (S4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.2-15, so no unit is bound to it. ### [`RFC3748-4.2-10`](#rfc3748-4.2-10) Success and Failure packets MUST NOT be sent by an EAP authenticator if the specification of the given method does not explicitly permit the method to finish at that point (S4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.2-10, so no unit is bound to it. ### [`RFC3748-4.2-11`](#rfc3748-4.2-11) A peer MUST allow for the circumstance that a Success or Failure packet, being unacknowledged, can be lost (S4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.2-11, so no unit is bound to it. ### [`RFC3748-4.2-12`](#rfc3748-4.2-12) After the authenticator sends a failure result indication to the peer, regardless of the response from the peer, it MUST subsequently send a Failure packet (S4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.2-12, so no unit is bound to it. ### [`RFC3748-4.2-13`](#rfc3748-4.2-13) After the authenticator sends a success result indication to the peer and receives a success result indication from the peer, it MUST subsequently send a Success packet (S4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.2-13, so no unit is bound to it. ### [`RFC3748-4.2-14`](#rfc3748-4.2-14) If the peer attempts to authenticate to the authenticator and fails to do so, the authenticator MUST send a Failure packet and MUST NOT grant access by sending a Success packet (S4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-4.2-14, so no unit is bound to it. ### [`RFC3748-7.10-5`](#rfc3748-7.10-5) The MSK and EMSK MUST NOT be used directly to protect data (S7.10) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-7.10-5, so no unit is bound to it. ### [`RFC3748-7.10-6`](#rfc3748-7.10-6) The EMSK MUST remain on the EAP peer and EAP server where it is derived, and MUST NOT be transported to, shared with, or used to derive keys for additional parties (S7.10) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-7.10-6, so no unit is bound to it. ### [`RFC3748-7.10-7`](#rfc3748-7.10-7) EAP peers, authenticators and authentication servers MUST be prepared for situations in which one of the parties discards the key state, which remains valid on another party (S7.10) Audit verdict: not audited: no reader has judged these tests No test carries RFC3748-7.10-7, so no unit is bound to it. ### [`RFC3748-5-1`](#rfc3748-5-1) NAK (Type 3) and Expanded NAK (Type 254) MUST NOT be sent in a Request (S5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748NoNAKInARequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L323) | unit/verify | unproven | | positive | [`TestRFC3748NoNAKInARequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L313) | unit/verify | unproven | ### [`RFC3748-5-2`](#rfc3748-5-2) All EAP implementations MUST support Types 1-4, which are defined in this document (S5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748PeerRefusesATypeOutsideOneToFour`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L387) | unit/verify | revert, verified | | positive | [`TestRFC3748PeerSupportsTypesOneToFour`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L316) | unit/verify | revert, verified | ### [`RFC3748-5.2-1`](#rfc3748-5.2-1) The peer MUST respond to a Notification Request with a Notification Response, unless the EAP authentication method specification prohibits the use of Notification messages (S5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPeerNeverNaksANotificationRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_notification_test.go#L147) | unit/verify | revert, verified | | positive | [`TestPeerAnswersANotificationRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_notification_test.go#L89) | unit/verify | revert, verified | ### [`RFC3748-5.2-2`](#rfc3748-5.2-2) A Nak Response MUST NOT be sent in response to a Notification Request (S5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPeerNaksAnAuthenticationTypeButNotANotification`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_notification_test.go#L179) | unit/verify | revert, verified | | positive | [`TestPeerNeverNaksANotificationRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_notification_test.go#L153) | unit/verify | revert, verified | ### [`RFC3748-5.3.1-1`](#rfc3748-5.3.1-1) Where a peer receives a Request for an unacceptable authentication Type (4-253,255), or a peer lacking support for Expanded Types receives a Request for Type 254, a Nak Response (Type 3) MUST be sent (S5.3.1, S5.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPeerDoesNotNakATypeItHandles`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L179) | unit/verify | revert, verified | | positive | [`TestPeerNaksAnExpandedTypeRequestWithALegacyNak`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L259) | unit/verify | revert, verified | | positive | [`TestPeerNaksAnUnacceptableAuthenticationType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L140) | unit/verify | revert, verified | ### [`RFC3748-5.3.1-2`](#rfc3748-5.3.1-2) The Type-Data field of the Nak Response (Type 3) MUST contain one or more octets indicating the desired authentication Type(s), one octet per Type, or the value zero (0) to indicate no proposed alternative (S5.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNakNamesTheConfiguredMethod`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L230) | unit/verify | revert, verified | | positive | [`TestPeerNaksAnUnacceptableAuthenticationType`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L147) | unit/verify | revert, verified | ### [`RFC3748-5.3.1-3`](#rfc3748-5.3.1-3) The Identifier field of a legacy Nak Response MUST match the Identifier field of the Request packet that it is sent in response to (S5.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNakIdentifierMatchesTheRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L304) | unit/verify | revert, verified | | positive | [`TestNakIdentifierMatchesTheRequest`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L289) | unit/verify | revert, verified | ### [`RFC3748-5.3.1-4`](#rfc3748-5.3.1-4) The legacy Nak MUST NOT be used as a general purpose error indication, such as for communication of error messages or negotiation of method-specific parameters (S5.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPeerDoesNotNakAMethodError`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L347) | unit/verify | revert, verified | | positive | [`TestPeerDoesNotNakAMethodError`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_nak_test.go#L324) | unit/verify | revert, verified | ### [`RFC3748-5.4-1`](#rfc3748-5.4-1) A Response MUST be sent in reply to an MD5-Challenge Request (S5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748MD5ChallengeRequeryDrawsNoResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L206) | unit/verify | revert, verified | | positive | [`TestRFC3748MD5ChallengeRequestDrawsAResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L117) | unit/verify | revert, verified | ### [`RFC3748-5.4-2`](#rfc3748-5.4-2) EAP peer and EAP server implementations MUST support the MD5-Challenge mechanism (S5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748MD5ChallengeIsTheConfiguredMethod`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L278) | unit/verify | revert, verified | | positive | [`TestRFC3748MD5ChallengeSupportedByBothRoles`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_md5challenge_test.go#L237) | unit/verify | revert, verified | ### [`RFC3748-5.1-2`](#rfc3748-5.1-2) The Identity Response field MUST NOT be null terminated (S5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748IdentityResponseIsNotNullTerminated`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L374) | unit/verify | unproven | | positive | [`TestRFC3748IdentityResponseIsNotNullTerminated`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L366) | unit/verify | unproven | ### [`RFC3748-7.5-1`](#rfc3748-7.5-1) Where an EAP method employs a per-packet MIC, the peer and an authenticator not in pass-through mode MUST validate it (S7.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748EAPTLSValidatesItsPerPacketMIC`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L486) | unit/verify | unproven | | positive | [`TestRFC3748EAPTLSValidatesItsPerPacketMIC`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L473) | unit/verify | unproven | ### [`RFC3748-7.10-4`](#rfc3748-7.10-4) EAP methods deriving keys MUST provide for mutual authentication between the EAP peer and the EAP server (S7.10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC3748KeyDerivingMethodAuthenticatesBothEnds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L520) | unit/verify | unproven | | positive | [`TestRFC3748KeyDerivingMethodAuthenticatesBothEnds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc3748_walk_test.go#L505) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | rfc3748walk (extraction sign-off agent, spec rfcgate-6-supported-extraction-signoff) | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc3748.txt | | Source fingerprint | df5b1ebf6f637eef | | Record | rfc/extraction/rfc3748.json | | Mapped sentences | 53 | | Declined as scope | 50 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, abstract, status of this memo and table of contents. | | `1` | not stated | 0 | walked | not stated | | `1.1` | not stated | 0 | walked | not stated | | `1.2` | not stated | 1 | walked | not stated | | `1.3` | not stated | 0 | walked | not stated | | `2` | not stated | 3 | walked | not stated | | `2.1` | not stated | 4 | walked | not stated | | `2.2` | not stated | 1 | walked | not stated | | `2.3` | not stated | 4 | walked | not stated | | `2.4` | not stated | 0 | walked | not stated | | `3` | not stated | 0 | walked | not stated | | `3.1` | not stated | 1 | walked | not stated | | `3.2` | not stated | 1 | walked | not stated | | `3.2.1` | not stated | 0 | walked | not stated | | `3.3` | not stated | 0 | walked | not stated | | `3.4` | not stated | 0 | walked | not stated | | `4` | not stated | 3 | walked | not stated | | `4.1` | not stated | 16 | walked | not stated | | `4.2` | not stated | 16 | walked | not stated | | `4.3` | not stated | 1 | walked | not stated | | `5` | not stated | 2 | walked | not stated | | `5.1` | not stated | 1 | walked | not stated | | `5.2` | not stated | 5 | walked | not stated | | `5.3` | not stated | 0 | walked | not stated | | `5.3.1` | not stated | 4 | walked | not stated | | `5.3.2` | not stated | 4 | walked | not stated | | `5.4` | not stated | 4 | walked | not stated | | `5.5` | not stated | 4 | walked | not stated | | `5.6` | not stated | 5 | walked | not stated | | `5.7` | not stated | 2 | walked | not stated | | `5.8` | not stated | 0 | walked | not stated | | `6` | not stated | 0 | skipped (iana) | IANA Considerations: the registry actions bind IANA, not an implementation. | | `6.1` | not stated | 0 | skipped (iana) | IANA Considerations, Packet Codes: a registry allocation policy. | | `6.2` | not stated | 0 | skipped (iana) | IANA Considerations, Method Types: a registry allocation policy. | | `7` | not stated | 0 | walked | not stated | | `7.1` | not stated | 0 | walked | not stated | | `7.2` | not stated | 4 | walked | not stated | | `7.2.1` | not stated | 2 | walked | not stated | | `7.3` | not stated | 0 | walked | not stated | | `7.4` | not stated | 0 | walked | not stated | | `7.5` | not stated | 1 | walked | not stated | | `7.6` | not stated | 0 | walked | not stated | | `7.7` | not stated | 0 | walked | not stated | | `7.8` | not stated | 0 | walked | not stated | | `7.9` | not stated | 0 | walked | not stated | | `7.10` | not stated | 11 | walked | not stated | | `7.11` | not stated | 0 | walked | not stated | | `7.12` | not stated | 0 | walked | not stated | | `7.13` | not stated | 1 | walked | not stated | | `7.14` | not stated | 0 | walked | not stated | | `7.15` | not stated | 0 | walked | not stated | | `7.16` | not stated | 2 | walked | not stated | | `8` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `9` | References | 0 | skipped (references) | References. | | `9.1` | Normative References | 0 | skipped (references) | Normative References. | | `9.2` | Informative References | 0 | skipped (references) | Informative References. | | `A` | not stated | 0 | skipped (appendix-non-normative) | Appendix A, Changes from RFC 2284: a historical diff against an obsoleted document. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1.2:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Section 1.2 is the Terminology list and the sentence sits inside the definition ENTRY for 'Displayable Message', fixing what the term means wherever a later section uses it. The obligations that put a displayable message on the wire are stated at Sections 5.1, 5.2, 5.5 and 5.6, and those sites carry their own dispositions. | The message encoding MUST follow the UTF-8 transformation format [RFC2279]. | | `2:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates the Section 2.1 obligation to close the conversation with a Success or a Failure, for the Failure arm. Site 4.2:1 maps the id. | [4] The conversation continues until the authenticator cannot authenticate the peer (unacceptable Responses to one or more Requests), in which case the authenticator implementation MUST transmit an EAP Failure (Code 4). | | `2:3` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates the same Section 2.1 obligation for the Success arm. Site 4.2:1 maps the id. | Alternatively, the authentication conversation can continue until the authenticator determines that successful authentication has occurred, in which case the authenticator MUST transmit an EAP Success (Code 3). | | `2.1:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of a 'tunneled' EAP method specification, a document role rather than a wire role, and the sentence states what such a specification must say about running a second method inside the tunnel. Ze publishes no EAP method specification, and neither method it runs tunnels a second one: tlsMethod carries TLS records and no EAP packet (tlsMethod.Process, internal/core/eap/eap_tls.go), and mschapv2Method carries the MS-CHAPv2 opcodes alone (mschapv2Method.Process, internal/core/eap/eap_mschapv2.go). The producer that would act as the role if Ze did is the specification of such a method, and Ze holds only the implementations of RFC 5216 and draft-kamath-pppext-eap-mschapv2-02. | To address security vulnerabilities, "tunneled" methods MUST support protection against man-in-the-middle attacks. | | `2.3:1` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on pass-through operation, which Ze does not offer. The sentence states that a pass-through authenticator must be capable of forwarding a Code=2 Response to the backend authentication server. RFC 3748 Section 2 states the option in its own words: 'Network Access Server (NAS) devices (e.g., a switch or access point) do not have to understand each authentication method and MAY act as a pass-through agent for a backend authentication server. Support for pass-through is optional.' NewSession (internal/core/eap/eap.go) builds the method in process and the IKE engine has no AAA back end for EAP: nothing under internal/component/radius/ produces an EAP-Message attribute, and handleResponderEAP (internal/component/ike/engine/responder_eap.go) answers from the local eap.Session alone. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | A pass-through authenticator implementation MUST be capable of forwarding EAP packets received from the peer with Code=2 (Response) to the backend authentication server. | | `2.3:2` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on pass-through operation, which Ze does not offer. The sentence states that a pass-through authenticator must be capable of forwarding Code=1, Code=3 and Code=4 packets received from the backend authentication server to the peer. RFC 3748 Section 2 states the option in its own words: 'Network Access Server (NAS) devices (e.g., a switch or access point) do not have to understand each authentication method and MAY act as a pass-through agent for a backend authentication server. Support for pass-through is optional.' NewSession (internal/core/eap/eap.go) builds the method in process and the IKE engine has no AAA back end for EAP: nothing under internal/component/radius/ produces an EAP-Message attribute, and handleResponderEAP (internal/component/ike/engine/responder_eap.go) answers from the local eap.Session alone. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | It also MUST be capable of receiving EAP packets from the backend authentication server and forwarding EAP packets of Code=1 (Request), Code=3 (Success), and Code=4 (Failure) to the peer. | | `2.3:4` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on pass-through operation, which Ze does not offer. The sentence states that a compliant pass-through authenticator must by default forward EAP packets of any Type. RFC 3748 Section 2 states the option in its own words: 'Network Access Server (NAS) devices (e.g., a switch or access point) do not have to understand each authentication method and MAY act as a pass-through agent for a backend authentication server. Support for pass-through is optional.' NewSession (internal/core/eap/eap.go) builds the method in process and the IKE engine has no AAA back end for EAP: nothing under internal/component/radius/ produces an EAP-Message attribute, and handleResponderEAP (internal/component/ike/engine/responder_eap.go) answers from the local eap.Session alone. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | For sessions in which the authenticator acts as a pass-through, it MUST determine the outcome of the authentication solely based on the Accept/Reject indication sent by the backend authentication server; the outcome MUST NOT be determined by the contents of an EAP packet sent along with the Accept/Reject indication, or the absence of such an encapsulated EAP packet. | | `3.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is a PPP implementation that has negotiated EAP as its LCP Authentication-Protocol (0xC227), which Section 3.2 titles 'EAP Usage Within PPP'. Ze never acts as it, and the producer that would is authMethodFromAuthProto (internal/component/l2tp/ppp/auth.go): it recognises PAP and CHAP alone and answers AuthMethodNone for every other Auth-Protocol value, 0xC227 included, so Ze's PPP falls back to the no-wire-auth phase rather than starting an EAP conversation. Ze carries EAP only inside the IKEv2 SK payload (startEAPExchange, internal/component/ike/engine/fsm.go). RFC 3748 obliges no implementation to support PPP as a lower layer: Section 2.2 lists PPP among the layers EAP 'has been run over', which is a description rather than a requirement. | If authentication of the link is desired, an implementation MUST specify the Authentication Protocol Configuration Option during the Link Establishment phase. | | `4.1:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates the same-Identifier rule for a retransmitted Request. Site 4.1:8 maps the id. | Retransmitted Requests MUST be sent with the same Identifier value in order to distinguish them from new Requests. | | `4.1:7` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates the same-Identifier rule for a Request retransmitted on a timeout. Site 4.1:8 maps the id. | The Identifier field MUST be the same if a Request packet is retransmitted due to a timeout while waiting for a Response. | | `4.1:11` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Repeats the Section 4 Length paragraph verbatim inside Section 4.1. Site 4:2 maps the id. | Octets outside the range of the Length field should be treated as Data Link Layer padding and MUST be ignored upon reception. | | `4.1:12` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Repeats the Section 4 Length paragraph verbatim inside Section 4.1. Site 4:3 maps the id. | A message with the Length field set to a value larger than the number of received octets MUST be silently discarded. | | `4.1:15` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates Section 2.1's ban on a Nak after an initial non-Nak Response. Site 2.1:3 maps the id. | A peer MUST NOT send a Nak (legacy or expanded) in response to a Request, after an initial non-Nak Response has been sent. | | `4.2:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The Failure arm of the same obligation, one sentence after the Success arm. Site 4.2:1 maps the id. | If the authenticator cannot authenticate the peer (unacceptable Responses to one or more Requests), then after unsuccessful completion of the EAP method in progress, the implementation MUST transmit an EAP packet with the Code field set to 4 (Failure). | | `4.2:15` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The MUST is the consequent of an unexercised MAY: 'However, an authenticator MAY omit having the peer authenticate to it in situations where limited access is offered (e.g., guest access). In this case, the authenticator MUST send a Success packet.' Ze's authenticator offers no guest access: every path to a Success runs through a completed method (Session.handleMethod, internal/core/eap/eap.go). | In this case, the authenticator MUST send a Success packet. | | `4.3:1` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The MUST is the consequent of a MAY inside a RECOMMENDED algorithm: '...the retransmission timer is calculated with a jitter by using the RTO value and randomly adding a value drawn between -RTOmin/2 and RTOmin/2. Alternative calculations to create jitter MAY be used. These MUST be pseudo-random.' Ze runs no EAP-layer retransmission timer at all, which Section 4.3 itself directs for a reliable lower layer. | These MUST be pseudo-random. | | `5.2:3` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The MUST is the consequent of a MAY, and the enclosing construction is: 'An EAP method MAY indicate within its specification that Notification messages must not be sent during that method. In this case, the peer MUST silently discard Notification Requests from the point where an initial Request for that Type is answered with a Response of the same Type.' The antecedent is false for every method Ze runs. Neither specification exercises that MAY: the string 'Notification' appears nowhere in rfc/full/rfc5216.txt and nowhere in rfc/full/rfc2759.txt, so no method Ze offers prohibits Notification messages and the peer is never in the state this sentence describes. What Ze owes a Notification Request OUTSIDE that state is Section 5.2's opening obligation, which sites 5.2:1 and 5.2:5 relocate to plan/spec-eap-notification-and-nak.md under RFC3748-5.2-1. | In this case, the peer MUST silently discard Notification Requests from the point where an initial Request for that Type is answered with a Response of the same Type. | | `5.2:4` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on SENDING a Notification Request, which RFC 3748 Section 5.2 states as an option in its own words: "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." The owner decided on 2026-09-01 (D-2 of plan/spec-eap-notification-and-nak.md) that ze's authenticator sends none, and Section 5.2 itself notes "In most circumstances, Notification should not be required." No producer composes a Type-2 Request: Session.Begin issues Identity and Session.handleMethod issues only the method's own packets (internal/core/eap/eap.go). The absent FEATURE is disclosed in rfc/short/rfc3748.md and a later scope decision can revisit it. Ze's peer ANSWERS a Notification Request, which is the mandatory half and is RFC3748-5.2-1. | The message MUST NOT be null terminated. | | `5.2:5` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates Section 5.2's opening obligation to answer a Notification Request with a Notification Response, which site 5.2:1 maps to RFC3748-5.2-1. | A Response MUST be sent in reply to the Request with a Type field of 2 (Notification). | | `5.3.2:1` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Expanded Type (254) namespace, which Ze does not offer. The sentence states when an Expanded Nak may be sent. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' TypeExpandedEAP is a bare constant with no producer and NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered (internal/core/eap/eap.go). Section 5.7 routes a peer that cannot interpret an Expanded Type to the LEGACY Nak of Section 5.3.1 instead, which is site 5.7:2, so declining Type 254 leaves Ze a conformant answer rather than none. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | It MUST be sent only in reply to a Request of Type 254 (Expanded Type) where the authentication Type is unacceptable. | | `5.3.2:2` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Expanded Type (254) namespace, which Ze does not offer. The sentence states the ban on an Expanded Nak as a general error indication. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' TypeExpandedEAP is a bare constant with no producer and NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered (internal/core/eap/eap.go). Section 5.7 routes a peer that cannot interpret an Expanded Type to the LEGACY Nak of Section 5.3.1 instead, which is site 5.7:2, so declining Type 254 leaves Ze a conformant answer rather than none. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | Since the Expanded Nak Type is valid only in Responses and has very limited functionality, it MUST NOT be used as a general purpose error indication, such as for communication of error messages, or negotiation of parameters specific to a particular EAP method. | | `5.3.2:3` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Expanded Type (254) namespace, which Ze does not offer. The sentence states the Expanded Nak Identifier rule. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' TypeExpandedEAP is a bare constant with no producer and NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered (internal/core/eap/eap.go). Section 5.7 routes a peer that cannot interpret an Expanded Type to the LEGACY Nak of Section 5.3.1 instead, which is site 5.7:2, so declining Type 254 leaves Ze a conformant answer rather than none. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | The Identifier field of an Expanded Nak Response MUST match the Identifier field of the Request packet that it is sent in response to. | | `5.3.2:4` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Expanded Type (254) namespace, which Ze does not offer. The sentence states the Expanded Nak Vendor-Data contents. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' TypeExpandedEAP is a bare constant with no producer and NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered (internal/core/eap/eap.go). Section 5.7 routes a peer that cannot interpret an Expanded Type to the LEGACY Nak of Section 5.3.1 instead, which is site 5.7:2, so declining Type 254 leaves Ze a conformant answer rather than none. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | The Vendor-Data field of the Nak Response MUST contain one or more authentication Types (4 or greater), all in expanded format, 8 octets per Type, or the value zero (0), also in Expanded Type format, to indicate no proposed alternative. | | `5.4:3` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on pass-through operation, which Ze does not offer. The sentence states what an authenticator that supports only pass-through must do with an MD5-Challenge Response. RFC 3748 Section 2 states the option in its own words: 'Network Access Server (NAS) devices (e.g., a switch or access point) do not have to understand each authentication method and MAY act as a pass-through agent for a backend authentication server. Support for pass-through is optional.' NewSession (internal/core/eap/eap.go) builds the method in process and the IKE engine has no AAA back end for EAP: nothing under internal/component/radius/ produces an EAP-Message attribute, and handleResponderEAP (internal/component/ike/engine/responder_eap.go) answers from the local eap.Session alone. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | An authenticator that supports only pass- through MUST allow communication with a backend authentication server that is capable of supporting MD5-Challenge, although the EAP authenticator implementation need not support MD5-Challenge itself. | | `5.4:4` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation is [RFC1994]'s, cited by this sentence: 'while [RFC1994] states that both the Identifier and Challenge fields MUST change each time a Challenge ... is sent'. | EAP allows for retransmission of MD5-Challenge Request packets, while [RFC1994] states that both the Identifier and Challenge fields MUST change each time a Challenge (the CHAP equivalent of the MD5-Challenge Request packet) is sent. | | `5.5:1` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the One Time Password method (Type 5), which Ze does not offer. The sentence states answering an OTP Request. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 5 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | A Response MUST be sent in reply to the Request. | | `5.5:2` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the One Time Password method (Type 5), which Ze does not offer. The sentence states the Type of the answering Response. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 5 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | The Response MUST be of Type 5 (OTP), Nak (Type 3), or Expanded Nak (Type 254). | | `5.5:3` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the One Time Password method (Type 5), which Ze does not offer. The sentence states the ban on cleartext passwords. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 5 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | The EAP OTP method is intended for use with the One-Time Password system only, and MUST NOT be used to provide support for cleartext passwords. | | `5.5:4` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the One Time Password method (Type 5), which Ze does not offer. The sentence states the message not being null terminated. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 5 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | The messages MUST NOT be null terminated. | | `5.6:1` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Generic Token Card method (Type 6), which Ze does not offer. The sentence states answering a GTC Request. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 6 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | A Response MUST be sent in reply to the Request. | | `5.6:2` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Generic Token Card method (Type 6), which Ze does not offer. The sentence states the Type of the answering Response. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 6 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | The Response MUST be of Type 6 (GTC), Nak (Type 3), or Expanded Nak (Type 254). | | `5.6:3` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Generic Token Card method (Type 6), which Ze does not offer. The sentence states the ban on cleartext passwords outside a protected tunnel. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 6 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | The EAP GTC method is intended for use with the Token Cards supporting challenge/response authentication and MUST NOT be used to provide support for cleartext passwords in the absence of a protected tunnel with server authentication. | | `5.6:4` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Generic Token Card method (Type 6), which Ze does not offer. The sentence states the message not being null terminated. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 6 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | The message MUST NOT be null terminated. | | `5.6:5` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on the Generic Token Card method (Type 6), which Ze does not offer. The sentence states the Type field of the answering Response. RFC 3748 Section 5 states the option in its own words: 'All EAP implementations MUST support Types 1-4, which are defined in this document, and SHOULD support Type 254. Implementations MAY support other Types defined here or in future RFCs.' NewSession (internal/core/eap/eap.go) builds a method for Type 4, Type 13 and Type 26 and refuses every other type, so no other Type is offered, and Type 6 derives no MSK, so RFC 7296 Section 2.16 would key its AUTH payloads from SK_pi and SK_pr; RFC3748-7.10-3 states that as a SHOULD NOT rather than a prohibition. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | A Response MUST be sent in reply to the Request with a Type field of 6 (Generic Token Card). | | `5.7:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The Nak this sentence demands IS the legacy Nak of Section 5.3.1, which it cites by number, so the obligation is the one site 5.3.1:3 maps to RFC3748-5.3.1-1. PeerSession.naks routes Type 254 to that same builder rather than composing an Expanded Nak (internal/core/eap/peer.go). | Peers not equipped to interpret the Expanded Type MUST send a Nak as described in Section 5.3.1, and negotiate a more suitable authentication method. | | `7.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the Security Claims section a method specification must include. | In order to clearly articulate the security provided by an EAP method, EAP method specifications MUST include a Security Claims section, including the following declarations: | | `7.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the effective key strength estimate. | If the method derives keys, then the effective key strength MUST be estimated. | | `7.2:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the key hierarchy reference or MSK/EMSK derivation description. | EAP methods deriving keys MUST either provide a reference to a key hierarchy specification, or describe how Master Session Keys (MSKs) and Extended Master Session Keys (EMSKs) are to be derived. | | `7.2:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the claims that are NOT being made. | In addition to the security claims that are made, the specification MUST indicate which of the security claims detailed in Section 7.2.1 are NOT being made. | | `7.2.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. Section 7.2.1 is the claims VOCABULARY a specification writes its Security Claims section in, and this sentence states describing the EAP packets and fields protected. | When making this claim, a method specification MUST describe the EAP packets and fields within the EAP packet that are protected. | | `7.2.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. Section 7.2.1 is the claims VOCABULARY a specification writes its Security Claims section in, and this sentence states the identity protection a claiming method must support. | A method making this claim MUST support identity protection (see Section 7.3). | | `7.10:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states that keying material a method exports must be independent of the ciphersuite negotiated to protect data, which is a property of the DERIVATION a specification defines. The two Ze runs are defined elsewhere: RFC 5216 Section 2.3 for EAP-TLS (exportEAPTLSMSK, internal/core/eap/eap_tls.go) and draft-kamath-pppext-eap-mschapv2-02 for EAP-MSCHAPv2 (DeriveMSK, internal/core/eap/mschapv2.go). | Keying material exported by EAP methods MUST be independent of the ciphersuite negotiated to protect data. | | `7.10:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states cryptographic separation between the MSK and EMSK branches, which is a property a method's key hierarchy must be SHOWN to have. The showing is the specification's, and Ze holds the implementations of the two it runs. | Methods supporting key derivation MUST demonstrate cryptographic separation between the MSK and EMSK branches of the EAP key hierarchy. | | `7.10:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the non-recoverability of one key from the other, which is a property a method's key hierarchy must be SHOWN to have. The showing is the specification's, and Ze holds the implementations of the two it runs. | Without violating a fundamental cryptographic assumption (such as the non-invertibility of a one-way function), 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. | | `7.10:7` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the separation of non-overlapping MSK substrings, which is a property a method's key hierarchy must be SHOWN to have. The showing is the specification's, and Ze holds the implementations of the two it runs. | Non-overlapping substrings of the MSK MUST be cryptographically separate from each other, as defined in Section 7.2.1. | | `7.10:8` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the same property stated as a knowledge bound, which is a property a method's key hierarchy must be SHOWN to have. The showing is the specification's, and Ze holds the implementations of the two it runs. | That is, knowledge of one substring MUST NOT help in recovering some other substring without breaking some hard cryptographic assumption. | | `7.10:9` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the separation of non-overlapping EMSK substrings, which is a property a method's key hierarchy must be SHOWN to have. The showing is the specification's, and Ze holds the implementations of the two it runs. | Likewise, non-overlapping substrings of the EMSK MUST be cryptographically separate from each other, and from substrings of the MSK. | | `7.13:1` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | Conditional on pass-through operation, which Ze does not offer. The sentence states that the AAA protocol spoken between an authenticator and a backend authentication server must support per-packet authentication. Section 7.13 states its own antecedent, 'in the case where the authenticator and authentication server reside on different machines', which is pass-through. RFC 3748 Section 2 states the option in its own words: 'Network Access Server (NAS) devices (e.g., a switch or access point) do not have to understand each authentication method and MAY act as a pass-through agent for a backend authentication server. Support for pass-through is optional.' NewSession (internal/core/eap/eap.go) builds the method in process and the IKE engine has no AAA back end for EAP: nothing under internal/component/radius/ produces an EAP-Message attribute, and handleResponderEAP (internal/component/ike/engine/responder_eap.go) answers from the local eap.Session alone. The absent feature is disclosed in docs/features/rfc-status.md as an implementation gap a later scope decision can revisit, never as a conformance gap. | In practice, this implies that the AAA protocol spoken between the authenticator and authentication server MUST support per-packet authentication, integrity, and replay protection. | | `7.16:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states which result indications are protected, which a specification claiming protected result indications must document. | A method supporting protected result indications MUST indicate which result indications are protected, and which are not. | | `7.16:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an EAP METHOD SPECIFICATION, which is a document role rather than a wire role: Section 7.2 addresses it as 'EAP method specifications MUST include a Security Claims section'. Ze publishes no EAP method specification. The producer that would act as the role if Ze did is the specification itself, and for the two methods Ze runs those documents are RFC 5216 (EAP-TLS) and draft-kamath-pppext-eap-mschapv2-02 (EAP-MSCHAPv2); newTLSMethod and newMSCHAPv2Method (internal/core/eap/eap.go) implement what those two state, and state nothing themselves. This sentence states the four claims a protected-result-indication method must also support, which a specification claiming protected result indications must document. | Since protected result indications require use of a key for per-packet authentication and integrity protection, methods supporting protected result indications MUST also support the "key derivation", "mutual authentication", "integrity protection", and "replay protection" claims. | ## Superseded No document obsoletes RFC 3748, so its obligations are stated where they were written. --- ### Page: RFC 3765 - NOPEER Community for Border Gateway Protocol (BGP) Route Scope Control https://ze-software.net/quality/rfc-compliance/rfc3765/ # RFC 3765 - NOPEER Community for Border Gateway Protocol (BGP) Route Scope Control Supported. Every requirement this repository extracted from RFC 3765, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 0 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 0 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 0 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 0 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | | Audit verdicts | 0 | of 0 gated MUSTs judged | 0 weak, wrong or unimplemented, 0 no longer current. Each is named below under its own requirement id | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 0 | of 2 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 0 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 0 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 0 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 0 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | No card above is a share of a population, so there is nothing to add up. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | ok | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 2 | | Gated MUST-level | 0 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3765.md` | | Requirement shard | `rfc/requirements/rfc3765.md` | | RFC text | `rfc/full/rfc3765.txt` | ## Enrolment Enrolled: NOPEER Community for BGP: an Informational document with ZERO gated obligations, and that absence is a property of the text rather than a gap in the summary (owner ruling OR-A, plan/spec-rfcgate-4-ledger.md). It carries no RFC 2119 key-words section and no occurrence of any of the ten keywords in any case; it calls its own mechanism "an advisory qualification to readvertisement of a route prefix" verbatim in section 2 and section 4, phrases the behaviour permissively both times, and defines no wire format -- NOPEER is a value carried in RFC 1997's COMMUNITIES attribute. Its two rows are therefore [MAY]. Enrolled on the evidence of a manual-walk extraction sign-off (rfc/extraction/rfc3765.json) rather than on a fabricated MUST: a requirement the RFC does not contain would put a false claim inside the ledger this gate exists to make honest. ## What the public ledger says **Status:** Supported **What the ledger says is covered** 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 [`rfc/short/rfc3765.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc3765.md) are therefore `[MAY]`, and the document gates nothing. The section-by-section walk behind that claim is recorded in [`rfc/extraction/rfc3765.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/rfc3765.json). **What the ledger says remains:** No tracked gap in current source anchors. The route-server path forwards to every client and applies no NOPEER filter. RFC 7947 permits that, because a route server is not a bilateral peer. ## Coverage RFC 3765 declares no MUST-level requirement, so the gate counts nothing here. ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3765-2-1` | The semantics of this attribute is to allow an AS to interpret the presence of this community as an advisory qualification to readvertisement of a route prefix, permitting an AS not to readvertise the route prefix to all external bilateral peer neighbour AS's (§2, §4) | MAY | 2 - NOPEER Attribute, the only definitional section | **positive:** no positive test. **negative:** no negative test | | `RFC3765-2-2` | It is consistent with these semantics that an AS may filter received prefixes that are received across a peering session that the receiver regards as a bilateral peer sessions (§2, §4) | MAY | 2 - NOPEER Attribute, the only definitional section | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 3765 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state RFC 3765 carries no gated, tagged or audited requirement, so there is no proof state to state. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement agent, spec-rfcgate-4-ledger phase 6 (OR-A) | | Signed off | 2026-07-30 | | Register | manual-walk | | Source | rfc/full/rfc3765.txt | | Source fingerprint | 288010a8fa4f2407 | | Record | rfc/extraction/rfc3765.json | | Mapped sentences | 0 | | Declined as scope | 1 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, copyright notice and Abstract. The Abstract restates section 1 and states no obligation. | | `1` | Introduction | 0 | walked | Introduction. Describes the problem (more-specific routes redistributed beyond their useful scope) and names the mechanism. No sentence directs a speaker. | | `2` | NOPEER Attribute, the only definitional section | 0 | walked | NOPEER Attribute, the only definitional section. Two behavioural sentences, both permissive: an AS is 'permitted' not to readvertise, and 'may filter' received prefixes. Captured as RFC3765-2-1 and RFC3765-2-2 at [MAY]. | | `3` | Motivation | 0 | walked | Motivation. Routing-table growth statistics and the reasoning that leads to a classification-based rather than enumeration-based redistribution qualifier. Rationale only. | | `4` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Registers NOPEER as 0xFFFFFF04 with global significance and repeats section 2's advisory sentence verbatim. The registration binds IANA, and the repeated sentence is already captured under section 2. | | `5` | Security Considerations | 0 | walked | Security Considerations. Analyses the attack surface an unauthorised NOPEER addition opens. States no countermeasure a speaker must apply. | | `6` | References heading | 0 | skipped (references) | References heading. | | `6.1` | Normative References: RFC 1997 only | 0 | skipped (references) | Normative References: RFC 1997 only. | | `6.2` | Informative References: RFC 3221 only | 0 | skipped (references) | Informative References: RFC 3221 only. | | `7` | Author's Address | 0 | skipped (front-matter) | Author's Address. Document furniture. | | `8` | Full Copyright Statement | 1 | walked | Full Copyright Statement. Walked because the prose scan attributes its one site here; the site is boilerplate and is excluded below. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `8:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The standard IETF copyright boilerplate. 'may be required to implement this standard' is the only lowercase 'required' in the document, and it addresses parties holding patents, not a BGP speaker. | The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. | ## Superseded No document obsoletes RFC 3765, so its obligations are stated where they were written. --- ### Page: RFC 3768 - Virtual Router Redundancy Protocol (VRRP) https://ze-software.net/quality/rfc-compliance/rfc3768/ # RFC 3768 - Virtual Router Redundancy Protocol (VRRP) Experimental. Every requirement this repository extracted from RFC 3768, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 79.5% | 31 of 39 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 12.8% | 5 of 39 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 39 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 39 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 2.9% | 2 of 69 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 39 | of 50 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 3 | of 39 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 7.7% | 3 of 39 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 39 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 39 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 39 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 50 | | Gated MUST-level | 39 | | Not applicable, so out of scope | 3 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 69 | | Tagged units | 69 | | Recorded audit verdicts | 0 | | Discrimination records | 2 | | Summary | `rfc/short/rfc3768.md` | | Requirement shard | `rfc/requirements/rfc3768.md` | | RFC text | `rfc/full/rfc3768.txt` | ## Enrolment Enrolled: Virtual Router Redundancy Protocol v2 / VRRP (RFC 3768): 30 MET (advertisement format, priority/master election, skew time, virtual MAC, gratuitous ARP, adoption) + 5 single-polarity positive + 1 gap (accept-mode) + 3 not-applicable (deprecated authentication) ## What the public ledger says **Status:** Experimental **What the ledger says is covered** 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). **What the ledger says remains:** No gap gated in [`rfc/short/rfc3768.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc3768.md). RFC 3768 authentication types are deliberately not implemented: RFC 9568 Section 9 removed them as providing no real security. Same VRRP experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 31 | one part of the gated population | | Annotated instead of tested | 8 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **39** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (31):** [`RFC3768-5.2.3-2`](#rfc3768-5.2.3-2), [`RFC3768-5.3.2-1`](#rfc3768-5.3.2-1), [`RFC3768-5.3.4-1`](#rfc3768-5.3.4-1), [`RFC3768-5.3.4-2`](#rfc3768-5.3.4-2), [`RFC3768-5.3.6-1`](#rfc3768-5.3.6-1), [`RFC3768-6.4.2-1`](#rfc3768-6.4.2-1), [`RFC3768-6.4.2-2`](#rfc3768-6.4.2-2), [`RFC3768-6.4.2-3`](#rfc3768-6.4.2-3), [`RFC3768-6.4.2-4`](#rfc3768-6.4.2-4), [`RFC3768-6.4.2-5`](#rfc3768-6.4.2-5), [`RFC3768-6.4.2-6`](#rfc3768-6.4.2-6), [`RFC3768-6.4.2-7`](#rfc3768-6.4.2-7), [`RFC3768-6.4.2-8`](#rfc3768-6.4.2-8), [`RFC3768-6.4.3-1`](#rfc3768-6.4.3-1), [`RFC3768-6.4.3-2`](#rfc3768-6.4.3-2), [`RFC3768-6.4.3-3`](#rfc3768-6.4.3-3), [`RFC3768-6.4.3-4`](#rfc3768-6.4.3-4), [`RFC3768-6.4.3-5`](#rfc3768-6.4.3-5), [`RFC3768-6.4.3-6`](#rfc3768-6.4.3-6), [`RFC3768-6.4.3-7`](#rfc3768-6.4.3-7), [`RFC3768-6.4.3-8`](#rfc3768-6.4.3-8), [`RFC3768-6.4.3-9`](#rfc3768-6.4.3-9), [`RFC3768-7.1-1`](#rfc3768-7.1-1), [`RFC3768-7.1-2`](#rfc3768-7.1-2), [`RFC3768-7.1-3`](#rfc3768-7.1-3), [`RFC3768-7.1-4`](#rfc3768-7.1-4), [`RFC3768-7.1-5`](#rfc3768-7.1-5), [`RFC3768-7.1-6`](#rfc3768-7.1-6), [`RFC3768-7.1-7`](#rfc3768-7.1-7), [`RFC3768-7.1-8`](#rfc3768-7.1-8), [`RFC3768-8.2-1`](#rfc3768-8.2-1) **Annotated instead of tested (8):** [`RFC3768-5.2.2-1`](#rfc3768-5.2.2-1), [`RFC3768-5.2.3-1`](#rfc3768-5.2.3-1), [`RFC3768-7.2-1`](#rfc3768-7.2-1), [`RFC3768-7.2-2`](#rfc3768-7.2-2), [`RFC3768-7.2-3`](#rfc3768-7.2-3), [`RFC3768-7.2-4`](#rfc3768-7.2-4), [`RFC3768-8.3-1`](#rfc3768-8.3-1), [`RFC3768-9.2-1`](#rfc3768-9.2-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3768-5.2.2-1` | Never forward a datagram destined to 224.0.0.18, regardless of its TTL (§5.2.2) | MUST NOT | 5.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the VRRP plugin performs no IP datagram forwarding -- instance.onPacket internal/plugins/vrrp/instance.go:453 consumes each received advert into the FSM and never re-emits it, and tx scopes adverts to link-local multicast with IP_MULTICAST_LOOP 0 at internal/plugins/vrrp/transport/backend_linux.go:143 | | `RFC3768-5.2.3-1` | Set the IP TTL of transmitted VRRP packets to 255 (§5.2.3) | MUST | 5.2.3 | **positive:** `unit/verify` [`TestSendAdvertIPv4HeaderTTLProtoDst`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L260). **negative:** no negative test. **{single-polarity}:** buildIPv4Header unconditionally sets TTL 255 at internal/plugins/vrrp/transport/transport.go:562, so no input yields a different TTL -- the rx TTL!=255 discard is the separate RFC3768-5.2.3-2 | | `RFC3768-5.2.3-2` | Discard received VRRP packets whose TTL is not equal to 255 (§5.2.3, §7.1) | MUST | 5.2.3 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L48). **negative:** `unit/verify` [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L533) | | `RFC3768-5.3.2-1` | Discard packets with unknown Type; only 1 = ADVERTISEMENT is defined (§5.3.2) | MUST | 5.3.2 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L49). **negative:** `unit/verify` [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L150) | | `RFC3768-5.3.4-1` | Use Priority 255 for the VRRP router that owns the virtual router's IP address(es) (§5.3.4) | MUST | 5.3.4 | **positive:** `unit/verify` [`TestOwnerAutoDetection`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L771). **negative:** `unit/verify` [`TestOwnerAutoDetection`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L772) | | `RFC3768-5.3.4-2` | Use Priority values 1-254 for VRRP routers backing up a virtual router (§5.3.4) | MUST | 5.3.4 | **positive:** `unit/verify` [`TestBoundaryPriority`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L316). **negative:** `unit/verify` [`TestBoundaryPriority`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L317) | | `RFC3768-5.3.6-1` | Discard packets with unknown Auth Type or an Auth Type that does not match the locally configured authentication method (§5.3.6, §7.1) | MUST | 5.3.6 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L50). **negative:** `unit/verify` [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L598) | | `RFC3768-6.4.2-1` | Backup: never respond to ARP requests for the IP address(es) associated with the virtual router (§6.4.2) | MUST NOT | 6.4.2 | **positive:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L318). **negative:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L362) | | `RFC3768-6.4.2-2` | Backup: discard packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L319). **negative:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L363) | | `RFC3768-6.4.2-3` | Backup: never accept packets addressed to the IP address(es) associated with the virtual router (§6.4.2) | MUST NOT | 6.4.2 | **positive:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L320). **negative:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L364) | | `RFC3768-6.4.2-4` | Backup: on Shutdown, cancel the Master_Down_Timer and transition to Initialize (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L88). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L89) | | `RFC3768-6.4.2-5` | Backup: when the Master_Down_Timer fires, send an ADVERTISEMENT, broadcast a gratuitous ARP with the virtual router MAC for each virtual IP address, set the Adver_Timer to Advertisement_Interval, and transition to Master (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMMasterDownPromotion`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L446). **negative:** `unit/verify` [`TestFSMStaleTimerGenerationIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L636) | | `RFC3768-6.4.2-6` | Backup: on an ADVERTISEMENT with Priority 0, set the Master_Down_Timer to Skew_Time (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L90). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L91) | | `RFC3768-6.4.2-7` | Backup: on a non-zero-priority ADVERTISEMENT, if Preempt_Mode is False or the advertised Priority >= local Priority, reset the Master_Down_Timer to Master_Down_Interval (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L92). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L93) | | `RFC3768-6.4.2-8` | Backup: on a non-zero-priority ADVERTISEMENT with Preempt_Mode True and advertised Priority < local Priority, discard the ADVERTISEMENT (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L94). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L95) | | `RFC3768-6.4.3-1` | Master: respond to ARP requests for the IP address(es) associated with the virtual router, answering with the virtual MAC address (§6.4.3, §8.2) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestDataplaneApplyIPv4SetsRecipe`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L61). **negative:** `unit/verify` [`TestDataplaneRestoreOnLastGroup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L122) | | `RFC3768-6.4.3-2` | Master: forward packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L360). **negative:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L321) | | `RFC3768-6.4.3-3` | Master: never accept packets addressed to the virtual router IP address(es) when not the IP address owner (§6.4.3) | MUST NOT | 6.4.3 | **positive:** `unit/verify` [`TestActiveV2RouterAcceptsOnlyWhenItOwnsTheAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L375). **negative:** `unit/verify` [`TestActiveV2RouterAcceptsOnlyWhenItOwnsTheAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L376) | | `RFC3768-6.4.3-4` | Master: accept packets addressed to the virtual router IP address(es) when the IP address owner (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L361). **negative:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L322) | | `RFC3768-6.4.3-5` | Master: on Shutdown, cancel the Adver_Timer, send an ADVERTISEMENT with Priority = 0, and transition to Initialize (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L96). **positive:** `unit/verify` [`TestInstanceShutdownAsMasterSendsPriorityZero`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L439). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L97) | | `RFC3768-6.4.3-6` | Master: when the Adver_Timer fires, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L98). **negative:** `unit/verify` [`TestFSMStaleTimerGenerationIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L637) | | `RFC3768-6.4.3-7` | Master: on an ADVERTISEMENT with Priority 0, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L99). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L100) | | `RFC3768-6.4.3-8` | Master: on an ADVERTISEMENT with higher Priority, or equal Priority and greater sender primary IP address, cancel the Adver_Timer, set the Master_Down_Timer to Master_Down_Interval, and transition to Backup (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L101). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L102) | | `RFC3768-6.4.3-9` | Master: on a losing ADVERTISEMENT (lower Priority, or equal Priority with smaller sender address), discard it (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L103). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L104) | | `RFC3768-7.1-1` | Rx: verify the VRRP version is 2 (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L42). **negative:** `unit/verify` [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L609) | | `RFC3768-7.1-2` | Rx: verify the received packet contains the complete VRRP packet, including fixed fields, IP address(es), and Authentication Data (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L43). **negative:** `unit/verify` [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L218) | | `RFC3768-7.1-3` | Rx: verify the VRRP checksum (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L44). **negative:** `unit/verify` [`TestDecodeV2ChecksumCorrupt`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L306) | | `RFC3768-7.1-4` | Rx: verify the VRID is configured on the receiving interface and the local router is not the IP address owner (Priority = 255) (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L45). **negative:** `unit/verify` [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L163) | | `RFC3768-7.1-5` | Rx: verify the Auth Type matches the locally configured authentication method and perform that method (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L46). **negative:** `unit/verify` [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L599) | | `RFC3768-7.1-6` | Rx: discard the packet if any mandatory receive check fails (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestInstanceRxValidAdvertReachesFSM`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L483). **negative:** `unit/verify` [`TestInstanceRxDecodeErrorMapsReason`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L463) | | `RFC3768-7.1-7` | Rx: if the optional address-list check fails and the sender is not the address owner (Priority != 255), drop the packet (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestInstanceV2AddressListMatchReachesFSM`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L565). **negative:** `unit/verify` [`TestInstanceV2AddressListMismatchDrops`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L526) | | `RFC3768-7.1-8` | Rx: verify the Adver Interval in the packet equals the locally configured value; discard the packet on mismatch (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L47). **negative:** `unit/verify` [`TestDecodeV2IntervalMismatchDiscard`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L293). **negative:** `unit/verify` [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L251) | | `RFC3768-7.2-1` | Tx: fill in the VRRP fields from the virtual router configuration state and compute the VRRP checksum (§7.2) | MUST | 7.2 | **positive:** `unit/verify` [`TestEncodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L87). **negative:** no negative test. **{single-polarity}:** a golden encode pins every field and the checksum -- WriteTo internal/plugins/vrrp/packet/packet.go:251 plus FillChecksum internal/plugins/vrrp/packet/checksum.go:86 -- while a corrupted-encoding rejection is the separate receive requirement RFC3768-7.1-3 | | `RFC3768-7.2-2` | Tx: set the source MAC address to the virtual router MAC address (§7.2) | MUST | 7.2 | **positive:** `unit/verify` [`TestConstants`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L385). **negative:** no negative test. **{single-polarity}:** the source MAC is the virtual-router MAC from packet.VirtualMAC internal/plugins/vrrp/packet/packet.go:97 egressed by binding the tx socket to the vMAC macvlan internal/plugins/vrrp/transport/backend_linux.go:133, a deterministic derivation with no input that yields a different MAC | | `RFC3768-7.2-3` | Tx: set the source IP address to the interface primary IP address (§7.2) | MUST | 7.2 | **positive:** `unit/verify` [`TestSendAdvertUsesParentPrimaryV4Source`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L202). **negative:** no negative test. **{single-polarity}:** the source IP is the parent unit primary IPv4 from resolveParentPrimaryV4 internal/plugins/vrrp/transport/transport.go:573, a deterministic selection re-resolved on address change, with no input that yields a wrong-source advert | | `RFC3768-7.2-4` | Tx: set the IP protocol to VRRP (112) and send to the VRRP IP multicast group 224.0.0.18 (§7.2) | MUST | 7.2 | **positive:** `unit/verify` [`TestSendAdvertIPv4HeaderTTLProtoDst`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L261). **negative:** no negative test. **{single-polarity}:** buildIPv4Header sets IP protocol 112 at internal/plugins/vrrp/transport/transport.go:563 and SendAdvert targets 224.0.0.18 at internal/plugins/vrrp/transport/backend_linux.go:256, both constants with no input that changes them | | `RFC3768-8.2-1` | Master: never respond to host ARP requests for virtual addresses with the physical MAC address (§8.2) | MUST NOT | 8.2 | **positive:** `unit/verify` [`TestDataplaneApplyIPv4SetsRecipe`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L62). **negative:** `unit/verify` [`TestDataplaneRestoreOnLastGroup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L123) | | `RFC3768-8.3-1` | Advertise the virtual router MAC address in Proxy ARP messages sent on behalf of VRRP-protected addresses (§8.3; lowercase "must" in the RFC) | MUST | 8.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze performs no proxy ARP for virtual addresses -- the per-group virtual-MAC macvlan answers ARP for the VIP directly (createMacvlan internal/plugins/vrrp/register.go:329 plus the sole-responder sysctl recipe internal/plugins/vrrp/dataplane_linux.go:64), and no proxy-ARP path exists | | `RFC3768-9.2-1` | Token ring: implement the functional-address mode of operation when supporting VRRP on token ring (§9.2) | MUST | 9.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze supports only Ethernet-family parents (ethernet, veth, bridge, dummy -- internal/plugins/vrrp/groups.go:76) over AF_PACKET macvlan transport internal/plugins/vrrp/transport/backend_linux.go, with no token-ring transport, so the functional-address mode has no applicable code path | | `RFC3768-5.3.10-1` | Set Authentication Data to zero on transmission and ignore it on reception (§5.3.10) | SHOULD | 5.3.10 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-7.1-9` | Log the event when a mandatory receive check fails (§7.1) | SHOULD | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-7.1-10` | Log the event on an address-list mismatch (§7.1) | SHOULD | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-7.1-11` | Log the event on an Adver Interval mismatch (§7.1) | SHOULD | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-8.2-2` | After restart/boot, never send ARP messages with the physical MAC address for owned virtual IP addresses (§8.2) | SHOULD NOT | 8.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-8.2-3` | Broadcast a gratuitous ARP with the virtual router MAC for each IP address when configuring an interface, and delay ARP at boot until both the IP address and virtual MAC are configured (§8.2) | SHOULD | 8.2 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-8.4-1` | Never forward packets addressed to IP addresses adopted as Master when not the owner (§8.4) | SHOULD NOT | 8.4 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-9.1-1` | FDDI: configure the virtual router MAC via a unicast MAC filter rather than changing the hardware MAC address (§9.1) | SHOULD | 9.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-7.1-12` | Indicate via network management that a receive error or misconfiguration occurred (§7.1) | MAY | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-7.1-13` | Verify that Count IP Addrs and the address list match the IP_Addresses configured for the VRID (§7.1) | MAY | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC3768-9.2-2` | Token ring: support the unicast mode of operation in addition to functional addresses (§9.2) | MAY | 9.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3768-5.2.2-1`](#rfc3768-5.2.2-1) Never forward a datagram destined to 224.0.0.18, regardless of its TTL (§5.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: the VRRP plugin performs no IP datagram forwarding -- instance.onPacket internal/plugins/vrrp/instance.go:453 consumes each received advert into the FSM and never re-emits it, and tx scopes adverts to link-local multicast with IP_MULTICAST_LOOP 0 at internal/plugins/vrrp/transport/backend_linux.go:143 | | [`RFC3768-8.3-1`](#rfc3768-8.3-1) Advertise the virtual router MAC address in Proxy ARP messages sent on behalf of VRRP-protected addresses (§8.3; lowercase "must" in the RFC) | no test | no test carries this requirement id; annotated {not-applicable}: ze performs no proxy ARP for virtual addresses -- the per-group virtual-MAC macvlan answers ARP for the VIP directly (createMacvlan internal/plugins/vrrp/register.go:329 plus the sole-responder sysctl recipe internal/plugins/vrrp/dataplane_linux.go:64), and no proxy-ARP path exists | | [`RFC3768-9.2-1`](#rfc3768-9.2-1) Token ring: implement the functional-address mode of operation when supporting VRRP on token ring (§9.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze supports only Ethernet-family parents (ethernet, veth, bridge, dummy -- internal/plugins/vrrp/groups.go:76) over AF_PACKET macvlan transport internal/plugins/vrrp/transport/backend_linux.go, with no token-ring transport, so the functional-address mode has no applicable code path | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3768-5.2.2-1`](#rfc3768-5.2.2-1) Never forward a datagram destined to 224.0.0.18, regardless of its TTL (§5.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3768-5.2.2-1, so no unit is bound to it. ### [`RFC3768-5.2.3-1`](#rfc3768-5.2.3-1) Set the IP TTL of transmitted VRRP packets to 255 (§5.2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSendAdvertIPv4HeaderTTLProtoDst`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L260) | unit/verify | unproven | ### [`RFC3768-5.2.3-2`](#rfc3768-5.2.3-2) Discard received VRRP packets whose TTL is not equal to 255 (§5.2.3, §7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L533) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L48) | unit/verify | unproven | ### [`RFC3768-5.3.2-1`](#rfc3768-5.3.2-1) Discard packets with unknown Type; only 1 = ADVERTISEMENT is defined (§5.3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L150) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L49) | unit/verify | unproven | ### [`RFC3768-5.3.4-1`](#rfc3768-5.3.4-1) Use Priority 255 for the VRRP router that owns the virtual router's IP address(es) (§5.3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOwnerAutoDetection`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L772) | unit/verify | revert, verified | | positive | [`TestOwnerAutoDetection`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L771) | unit/verify | revert, verified | ### [`RFC3768-5.3.4-2`](#rfc3768-5.3.4-2) Use Priority values 1-254 for VRRP routers backing up a virtual router (§5.3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBoundaryPriority`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L317) | unit/verify | unproven | | positive | [`TestBoundaryPriority`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L316) | unit/verify | unproven | ### [`RFC3768-5.3.6-1`](#rfc3768-5.3.6-1) Discard packets with unknown Auth Type or an Auth Type that does not match the locally configured authentication method (§5.3.6, §7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L598) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L50) | unit/verify | unproven | ### [`RFC3768-6.4.2-1`](#rfc3768-6.4.2-1) Backup: never respond to ARP requests for the IP address(es) associated with the virtual router (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L362) | unit/verify | unproven | | positive | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L318) | unit/verify | unproven | ### [`RFC3768-6.4.2-2`](#rfc3768-6.4.2-2) Backup: discard packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L363) | unit/verify | unproven | | positive | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L319) | unit/verify | unproven | ### [`RFC3768-6.4.2-3`](#rfc3768-6.4.2-3) Backup: never accept packets addressed to the IP address(es) associated with the virtual router (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L364) | unit/verify | unproven | | positive | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L320) | unit/verify | unproven | ### [`RFC3768-6.4.2-4`](#rfc3768-6.4.2-4) Backup: on Shutdown, cancel the Master_Down_Timer and transition to Initialize (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L89) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L88) | unit/verify | unproven | ### [`RFC3768-6.4.2-5`](#rfc3768-6.4.2-5) Backup: when the Master_Down_Timer fires, send an ADVERTISEMENT, broadcast a gratuitous ARP with the virtual router MAC for each virtual IP address, set the Adver_Timer to Advertisement_Interval, and transition to Master (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMStaleTimerGenerationIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L636) | unit/verify | unproven | | positive | [`TestFSMMasterDownPromotion`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L446) | unit/verify | unproven | ### [`RFC3768-6.4.2-6`](#rfc3768-6.4.2-6) Backup: on an ADVERTISEMENT with Priority 0, set the Master_Down_Timer to Skew_Time (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L91) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L90) | unit/verify | unproven | ### [`RFC3768-6.4.2-7`](#rfc3768-6.4.2-7) Backup: on a non-zero-priority ADVERTISEMENT, if Preempt_Mode is False or the advertised Priority >= local Priority, reset the Master_Down_Timer to Master_Down_Interval (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L93) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L92) | unit/verify | unproven | ### [`RFC3768-6.4.2-8`](#rfc3768-6.4.2-8) Backup: on a non-zero-priority ADVERTISEMENT with Preempt_Mode True and advertised Priority < local Priority, discard the ADVERTISEMENT (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L95) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L94) | unit/verify | unproven | ### [`RFC3768-6.4.3-1`](#rfc3768-6.4.3-1) Master: respond to ARP requests for the IP address(es) associated with the virtual router, answering with the virtual MAC address (§6.4.3, §8.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDataplaneRestoreOnLastGroup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L122) | unit/verify | unproven | | positive | [`TestDataplaneApplyIPv4SetsRecipe`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L61) | unit/verify | unproven | ### [`RFC3768-6.4.3-2`](#rfc3768-6.4.3-2) Master: forward packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L321) | unit/verify | unproven | | positive | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L360) | unit/verify | unproven | ### [`RFC3768-6.4.3-3`](#rfc3768-6.4.3-3) Master: never accept packets addressed to the virtual router IP address(es) when not the IP address owner (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestActiveV2RouterAcceptsOnlyWhenItOwnsTheAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L376) | unit/verify | unproven | | positive | [`TestActiveV2RouterAcceptsOnlyWhenItOwnsTheAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L375) | unit/verify | unproven | ### [`RFC3768-6.4.3-4`](#rfc3768-6.4.3-4) Master: accept packets addressed to the virtual router IP address(es) when the IP address owner (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L322) | unit/verify | unproven | | positive | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L361) | unit/verify | unproven | ### [`RFC3768-6.4.3-5`](#rfc3768-6.4.3-5) Master: on Shutdown, cancel the Adver_Timer, send an ADVERTISEMENT with Priority = 0, and transition to Initialize (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L97) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L96) | unit/verify | unproven | | positive | [`TestInstanceShutdownAsMasterSendsPriorityZero`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L439) | unit/verify | unproven | ### [`RFC3768-6.4.3-6`](#rfc3768-6.4.3-6) Master: when the Adver_Timer fires, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMStaleTimerGenerationIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L637) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L98) | unit/verify | unproven | ### [`RFC3768-6.4.3-7`](#rfc3768-6.4.3-7) Master: on an ADVERTISEMENT with Priority 0, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L100) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L99) | unit/verify | unproven | ### [`RFC3768-6.4.3-8`](#rfc3768-6.4.3-8) Master: on an ADVERTISEMENT with higher Priority, or equal Priority and greater sender primary IP address, cancel the Adver_Timer, set the Master_Down_Timer to Master_Down_Interval, and transition to Backup (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L102) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L101) | unit/verify | unproven | ### [`RFC3768-6.4.3-9`](#rfc3768-6.4.3-9) Master: on a losing ADVERTISEMENT (lower Priority, or equal Priority with smaller sender address), discard it (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L104) | unit/verify | unproven | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L103) | unit/verify | unproven | ### [`RFC3768-7.1-1`](#rfc3768-7.1-1) Rx: verify the VRRP version is 2 (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L609) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L42) | unit/verify | unproven | ### [`RFC3768-7.1-2`](#rfc3768-7.1-2) Rx: verify the received packet contains the complete VRRP packet, including fixed fields, IP address(es), and Authentication Data (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L218) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L43) | unit/verify | unproven | ### [`RFC3768-7.1-3`](#rfc3768-7.1-3) Rx: verify the VRRP checksum (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDecodeV2ChecksumCorrupt`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L306) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L44) | unit/verify | unproven | ### [`RFC3768-7.1-4`](#rfc3768-7.1-4) Rx: verify the VRID is configured on the receiving interface and the local router is not the IP address owner (Priority = 255) (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L163) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L45) | unit/verify | unproven | ### [`RFC3768-7.1-5`](#rfc3768-7.1-5) Rx: verify the Auth Type matches the locally configured authentication method and perform that method (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L599) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L46) | unit/verify | unproven | ### [`RFC3768-7.1-6`](#rfc3768-7.1-6) Rx: discard the packet if any mandatory receive check fails (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceRxDecodeErrorMapsReason`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L463) | unit/verify | unproven | | positive | [`TestInstanceRxValidAdvertReachesFSM`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L483) | unit/verify | unproven | ### [`RFC3768-7.1-7`](#rfc3768-7.1-7) Rx: if the optional address-list check fails and the sender is not the address owner (Priority != 255), drop the packet (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceV2AddressListMismatchDrops`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L526) | unit/verify | unproven | | positive | [`TestInstanceV2AddressListMatchReachesFSM`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L565) | unit/verify | unproven | ### [`RFC3768-7.1-8`](#rfc3768-7.1-8) Rx: verify the Adver Interval in the packet equals the locally configured value; discard the packet on mismatch (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDecodeV2IntervalMismatchDiscard`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L293) | unit/verify | unproven | | negative | [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L251) | unit/verify | unproven | | positive | [`TestDecodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L47) | unit/verify | unproven | ### [`RFC3768-7.2-1`](#rfc3768-7.2-1) Tx: fill in the VRRP fields from the virtual router configuration state and compute the VRRP checksum (§7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEncodeGoldenV2`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L87) | unit/verify | unproven | ### [`RFC3768-7.2-2`](#rfc3768-7.2-2) Tx: set the source MAC address to the virtual router MAC address (§7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestConstants`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L385) | unit/verify | unproven | ### [`RFC3768-7.2-3`](#rfc3768-7.2-3) Tx: set the source IP address to the interface primary IP address (§7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSendAdvertUsesParentPrimaryV4Source`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L202) | unit/verify | unproven | ### [`RFC3768-7.2-4`](#rfc3768-7.2-4) Tx: set the IP protocol to VRRP (112) and send to the VRRP IP multicast group 224.0.0.18 (§7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSendAdvertIPv4HeaderTTLProtoDst`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L261) | unit/verify | unproven | ### [`RFC3768-8.2-1`](#rfc3768-8.2-1) Master: never respond to host ARP requests for virtual addresses with the physical MAC address (§8.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDataplaneRestoreOnLastGroup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L123) | unit/verify | unproven | | positive | [`TestDataplaneApplyIPv4SetsRecipe`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L62) | unit/verify | unproven | ### [`RFC3768-8.3-1`](#rfc3768-8.3-1) Advertise the virtual router MAC address in Proxy ARP messages sent on behalf of VRRP-protected addresses (§8.3; lowercase "must" in the RFC) Audit verdict: not audited: no reader has judged these tests No test carries RFC3768-8.3-1, so no unit is bound to it. ### [`RFC3768-9.2-1`](#rfc3768-9.2-1) Token ring: implement the functional-address mode of operation when supporting VRRP on token ring (§9.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3768-9.2-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 3768, so no reviewer has walked its text sentence by sentence. ## Superseded RFC 3768 is obsoleted by RFC 9568. | Requirement | Disposition | Now stated at | Reason | |---|---|---|---| | [`RFC3768-5.2.2-1`](#rfc3768-5.2.2-1) Never forward a datagram destined to 224.0.0.18, regardless of its TTL (§5.2.2) | restated | RFC9568-5.1.1.2-1 | RFC 9568 Section 5.1.1.2 keeps the rule for IPv4 and adds the IPv6 counterpart at RFC9568-5.1.2.2-1 for ff02::12 | | [`RFC3768-5.2.3-1`](#rfc3768-5.2.3-1) Set the IP TTL of transmitted VRRP packets to 255 (§5.2.3) | restated | RFC9568-5.1.1.3-1 | RFC 9568 Section 5.1.1.3 keeps the IPv4 TTL of 255 and adds the IPv6 Hop Limit counterpart at RFC9568-5.1.2.3-1 | | [`RFC3768-5.2.3-2`](#rfc3768-5.2.3-2) Discard received VRRP packets whose TTL is not equal to 255 (§5.2.3, §7.1) | restated | RFC9568-5.1.1.3-2 | RFC 9568 Section 5.1.1.3 keeps the discard rule for IPv4 and adds the IPv6 Hop Limit counterpart at RFC9568-5.1.2.3-2 | | [`RFC3768-5.3.2-1`](#rfc3768-5.3.2-1) Discard packets with unknown Type; only 1 = ADVERTISEMENT is defined (§5.3.2) | restated | RFC9568-5.2.2-1 | RFC 9568 Section 5.2.2 keeps ADVERTISEMENT as the only defined type and keeps the discard rule for any other value | | [`RFC3768-5.3.4-1`](#rfc3768-5.3.4-1) Use Priority 255 for the VRRP router that owns the virtual router's IP address(es) (§5.3.4) | restated | RFC9568-5.2.4-1 | RFC 9568 Section 5.2.4 keeps Priority 255 for the router owning the Virtual Router addresses, over both address families | | [`RFC3768-5.3.4-2`](#rfc3768-5.3.4-2) Use Priority values 1-254 for VRRP routers backing up a virtual router (§5.3.4) | restated | RFC9568-5.2.4-2 | RFC 9568 Section 5.2.4 keeps the 1 to 254 range for backup routers | | [`RFC3768-5.3.6-1`](#rfc3768-5.3.6-1) Discard packets with unknown Auth Type or an Auth Type that does not match the locally configured authentication method (§5.3.6, §7.1) | dropped | not stated | VRRPv3 removes authentication. RFC 9568 Section 9 states that VRRP for IPvX does not currently include any type of authentication, and its packet format carries no Auth Type field, so no unknown-or-mismatched Auth Type can be received. The octet that held Auth Type in VRRPv2 is the Reserve field of RFC 9568 Section 5.2.6 | | [`RFC3768-6.4.2-1`](#rfc3768-6.4.2-1) Backup: never respond to ARP requests for the IP address(es) associated with the virtual router (§6.4.2) | restated | RFC9568-6.4.2-1 | RFC 9568 Section 6.4.2 keeps the rule for IPv4 ARP and adds the IPv6 counterparts, RFC9568-6.4.2-2 for Neighbor Solicitations and RFC9568-6.4.2-3 for Router Advertisements | | [`RFC3768-6.4.2-2`](#rfc3768-6.4.2-2) Backup: discard packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.2) | restated | RFC9568-6.4.2-4 | RFC 9568 Section 6.4.2 keeps the rule that a Backup Router discards packets whose destination link-layer address is the Virtual Router MAC | | [`RFC3768-6.4.2-3`](#rfc3768-6.4.2-3) Backup: never accept packets addressed to the IP address(es) associated with the virtual router (§6.4.2) | restated | RFC9568-6.4.2-5 | RFC 9568 Section 6.4.2 keeps the rule and states it over both address families | | [`RFC3768-6.4.2-4`](#rfc3768-6.4.2-4) Backup: on Shutdown, cancel the Master_Down_Timer and transition to Initialize (§6.4.2) | restated | RFC9568-6.4.2-6 | RFC 9568 Section 6.4.2 keeps the Shutdown transition and renames Master_Down_Timer to Active_Down_Timer | | [`RFC3768-6.4.2-5`](#rfc3768-6.4.2-5) Backup: when the Master_Down_Timer fires, send an ADVERTISEMENT, broadcast a gratuitous ARP with the virtual router MAC for each virtual IP address, set the Adver_Timer to Advertisement_Interval, and transition to Master (§6.4.2) | restated | RFC9568-6.4.2-7 | RFC 9568 Section 6.4.2 keeps the whole timer-expiry sequence, renames the timer to Active_Down_Timer and the state to Active, and adds the IPv6 unsolicited Neighbor Advertisement beside the gratuitous ARP | | [`RFC3768-6.4.2-6`](#rfc3768-6.4.2-6) Backup: on an ADVERTISEMENT with Priority 0, set the Master_Down_Timer to Skew_Time (§6.4.2) | restated | RFC9568-6.4.2-8 | RFC 9568 Section 6.4.2 keeps the Skew_Time rule for a Priority 0 advertisement | | [`RFC3768-6.4.2-7`](#rfc3768-6.4.2-7) Backup: on a non-zero-priority ADVERTISEMENT, if Preempt_Mode is False or the advertised Priority >= local Priority, reset the Master_Down_Timer to Master_Down_Interval (§6.4.2) | restated | RFC9568-6.4.2-9 | RFC 9568 Section 6.4.2 keeps the rule and adds one step, that the Backup Router adopts the Max Advertise Interval from the advertisement as Active_Adver_Interval before recomputing the timers | | [`RFC3768-6.4.2-8`](#rfc3768-6.4.2-8) Backup: on a non-zero-priority ADVERTISEMENT with Preempt_Mode True and advertised Priority < local Priority, discard the ADVERTISEMENT (§6.4.2) | restated | RFC9568-6.4.2-10 | RFC 9568 Section 6.4.2 keeps the discard rule for a lower-priority advertisement under Preempt_Mode | | [`RFC3768-6.4.3-1`](#rfc3768-6.4.3-1) Master: respond to ARP requests for the IP address(es) associated with the virtual router, answering with the virtual MAC address (§6.4.3, §8.2) | restated | RFC9568-6.4.3-1 | RFC 9568 Section 6.4.3 keeps the ARP obligation for IPv4 and adds the IPv6 Neighbor Discovery counterparts at RFC9568-6.4.3-2, RFC9568-6.4.3-3 and RFC9568-6.4.3-4 | | [`RFC3768-6.4.3-2`](#rfc3768-6.4.3-2) Master: forward packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.3) | restated | RFC9568-6.4.3-5 | RFC 9568 Section 6.4.3 keeps the rule that the Active Router forwards packets whose destination link-layer address is the Virtual Router MAC | | [`RFC3768-6.4.3-3`](#rfc3768-6.4.3-3) Master: never accept packets addressed to the virtual router IP address(es) when not the IP address owner (§6.4.3) | restated | RFC9568-6.4.3-7 | RFC 9568 Section 6.4.3 keeps the prohibition and narrows it, because VRRPv3 adds Accept_Mode: the Active Router refuses such packets only when it is neither the address owner nor configured with Accept_Mode True | | [`RFC3768-6.4.3-4`](#rfc3768-6.4.3-4) Master: accept packets addressed to the virtual router IP address(es) when the IP address owner (§6.4.3) | restated | RFC9568-6.4.3-6 | RFC 9568 Section 6.4.3 keeps the owner case and widens it to Accept_Mode True | | [`RFC3768-6.4.3-5`](#rfc3768-6.4.3-5) Master: on Shutdown, cancel the Adver_Timer, send an ADVERTISEMENT with Priority = 0, and transition to Initialize (§6.4.3) | restated | RFC9568-6.4.3-8 | RFC 9568 Section 6.4.3 keeps the Shutdown sequence unchanged | | [`RFC3768-6.4.3-6`](#rfc3768-6.4.3-6) Master: when the Adver_Timer fires, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) | restated | RFC9568-6.4.3-9 | RFC 9568 Section 6.4.3 keeps the Adver_Timer expiry behaviour unchanged | | [`RFC3768-6.4.3-7`](#rfc3768-6.4.3-7) Master: on an ADVERTISEMENT with Priority 0, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) | restated | RFC9568-6.4.3-10 | RFC 9568 Section 6.4.3 keeps the response to a Priority 0 advertisement unchanged | | [`RFC3768-6.4.3-8`](#rfc3768-6.4.3-8) Master: on an ADVERTISEMENT with higher Priority, or equal Priority and greater sender primary IP address, cancel the Adver_Timer, set the Master_Down_Timer to Master_Down_Interval, and transition to Backup (§6.4.3) | restated | RFC9568-6.4.3-11 | RFC 9568 Section 6.4.3 keeps the rule and states that the sender address comparison is an unsigned integer comparison in network byte order | | [`RFC3768-6.4.3-9`](#rfc3768-6.4.3-9) Master: on a losing ADVERTISEMENT (lower Priority, or equal Priority with smaller sender address), discard it (§6.4.3) | restated | RFC9568-6.4.3-12 | RFC 9568 Section 6.4.3 keeps the discard and adds one step, that the Active Router immediately sends an advertisement so learning bridges relearn the segment. RFC 9568 Section 1.2 lists that addition as change 5 | | [`RFC3768-7.1-1`](#rfc3768-7.1-1) Rx: verify the VRRP version is 2 (§7.1) | restated | RFC9568-7.1-1 | RFC 9568 Section 7.1 keeps the version check and changes the value it verifies from 2 to 3 | | [`RFC3768-7.1-2`](#rfc3768-7.1-2) Rx: verify the received packet contains the complete VRRP packet, including fixed fields, IP address(es), and Authentication Data (§7.1) | restated | RFC9568-7.1-3 | RFC 9568 Section 7.1 keeps the completeness check over the fixed fields and the address list. The Authentication Data half of the RFC 3768 check is gone with the field | | [`RFC3768-7.1-3`](#rfc3768-7.1-3) Rx: verify the VRRP checksum (§7.1) | restated | RFC9568-5.2.8-1 | RFC 9568 moves the checksum definition to Section 5.2.8 and extends it, because the IPv6 checksum covers a pseudo-header. RFC 9568 Section 1.2 lists that as change 4 | | [`RFC3768-7.1-4`](#rfc3768-7.1-4) Rx: verify the VRID is configured on the receiving interface and the local router is not the IP address owner (Priority = 255) (§7.1) | restated | RFC9568-7.1-4 | RFC 9568 Section 7.1 keeps both halves of the check, that the VRID is configured on the receiving interface and that the local router is not the address owner | | [`RFC3768-7.1-5`](#rfc3768-7.1-5) Rx: verify the Auth Type matches the locally configured authentication method and perform that method (§7.1) | dropped | not stated | VRRPv3 removes authentication. RFC 9568 Section 7.1 lists no Auth Type check, its packet format carries no Auth Type field, and its Section 9 states that VRRP for IPvX does not currently include any type of authentication | | [`RFC3768-7.1-6`](#rfc3768-7.1-6) Rx: discard the packet if any mandatory receive check fails (§7.1) | restated | RFC9568-7.1-6 | RFC 9568 Section 7.1 keeps the discard on a failed mandatory check, and keeps the SHOULD log and MAY network-management indication beside it | | [`RFC3768-7.1-7`](#rfc3768-7.1-7) Rx: if the optional address-list check fails and the sender is not the address owner (Priority != 255), drop the packet (§7.1) | dropped | not stated | RFC 9568 Section 7.1 removes the drop. The address-list check is a MAY at RFC9568-7.1-11, and on failure the receiver only SHOULD log the event and MAY indicate a misconfiguration. No sender-priority condition and no packet drop remain | | [`RFC3768-7.1-8`](#rfc3768-7.1-8) Rx: verify the Adver Interval in the packet equals the locally configured value; discard the packet on mismatch (§7.1) | restated | RFC9568-7.1-8 | RFC 9568 Section 7.1 lowers the check from MUST to SHOULD and removes the drop, stating that the mismatch will not result in the VRRP packet being dropped. RFC 9568 Section 1.2 lists that as change 8. The field is also renamed and rescaled, from Adver Int in seconds to Max Advertise Interval in centiseconds | | [`RFC3768-7.2-1`](#rfc3768-7.2-1) Tx: fill in the VRRP fields from the virtual router configuration state and compute the VRRP checksum (§7.2) | restated | RFC9568-7.2-1 | RFC 9568 Section 7.2 keeps the fill-and-checksum step unchanged | | [`RFC3768-7.2-2`](#rfc3768-7.2-2) Tx: set the source MAC address to the virtual router MAC address (§7.2) | restated | RFC9568-7.2-2 | RFC 9568 Section 7.2 keeps the Virtual Router MAC as the source link-layer address | | [`RFC3768-7.2-3`](#rfc3768-7.2-3) Tx: set the source IP address to the interface primary IP address (§7.2) | restated | RFC9568-7.2-3 | RFC 9568 Section 7.2 keeps the interface primary IPv4 address and adds the IPv6 case, where the source is the interface link-local address | | [`RFC3768-7.2-4`](#rfc3768-7.2-4) Tx: set the IP protocol to VRRP (112) and send to the VRRP IP multicast group 224.0.0.18 (§7.2) | restated | RFC9568-7.2-4 | RFC 9568 Section 7.2 keeps IP protocol 112 and adds the IPv6 destination group ff02::12 beside 224.0.0.18 | | [`RFC3768-8.2-1`](#rfc3768-8.2-1) Master: never respond to host ARP requests for virtual addresses with the physical MAC address (§8.2) | restated | RFC9568-8.1.2-1 | RFC 9568 Section 8.1.2 keeps the prohibition for IPv4 ARP and adds the IPv6 Neighbor Discovery counterpart at RFC9568-8.2.2-1 | | [`RFC3768-8.3-1`](#rfc3768-8.3-1) Advertise the virtual router MAC address in Proxy ARP messages sent on behalf of VRRP-protected addresses (§8.3; lowercase "must" in the RFC) | restated | RFC9568-8.1.3-1 | RFC 9568 Section 8.1.3 keeps the Proxy ARP obligation and states it with an uppercase MUST | | [`RFC3768-9.2-1`](#rfc3768-9.2-1) Token ring: implement the functional-address mode of operation when supporting VRRP on token ring (§9.2) | dropped | not stated | RFC 9568 removes token ring. Its Section 1.2 lists as change 6 that the appendices describing operation over legacy technologies, FDDI, Token Ring and ATM LAN Emulation, were removed, so RFC 9568 states no functional-address obligation | | [`RFC3768-5.3.10-1`](#rfc3768-5.3.10-1) Set Authentication Data to zero on transmission and ignore it on reception (§5.3.10) | dropped | not stated | VRRPv3 removes the Authentication Data field, so no obligation about its value remains. RFC 9568 Section 5.2.6 states the same zero-on-transmission and ignore-on-reception rule for its Reserve field at RFC9568-5.2.6-1, which is a different field: it occupies the octet VRRPv2 used for Auth Type | | [`RFC3768-7.1-9`](#rfc3768-7.1-9) Log the event when a mandatory receive check fails (§7.1) | restated | RFC9568-7.1-7 | RFC 9568 Section 7.1 keeps the log on a failed mandatory check and adds rate-limiting to it | | [`RFC3768-7.1-10`](#rfc3768-7.1-10) Log the event on an address-list mismatch (§7.1) | restated | RFC9568-7.1-9 | RFC 9568 Section 7.1 keeps the log on an address-list mismatch, adds rate-limiting, and states it in one sentence covering the Max Advertise Interval mismatch as well | | [`RFC3768-7.1-11`](#rfc3768-7.1-11) Log the event on an Adver Interval mismatch (§7.1) | restated | RFC9568-7.1-9 | RFC 9568 Section 7.1 merges this log with the address-list one into a single rate-limited recommendation, and renames the field to Max Advertise Interval | | [`RFC3768-8.2-2`](#rfc3768-8.2-2) After restart/boot, never send ARP messages with the physical MAC address for owned virtual IP addresses (§8.2) | restated | RFC9568-8.1.2-3 | RFC 9568 Section 8.1.2 keeps the rule for IPv4 ARP and adds the IPv6 Neighbor Discovery counterpart at RFC9568-8.2.2-5 | | [`RFC3768-8.2-3`](#rfc3768-8.2-3) Broadcast a gratuitous ARP with the virtual router MAC for each IP address when configuring an interface, and delay ARP at boot until both the IP address and virtual MAC are configured (§8.2) | restated | RFC9568-8.1.2-4 | RFC 9568 Section 8.1.2 splits the sentence in two and raises one half. The gratuitous ARP on interface configuration stays a SHOULD at RFC9568-8.1.2-4, and the boot delay until both the address and the Virtual Router MAC are configured becomes a MUST at RFC9568-8.1.2-2 | | [`RFC3768-8.4-1`](#rfc3768-8.4-1) Never forward packets addressed to IP addresses adopted as Master when not the owner (§8.4) | restated | RFC9568-8.3.1-1 | RFC 9568 Section 8.3.1 keeps the rule and states it over both address families | | [`RFC3768-9.1-1`](#rfc3768-9.1-1) FDDI: configure the virtual router MAC via a unicast MAC filter rather than changing the hardware MAC address (§9.1) | dropped | not stated | RFC 9568 removes FDDI. Its Section 1.2 lists as change 6 that the appendices describing operation over legacy technologies, FDDI, Token Ring and ATM LAN Emulation, were removed, so RFC 9568 states no unicast-MAC-filter recommendation | | [`RFC3768-7.1-12`](#rfc3768-7.1-12) Indicate via network management that a receive error or misconfiguration occurred (§7.1) | restated | RFC9568-7.1-10 | RFC 9568 Section 7.1 keeps the network-management indication as a MAY | | [`RFC3768-7.1-13`](#rfc3768-7.1-13) Verify that Count IP Addrs and the address list match the IP_Addresses configured for the VRID (§7.1) | restated | RFC9568-7.1-11 | RFC 9568 Section 7.1 keeps the address-list check as a MAY and renames the field to IPvX Addr Count | | [`RFC3768-9.2-2`](#rfc3768-9.2-2) Token ring: support the unicast mode of operation in addition to functional addresses (§9.2) | dropped | not stated | RFC 9568 removes token ring. Its Section 1.2 lists as change 6 that the appendices describing operation over legacy technologies, FDDI, Token Ring and ATM LAN Emulation, were removed, so RFC 9568 states no unicast-mode permission | --- ### Page: RFC 3786 - Extending the Number of Intermediate System to Intermediate System (IS-IS) Link State PDU (LSP) Fragments Beyond the 256 Limit https://ze-software.net/quality/rfc-compliance/rfc3786/ # RFC 3786 - Extending the Number of Intermediate System to Intermediate System (IS-IS) Link State PDU (LSP) Fragments Beyond the 256 Limit No row in the public ledger. Every requirement this repository extracted from RFC 3786, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 7 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 4 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 4 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 7 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 4 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3786.md` | | Requirement shard | `rfc/requirements/rfc3786.md` | | RFC text | `rfc/full/rfc3786.txt` | ## Enrolment Enrolled: Extending the Number of IS-IS LSP Fragments Beyond the 256 Limit: four MUST-level requirements, all {not-applicable} to Ze. Ze does not implement the RFC 3786 extended-LSP-fragment mechanism -- its IS-IS codec recognizes no IS Alias ID TLV (type 24) and originates no extended LSP sets or virtual system (recognized TLV set 1/2/6/8/9/10/22/129/132/135/137/232/236/240 in internal/plugins/isis/packet/tlv.go), running only standard ISO/IEC 10589 LSPs bounded by the 256-fragment limit. RFC3786-2-1 (IS Alias ID TLV in fragment 0) and RFC3786-3.1-1 (generate extended fragment zero) have no extended-LSP origination path; RFC3786-5-1 (SPF exclusion of an expired extended fragment 0 set) governs extended sets Ze does not have (its SPF operates only on standard LSPs); RFC3786-x-2 (Mode 1 Originating-to-Virtual adjacency zero metric) governs a virtual-system model Ze does not implement. No SHOULD/MAY requirements are gated. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 3786. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (4):** [`RFC3786-2-1`](#rfc3786-2-1), [`RFC3786-3.1-1`](#rfc3786-3.1-1), [`RFC3786-5-1`](#rfc3786-5-1), [`RFC3786-x-2`](#rfc3786-x-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3786-2-1` | IS Alias ID TLV included in fragment 0 of every LSP set in either mode (Section 2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the RFC 3786 extended-LSP-fragment mechanism. Its IS-IS codec does not recognize the IS Alias ID TLV (type 24) -- the recognized TLV set is 1/2/6/8/9/10/22/129/132/135/137/232/236/240 (internal/plugins/isis/packet/tlv.go) with no type 24 and no extended-LSP-set origination, so Ze never generates an extended LSP set that would need an IS Alias ID TLV. | | `RFC3786-3.1-1` | Generate an extended LSP fragment zero for every extended LSP set (Section 3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze generates no extended LSP sets -- it originates only standard IS-IS LSPs (bounded by the ISO/IEC 10589 256-fragment limit) and has no IS Alias ID TLV (type 24) / virtual-system origination path (internal/plugins/isis/packet/tlv.go recognizes no type 24), so there is no extended LSP set for which an extended fragment zero would be generated. | | `RFC3786-5-1` | Consider any of a system's LSPs in SPF when its Original LSP fragment 0 is missing or has zero RemainingLifetime; for an expired extended fragment 0, exclude only that set (Section 5) | MUST NOT | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** The RFC 3786-specific clause -- "for an expired extended fragment 0, exclude only that set" -- governs extended LSP sets, which Ze does not implement (no IS Alias ID TLV type 24, no extended LSP sets; internal/plugins/isis/packet/tlv.go). Ze's SPF operates only on standard (non-extended) IS-IS LSPs per ISO/IEC 10589, so the extended-set exclusion rule has no applicable code path. | | `RFC3786-x-1` | Set ATT bits and the Partition Repair bit to zero on all extended LSPs (Sections 3.1.1, 3.1.2) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3786-3.1.4-1` | Set the overload bit consistently across all original and extended LSPs to reflect the Originating System's overload state (Section 3.1.4) | SHOULD | 3.1.4 | **positive:** no positive test. **negative:** no negative test | | `RFC3786-x-2` | In Mode 1, metric for Originating-to-Virtual adjacencies is zero and no other neighbors are specified in an Extended LSP (Sections 3.2, 3.2.1) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Mode 1 (the virtual-system model with Originating-to-Virtual adjacencies in Extended LSPs) is part of the RFC 3786 extended-fragment mechanism Ze does not implement -- Ze has no virtual system, no extended LSP, and no IS Alias ID TLV (type 24) origination (internal/plugins/isis/packet/tlv.go), so there is no Originating-to-Virtual adjacency to assign a zero metric to. | | `RFC3786-7-1` | Provide a config parameter for LSP origination behavior, defaulting to ISO/IEC 10589 behavior with neither mode enabled (Section 7) | SHOULD | 7 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3786-2-1`](#rfc3786-2-1) IS Alias ID TLV included in fragment 0 of every LSP set in either mode (Section 2) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the RFC 3786 extended-LSP-fragment mechanism. Its IS-IS codec does not recognize the IS Alias ID TLV (type 24) -- the recognized TLV set is 1/2/6/8/9/10/22/129/132/135/137/232/236/240 (internal/plugins/isis/packet/tlv.go) with no type 24 and no extended-LSP-set origination, so Ze never generates an extended LSP set that would need an IS Alias ID TLV. | | [`RFC3786-3.1-1`](#rfc3786-3.1-1) Generate an extended LSP fragment zero for every extended LSP set (Section 3.1) | no test | no test carries this requirement id; annotated {not-applicable}: Ze generates no extended LSP sets -- it originates only standard IS-IS LSPs (bounded by the ISO/IEC 10589 256-fragment limit) and has no IS Alias ID TLV (type 24) / virtual-system origination path (internal/plugins/isis/packet/tlv.go recognizes no type 24), so there is no extended LSP set for which an extended fragment zero would be generated. | | [`RFC3786-5-1`](#rfc3786-5-1) Consider any of a system's LSPs in SPF when its Original LSP fragment 0 is missing or has zero RemainingLifetime; for an expired extended fragment 0, exclude only that set (Section 5) | no test | no test carries this requirement id; annotated {not-applicable}: The RFC 3786-specific clause -- "for an expired extended fragment 0, exclude only that set" -- governs extended LSP sets, which Ze does not implement (no IS Alias ID TLV type 24, no extended LSP sets; internal/plugins/isis/packet/tlv.go). Ze's SPF operates only on standard (non-extended) IS-IS LSPs per ISO/IEC 10589, so the extended-set exclusion rule has no applicable code path. | | [`RFC3786-x-2`](#rfc3786-x-2) In Mode 1, metric for Originating-to-Virtual adjacencies is zero and no other neighbors are specified in an Extended LSP (Sections 3.2, 3.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: Mode 1 (the virtual-system model with Originating-to-Virtual adjacencies in Extended LSPs) is part of the RFC 3786 extended-fragment mechanism Ze does not implement -- Ze has no virtual system, no extended LSP, and no IS Alias ID TLV (type 24) origination (internal/plugins/isis/packet/tlv.go), so there is no Originating-to-Virtual adjacency to assign a zero metric to. | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3786-2-1`](#rfc3786-2-1) IS Alias ID TLV included in fragment 0 of every LSP set in either mode (Section 2) Audit verdict: not audited: no reader has judged these tests No test carries RFC3786-2-1, so no unit is bound to it. ### [`RFC3786-3.1-1`](#rfc3786-3.1-1) Generate an extended LSP fragment zero for every extended LSP set (Section 3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3786-3.1-1, so no unit is bound to it. ### [`RFC3786-5-1`](#rfc3786-5-1) Consider any of a system's LSPs in SPF when its Original LSP fragment 0 is missing or has zero RemainingLifetime; for an expired extended fragment 0, exclude only that set (Section 5) Audit verdict: not audited: no reader has judged these tests No test carries RFC3786-5-1, so no unit is bound to it. ### [`RFC3786-x-2`](#rfc3786-x-2) In Mode 1, metric for Originating-to-Virtual adjacencies is zero and no other neighbors are specified in an Extended LSP (Sections 3.2, 3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3786-x-2, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 3786, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 3786, so its obligations are stated where they were written. --- ### Page: RFC 3787 - Recommendations for Interoperable IP Networks using Intermediate System to Intermediate System (IS-IS) https://ze-software.net/quality/rfc-compliance/rfc3787/ # RFC 3787 - Recommendations for Interoperable IP Networks using Intermediate System to Intermediate System (IS-IS) Partial. Every requirement this repository extracted from RFC 3787, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 66.7% | 2 of 3 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 3 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 3 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 4 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 3 | of 6 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 3 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 3 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 3 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 3 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 33.3% | 1 of 3 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 3 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 6 | | Gated MUST-level | 3 | | Not applicable, so out of scope | 0 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 4 | | Tagged units | 4 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3787.md` | | Requirement shard | `rfc/requirements/rfc3787.md` | | RFC text | `rfc/full/rfc3787.txt` | ## Enrolment Enrolled: IS-IS interoperability guidelines: three MUST-level requirements over the IS-IS TLV codec and IIH origination. RFC3787-x-1 (ignore TLV 131 Inter-Domain Routing Protocol Info and TLV 133 old Authentication if received) has both polarities in TestISISIgnoreObsoleteTLVs131And133: neither is a recognized codec type constant so both fall through the opaque-unknown passthrough (retained for verbatim re-flood, never interpreted) and TLV 133 is never selected by AuthTLVIndex (only TLV 10 is auth), while a recognized TLV 129 in the same region IS still decoded (ignore is scoped, not a blanket drop). RFC3787-x-2 (generate Protocols Supported TLV 129 including IP + IP Interface Address TLV 132 in IIH) has both polarities: TestISISIIHOriginationTLVs proves the originated LAN and P2P IIH both carry TLV 129 and 132, and TestISISHelloTLV132RequiresInterfaceAddr proves TLV 132 is emitted only from a real interface address (a circuit with no IPv4 omits it) while TLV 129 still advertises the IPv4 NLPID. RFC3787-5-1 (continue narrow metrics unless all devices support wide) is {gap}: Ze originates ONLY wide metrics (TLV 22/135) and never the narrow TLV 2 by umbrella decision, so it does not fall back to narrow-metric origination for a mixed domain; disclosed in the docs/features/rfc-status.md RFC 3787 row (Partial). The 4-1/4-2 SHOULDs (overload bit), 8-1 MAY (default routes in L1) and the FORMAT rows are not gated. ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc3787.md). **What the ledger says remains** One MUST gap, gated in [`rfc/short/rfc3787.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc3787.md): 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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 1 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **3** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC3787-x-1`](#rfc3787-x-1), [`RFC3787-x-2`](#rfc3787-x-2) **Annotated instead of tested (1):** [`RFC3787-5-1`](#rfc3787-5-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3787-x-1` | Ignore TLV 131 (Inter-Domain Routing Protocol Information) and TLV 133 (Authentication, replaced by TLV 10) if received (Sections 3.1, 3.2) | MUST | x | **positive:** `unit/verify` [`TestISISIgnoreObsoleteTLVs131And133`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_opaque_test.go#L54). **negative:** `unit/verify` [`TestISISIgnoreObsoleteTLVs131And133`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_opaque_test.go#L61) | | `RFC3787-4-1` | Use the Overload Bit to signal not ready for transit traffic; set it in non-pseudonode LSP number Zero, not in PseudoNode LSPs; ignore OL in PseudoNode LSPs (Section 4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC3787-4-2` | On receiving an OL-set LSP number Zero, treat all IP reachability advertisements as directly connected in SPF (Section 4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC3787-5-1` | Continue using narrow metrics unless all devices in the domain support wide metrics (Section 5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Ze originates ONLY wide metrics by umbrella decision -- it never emits the narrow TLV 2 (internal/plugins/isis/packet/tlv_neighbours.go:59-60 "Ze never originates TLV 2"; internal/plugins/isis/types/metric.go:15 "Only wide metrics are originated by Ze"). Ze DECODES a legacy neighbor's narrow TLV 2 (decode-only) so it can parse mixed-domain LSPs, but it does not fall back to narrow-metric ORIGINATION, so a narrow-only router cannot interpret Ze's advertisements. Ze therefore requires every device in the domain to support wide metrics rather than continuing with narrow until the whole domain is wide-capable. Disclosed in docs/features/rfc-status.md | | `RFC3787-8-1` | Generate default routes in Level 1 (Section 8) | MAY | 8 | **positive:** no positive test. **negative:** no negative test | | `RFC3787-x-2` | Generate a Protocol Supported TLV (code 129) including IP, and include an IP Interface Address TLV (132) in IIH PDUs for mixed-environment interoperability (Sections 9, 10) | MUST | x | **positive:** `unit/verify` [`TestISISIIHOriginationTLVs`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_test.go#L136). **negative:** `unit/verify` [`TestISISHelloTLV132RequiresInterfaceAddr`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_test.go#L186) | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3787-5-1`](#rfc3787-5-1) Continue using narrow metrics unless all devices in the domain support wide metrics (Section 5) | {gap}, no test | Ze originates ONLY wide metrics by umbrella decision -- it never emits the narrow TLV 2 (internal/plugins/isis/packet/tlv_neighbours.go:59-60 "Ze never originates TLV 2"; internal/plugins/isis/types/metric.go:15 "Only wide metrics are originated by Ze"). Ze DECODES a legacy neighbor's narrow TLV 2 (decode-only) so it can parse mixed-domain LSPs, but it does not fall back to narrow-metric ORIGINATION, so a narrow-only router cannot interpret Ze's advertisements. Ze therefore requires every device in the domain to support wide metrics rather than continuing with narrow until the whole domain is wide-capable. Disclosed in docs/features/rfc-status.md | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3787-x-1`](#rfc3787-x-1) Ignore TLV 131 (Inter-Domain Routing Protocol Information) and TLV 133 (Authentication, replaced by TLV 10) if received (Sections 3.1, 3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISIgnoreObsoleteTLVs131And133`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_opaque_test.go#L61) | unit/verify | unproven | | positive | [`TestISISIgnoreObsoleteTLVs131And133`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_opaque_test.go#L54) | unit/verify | unproven | ### [`RFC3787-5-1`](#rfc3787-5-1) Continue using narrow metrics unless all devices in the domain support wide metrics (Section 5) Audit verdict: not audited: no reader has judged these tests No test carries RFC3787-5-1, so no unit is bound to it. ### [`RFC3787-x-2`](#rfc3787-x-2) Generate a Protocol Supported TLV (code 129) including IP, and include an IP Interface Address TLV (132) in IIH PDUs for mixed-environment interoperability (Sections 9, 10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISHelloTLV132RequiresInterfaceAddr`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_test.go#L186) | unit/verify | unproven | | positive | [`TestISISIIHOriginationTLVs`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_test.go#L136) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 3787, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 3787, so its obligations are stated where they were written. --- ### Page: RFC 3948 - UDP Encapsulation of IPsec ESP Packets https://ze-software.net/quality/rfc-compliance/rfc3948/ # RFC 3948 - UDP Encapsulation of IPsec ESP Packets Partial. Every requirement this repository extracted from RFC 3948, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 57.1% | 8 of 14 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 28.6% | 4 of 14 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 14 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 3.8% | 1 of 26 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 14 | of 18 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 14 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 7.1% | 1 of 14 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 14 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 14 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 7.1% | 1 of 14 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 14 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 18 | | Gated MUST-level | 14 | | Not applicable, so out of scope | 1 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 2 | | Test tags | 26 | | Tagged units | 26 | | Recorded audit verdicts | 0 | | Discrimination records | 1 | | Summary | `rfc/short/rfc3948.md` | | Requirement shard | `rfc/requirements/rfc3948.md` | | RFC text | `rfc/full/rfc3948.txt` | ## Enrolment Enrolled: UDP Encapsulation of IPsec ESP Packets (NAT-Traversal): eleven MUST-level requirements. Eight are met with tags in internal/component/ike: 2.1-1 (the ESP SPI is never zero), 2.1-3 (demultiplex on port 4500 -- a four-zero-byte marker is IKE, a single 0xFF byte is a NAT keepalive, otherwise ESP), 2.2-1 (the non-ESP marker of four zero bytes is prepended to IKE), 1-1 (a tunnel-mode client supports tunnel mode: every Child SA that negotiated no USE_TRANSPORT_MODE installs as tunnel, and a peer request the operator did not configure cannot change that), and 4-3 (a received NAT-keepalive reaches no SA, so it is never read as evidence the connection is live) carry positive+negative tags; 2.1-2 (the ESP SA uses UDP port 4500 for encapsulation), 4-1 (NAT keepalives are sent at a conservative sub-binding-timeout interval) and 2.3-2 (the keepalive payload is one octet of 0xFF) are {single-polarity: positive} with new tests. The last three ids were extracted by the 2026-08-31 extraction sign-off, which found their sites unmapped. 3.1.2-1 (ESP-in-UDP encapsulation and transport-mode checksum fixup), 3.1.2-2 (inner checksum handling on decapsulation), and 2.1-4 (do not depend on a zero UDP checksum) are {not-applicable}: ze delegates UDP-ESP encapsulation and decapsulation to the kernel via XFRM_ENCAP_ESPINUDP and never inspects the UDP checksum. ## What the public ledger says **Status:** Partial **What the ledger says is covered:** NAT-T non-ESP marker, UDP 4500 encapsulation, NAT keepalive, XFRM UDP encap attributes. **What the ledger says remains** 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` ([`internal/core/eap/pool.go`](https://github.com/ze-software/ze/blob/main/internal/core/eap/pool.go)) has no non-test caller, and `registerIKE` ([`internal/component/ike/engine/register.go`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/register.go)) discards the pool it builds. No engine code constructs a Configuration payload, so ze sends no CFG_REPLY. Closing it is [`plan/immediate/spec-ike-virtual-ip-assignment.md`](https://github.com/ze-software/ze/blob/main/plan/immediate/spec-ike-virtual-ip-assignment.md). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 8 | one part of the gated population | | Annotated instead of tested | 6 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 2 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **14** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (8):** [`RFC3948-2.1-1`](#rfc3948-2.1-1), [`RFC3948-3.1.2-1`](#rfc3948-3.1.2-1), [`RFC3948-3.1.2-2`](#rfc3948-3.1.2-2), [`RFC3948-2.2-1`](#rfc3948-2.2-1), [`RFC3948-2.1-3`](#rfc3948-2.1-3), [`RFC3948-1-1`](#rfc3948-1-1), [`RFC3948-4-3`](#rfc3948-4-3), [`RFC3948-5.2-1`](#rfc3948-5.2-1) **Annotated instead of tested (6):** [`RFC3948-2.1-2`](#rfc3948-2.1-2), [`RFC3948-2.1-4`](#rfc3948-2.1-4), [`RFC3948-4-1`](#rfc3948-4-1), [`RFC3948-2.3-2`](#rfc3948-2.3-2), [`RFC3948-3.1.1-1`](#rfc3948-3.1.1-1), [`RFC3948-5.1-1`](#rfc3948-5.1-1) **Evidence that runs nightly only (2):** [`RFC3948-3.1.2-1`](#rfc3948-3.1.2-1), [`RFC3948-3.1.2-2`](#rfc3948-3.1.2-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3948-2.1-1` | ESP SPI MUST NOT be zero (zero is reserved for the Non-ESP Marker to distinguish IKE from ESP on port 4500) (S2.1) | MUST NOT | 2.1 - UDP-Encapsulated ESP Header Format | **positive:** `unit/verify` [`TestGenerateESPSPI`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L255). **negative:** `unit/verify` [`TestGenerateESPSPI`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L269) | | `RFC3948-2.1-2` | Source and destination ports MUST match the ports used by IKE (port 4500 after port float) (S2.1) | MUST | 2.1 - UDP-Encapsulated ESP Header Format | **positive:** `unit/verify` [`TestChildSANATTEncapPorts`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L288). **negative:** no negative test. **{single-polarity}:** the Child SA install unconditionally sets both UDP-encap ports to the fixed IKE NAT-T port 4500 (internal/component/ike/engine/child.go:237-238,265-266); no ze code path produces a non-4500 encap port, so there is no mismatched-port case to reject | | `RFC3948-3.1.2-1` | Transport mode decapsulation MUST do one of three things with the inner TCP/UDP checksum: recompute it incrementally from the addresses received via IKE, recompute it in full, or zero a UDP checksum and flag the stack that a TCP one need not be computed (S3.1.2) | MUST | 3.1.2 - Transport Mode Decapsulation NAT Procedure | **positive:** `interop/nightly` [`checkNATTTransportInnerChecksum`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/ipsec/checkers.go#L1201). **negative:** `interop/nightly` [`checkNATTTunnelInnerChecksum`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/ipsec/checkers.go#L1240). **nightly-only:** every test bound to this requirement runs in the scheduled workflow alone, so nothing here is proven on the merge path | | `RFC3948-3.1.2-2` | Tunnel mode TCP checksums MUST be verified (S3.1.2) | MUST | 3.1.2 - Transport Mode Decapsulation NAT Procedure | **positive:** `interop/nightly` [`checkNATTTunnelInnerChecksum`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/ipsec/checkers.go#L1237). **negative:** `interop/nightly` [`checkNATTTransportInnerChecksum`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/ipsec/checkers.go#L1204). **nightly-only:** every test bound to this requirement runs in the scheduled workflow alone, so nothing here is proven on the merge path | | `RFC3948-2.2-1` | Non-ESP Marker (4 zero bytes) MUST be prepended to IKE packets on port 4500 (S2.2) | MUST | 2.2 - IKE Header Format for Port 4500 | **positive:** `unit/verify` [`TestNonESPMarker`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L83). **negative:** `unit/verify` [`TestNonESPMarkerESPPacket`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L115) | | `RFC3948-2.1-3` | Receiver MUST demultiplex by inspecting first 4 bytes after UDP header: zero = IKE, non-zero = ESP, 1-byte 0xFF = keepalive (S2.1, S2.2, S2.3) | MUST | 2.1 - UDP-Encapsulated ESP Header Format | **positive:** `unit/verify` [`TestIsNATKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L128). **positive:** `unit/verify` [`TestNonESPMarker`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L86). **negative:** `unit/verify` [`TestIsNATKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L134). **negative:** `unit/verify` [`TestNonESPMarkerESPPacket`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L118) | | `RFC3948-2.1-4` | Receivers MUST NOT depend on the UDP checksum being zero (S2.1) | MUST NOT | 2.1 - UDP-Encapsulated ESP Header Format | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze reads IKE/ESP demux from the OS UDP socket (internal/component/ike/engine/register.go:421) and never inspects the UDP checksum, so no ze code path can depend on it; ESP-in-UDP checksum handling is the kernel's | | `RFC3948-4-1` | Keepalive interval MUST be shorter than the NAT binding timeout (S4) | MUST | 4 - NAT Keepalive Procedure | **positive:** `unit/verify` [`TestKeepaliveDefaultInterval`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/keepalive_test.go#L67). **positive:** `unit/verify` [`TestNATKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/keepalive_test.go#L13). **negative:** no negative test. **{single-polarity}:** the keepalive interval is a fixed conservative 20s constant (internal/component/ike/transport/keepalive.go:13), well under a typical NAT UDP binding lifetime; there is no dynamic binding-timeout query to drive a negative | | `RFC3948-1-1` | IPsec tunnel mode clients MUST support tunnel mode (S1) | MUST | 1 - Introduction | **positive:** `unit/verify` [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L195). **positive:** `unit/verify` [`TestTunnelModeIsTheChildSADefault`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L468). **negative:** `unit/verify` [`TestTunnelModeIsTheChildSADefault`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L509) | | `RFC3948-2.3-2` | The NAT-keepalive sender MUST use a one-octet-long payload with the value 0xFF (S2.3) | MUST | 2.3 - NAT-Keepalive Packet Format | **positive:** `unit/verify` [`TestNATKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/keepalive_test.go#L16). **negative:** no negative test. **{single-polarity}:** the keepalive payload is a fixed one-octet constant (internal/component/ike/transport/keepalive.go:14,48) that no input can vary, so there is no non-conforming sender case to drive | | `RFC3948-4-3` | Reception of NAT-keepalive packets MUST NOT be used to detect whether a connection is live (S4) | MUST NOT | 4 - NAT Keepalive Procedure | **positive:** `unit/verify` [`TestNATKeepaliveReachesNoSA`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc7296_natt_test.go#L655). **negative:** `unit/verify` [`TestNATKeepaliveIsNeverDeliveredToASession`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/keepalive_test.go#L85). **negative:** `unit/verify` [`TestNATKeepaliveReachesNoSA`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc7296_natt_test.go#L664) | | `RFC3948-3.1.1-1` | Tunnel mode decapsulation, depending on local policy, MUST do one of three things: check the inner source address against the policy, check it against the address assigned to the peer, or NAT the packet (S3.1.1) | MUST | 3.1.1 - Tunnel Mode Decapsulation NAT Procedure | **positive:** `unit/verify` [`TestChildInboundPolicyDefinesTheValidInnerSourceSpace`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc3948_inner_source_policy_test.go#L28). **positive:** `unit/verify` [`TestChildSAInboundPolicyUsesNegotiatedTS`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L423). **negative:** no negative test. **{single-polarity}:** ze takes the first option by installing the inbound policy with the negotiated remote traffic selector as its source selector (childPolicyParams, internal/component/ike/engine/child.go), and the drop of a mismatched inner packet is the kernel's, so ze holds no rejecting branch | | `RFC3948-5.1-1` | Implementors MUST devise ways of preventing two remote peers from reaching one security gateway with overlapping inner addresses (S5.1) | MUST | 5.1 - Tunnel Mode Conflict | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze devises no such way, because it assigns no inner address at all. The section's RECOMMENDED remedy is to give each remote peer a locally unique address, and the allocator for it exists and is unreached: Pool.Allocate (internal/core/eap/pool.go) leases a unique address and refuses on exhaustion with ErrPoolExhausted, yet its only callers are pool_test.go and pool_release_test.go. registerIKE (internal/component/ike/engine/register.go) builds the pool from the remote-access config and then discards it at a bare `_ = ipPool`, so no lease ever reaches a peer. No engine code constructs a wire.PayloadCP either, so ze sends no CFG_REPLY and a client keeps whatever inner address it chose for itself. Two peers behind one NAT that both chose 10.1.2.3 would therefore present the gateway with the ambiguity the section names, and ze holds nothing that prevents it. Closing this is plan/immediate/spec-ike-virtual-ip-assignment.md | | `RFC3948-5.2-1` | Implementations MUST handle conflicting connections from clients behind one NAT, either by disallowing conflicting connections or by other means (S5.2) | MUST | 5.2 - Transport Mode Conflict | **positive:** `unit/verify` [`TestPolicyOwnerSeparatesDistinctSelectors`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/policy_owner_test.go#L263). **negative:** `unit/verify` [`TestPolicyOwnerRefusesASecondPeerOnOneSelector`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/policy_owner_test.go#L40) | | `RFC3948-2.1-5` | IPv4 UDP checksum SHOULD be zero on transmit (S2.1) | SHOULD | 2.1 - UDP-Encapsulated ESP Header Format | **positive:** no positive test. **negative:** no negative test | | `RFC3948-2.3-1` | NAT-keepalive packets (1 byte 0xFF) SHOULD be ignored by the receiver (S2.3) | SHOULD | 2.3 - NAT-Keepalive Packet Format | **positive:** no positive test. **negative:** no negative test | | `RFC3948-3.1.2-3` | TCP checksum verification MAY be skipped for transport mode only if the packet is integrity-protected (S3.1.2) | MAY | 3.1.2 - Transport Mode Decapsulation NAT Procedure | **positive:** no positive test. **negative:** no negative test | | `RFC3948-4-2` | Keepalive interval is implementation-specific; typical 20-30 seconds (S4) | MAY | 4 - NAT Keepalive Procedure | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3948-2.1-4`](#rfc3948-2.1-4) Receivers MUST NOT depend on the UDP checksum being zero (S2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze reads IKE/ESP demux from the OS UDP socket (internal/component/ike/engine/register.go:421) and never inspects the UDP checksum, so no ze code path can depend on it; ESP-in-UDP checksum handling is the kernel's | | [`RFC3948-5.1-1`](#rfc3948-5.1-1) Implementors MUST devise ways of preventing two remote peers from reaching one security gateway with overlapping inner addresses (S5.1) | {gap}, no test | ze devises no such way, because it assigns no inner address at all. The section's RECOMMENDED remedy is to give each remote peer a locally unique address, and the allocator for it exists and is unreached: Pool.Allocate (internal/core/eap/pool.go) leases a unique address and refuses on exhaustion with ErrPoolExhausted, yet its only callers are pool_test.go and pool_release_test.go. registerIKE (internal/component/ike/engine/register.go) builds the pool from the remote-access config and then discards it at a bare `_ = ipPool`, so no lease ever reaches a peer. No engine code constructs a wire.PayloadCP either, so ze sends no CFG_REPLY and a client keeps whatever inner address it chose for itself. Two peers behind one NAT that both chose 10.1.2.3 would therefore present the gateway with the ambiguity the section names, and ze holds nothing that prevents it. Closing this is plan/immediate/spec-ike-virtual-ip-assignment.md | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3948-2.1-1`](#rfc3948-2.1-1) ESP SPI MUST NOT be zero (zero is reserved for the Non-ESP Marker to distinguish IKE from ESP on port 4500) (S2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGenerateESPSPI`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L269) | unit/verify | unproven | | positive | [`TestGenerateESPSPI`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L255) | unit/verify | unproven | ### [`RFC3948-2.1-2`](#rfc3948-2.1-2) Source and destination ports MUST match the ports used by IKE (port 4500 after port float) (S2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSANATTEncapPorts`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L288) | unit/verify | unproven | ### [`RFC3948-3.1.2-1`](#rfc3948-3.1.2-1) Transport mode decapsulation MUST do one of three things with the inner TCP/UDP checksum: recompute it incrementally from the addresses received via IKE, recompute it in full, or zero a UDP checksum and flag the stack that a TCP one need not be computed (S3.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`checkNATTTunnelInnerChecksum`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/ipsec/checkers.go#L1240) | interop/nightly | unproven | | positive | [`checkNATTTransportInnerChecksum`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/ipsec/checkers.go#L1201) | interop/nightly | unproven | ### [`RFC3948-3.1.2-2`](#rfc3948-3.1.2-2) Tunnel mode TCP checksums MUST be verified (S3.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`checkNATTTransportInnerChecksum`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/ipsec/checkers.go#L1204) | interop/nightly | unproven | | positive | [`checkNATTTunnelInnerChecksum`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/ipsec/checkers.go#L1237) | interop/nightly | unproven | ### [`RFC3948-2.2-1`](#rfc3948-2.2-1) Non-ESP Marker (4 zero bytes) MUST be prepended to IKE packets on port 4500 (S2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNonESPMarkerESPPacket`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L115) | unit/verify | unproven | | positive | [`TestNonESPMarker`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L83) | unit/verify | unproven | ### [`RFC3948-2.1-3`](#rfc3948-2.1-3) Receiver MUST demultiplex by inspecting first 4 bytes after UDP header: zero = IKE, non-zero = ESP, 1-byte 0xFF = keepalive (S2.1, S2.2, S2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIsNATKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L134) | unit/verify | unproven | | negative | [`TestNonESPMarkerESPPacket`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L118) | unit/verify | unproven | | positive | [`TestIsNATKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L128) | unit/verify | unproven | | positive | [`TestNonESPMarker`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/nat_test.go#L86) | unit/verify | unproven | ### [`RFC3948-2.1-4`](#rfc3948-2.1-4) Receivers MUST NOT depend on the UDP checksum being zero (S2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3948-2.1-4, so no unit is bound to it. ### [`RFC3948-4-1`](#rfc3948-4-1) Keepalive interval MUST be shorter than the NAT binding timeout (S4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestKeepaliveDefaultInterval`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/keepalive_test.go#L67) | unit/verify | unproven | | positive | [`TestNATKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/keepalive_test.go#L13) | unit/verify | unproven | ### [`RFC3948-1-1`](#rfc3948-1-1) IPsec tunnel mode clients MUST support tunnel mode (S1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTunnelModeIsTheChildSADefault`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L509) | unit/verify | unproven | | positive | [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L195) | unit/verify | unproven | | positive | [`TestTunnelModeIsTheChildSADefault`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L468) | unit/verify | unproven | ### [`RFC3948-2.3-2`](#rfc3948-2.3-2) The NAT-keepalive sender MUST use a one-octet-long payload with the value 0xFF (S2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestNATKeepalive`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/keepalive_test.go#L16) | unit/verify | unproven | ### [`RFC3948-4-3`](#rfc3948-4-3) Reception of NAT-keepalive packets MUST NOT be used to detect whether a connection is live (S4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNATKeepaliveReachesNoSA`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc7296_natt_test.go#L664) | unit/verify | unproven | | negative | [`TestNATKeepaliveIsNeverDeliveredToASession`](https://github.com/ze-software/ze/blob/main/internal/component/ike/transport/keepalive_test.go#L85) | unit/verify | unproven | | positive | [`TestNATKeepaliveReachesNoSA`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc7296_natt_test.go#L655) | unit/verify | unproven | ### [`RFC3948-3.1.1-1`](#rfc3948-3.1.1-1) Tunnel mode decapsulation, depending on local policy, MUST do one of three things: check the inner source address against the policy, check it against the address assigned to the peer, or NAT the packet (S3.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAInboundPolicyUsesNegotiatedTS`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L423) | unit/verify | unproven | | positive | [`TestChildInboundPolicyDefinesTheValidInnerSourceSpace`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc3948_inner_source_policy_test.go#L28) | unit/verify | revert, verified | ### [`RFC3948-5.1-1`](#rfc3948-5.1-1) Implementors MUST devise ways of preventing two remote peers from reaching one security gateway with overlapping inner addresses (S5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC3948-5.1-1, so no unit is bound to it. ### [`RFC3948-5.2-1`](#rfc3948-5.2-1) Implementations MUST handle conflicting connections from clients behind one NAT, either by disallowing conflicting connections or by other means (S5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPolicyOwnerRefusesASecondPeerOnOneSelector`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/policy_owner_test.go#L40) | unit/verify | unproven | | positive | [`TestPolicyOwnerSeparatesDistinctSelectors`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/policy_owner_test.go#L263) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase 2, rfc3948 | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc3948.txt | | Source fingerprint | 70b3d9d30da1c716 | | Record | rfc/extraction/rfc3948.json | | Mapped sentences | 10 | | Declined as scope | 5 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, Copyright Notice, Abstract and Table of Contents. The Abstract says what the document defines and binds no speaker. | | `1` | Introduction | 3 | walked | Introduction. Scope, the shared-port rationale, the two mode-support sentences, the zero-SPI ban, the IPv6 note, the exclusion of AH and of manual keying, and the RFC 2119 key-words paragraph. Its three sites are classified below. | | `2` | Packet Formats | 0 | walked | Packet Formats. A heading with no text of its own; every sentence belongs to 2.1, 2.2 or 2.3. | | `2.1` | UDP-Encapsulated ESP Header Format | 2 | walked | UDP-Encapsulated ESP Header Format. The wire diagram, the three UDP-header bullets and the zero-SPI ban. The splitter fuses the three bullets into one site, so only the first of them has a site of its own. The other two, and the demultiplexing rule the summary reads across 2.1, 2.2 and 2.3 together, are listed below. | | `2.2` | IKE Header Format for Port 4500 | 0 | walked | IKE Header Format for Port 4500. The marker is stated in the indicative, 'A Non-ESP Marker is 4 zero-valued bytes aligning with the SPI field of an ESP packet', so the keyword scan sees no site. The obligation to prepend it is real and is listed below. The section states no checksum rule of its own and defers the checksum to RFC 3947. | | `2.3` | NAT-Keepalive Packet Format | 2 | walked | NAT-Keepalive Packet Format. The diagram, the three UDP-header bullets repeated from 2.1, the sender payload rule and the receiver's SHOULD. The SHOULD sentence carries no MUST-level keyword, so it makes no site and is listed below as RFC3948-2.3-1, which is advisory and gates nothing. | | `3` | Encapsulation and Decapsulation Procedures | 0 | walked | Encapsulation and Decapsulation Procedures. A heading with no text of its own. | | `3.1` | Auxiliary Procedures | 0 | walked | Auxiliary Procedures. A heading with no text of its own. | | `3.1.1` | Tunnel Mode Decapsulation NAT Procedure | 1 | walked | Tunnel Mode Decapsulation NAT Procedure. One MUST opening a three-way choice about the inner packet's addresses. Its site is mapped below to RFC3948-3.1.1-1: ze takes the first choice by defining the valid source address space in the inbound Security Policy it installs, and the kernel checks each decapsulated packet against it. | | `3.1.2` | Transport Mode Decapsulation NAT Procedure | 2 | walked | Transport Mode Decapsulation NAT Procedure. Two sites, both mapped. The third choice carries a MAY about skipping the TCP checksum, and the section closes with 'an implementation MAY fix any contained protocols'. Both are advisory, make no site, and are listed below as RFC3948-3.1.2-3. | | `3.2` | Transport Mode ESP Encapsulation | 0 | walked | Transport Mode ESP Encapsulation. A before-and-after diagram and three numbered steps written in the indicative (ordinary ESP, insert the UDP header, edit Total Length, Protocol and Header Checksum). No keyword, and no obligation the mapped sections do not already carry. | | `3.3` | Transport Mode ESP Decapsulation | 0 | walked | Transport Mode ESP Decapsulation. Four numbered steps in the indicative, the last of which invokes the Section 3.1.2 procedure. No keyword, and no requirement id is read from it. | | `3.4` | Tunnel Mode ESP Encapsulation | 0 | walked | Tunnel Mode ESP Encapsulation. A before-and-after diagram and three numbered steps in the indicative, the same shape as Section 3.2 with a new outer header. No keyword, and no requirement id is read from it. | | `3.5` | Tunnel Mode ESP Decapsulation | 0 | walked | Tunnel Mode ESP Decapsulation. Four numbered steps in the indicative, the last of which invokes the Section 3.1.1 procedure. No keyword, and no requirement id is read from it. | | `4` | NAT Keepalive Procedure | 1 | walked | NAT Keepalive Procedure. One MUST NOT site, mapped below. The sending rules are a MAY over N minutes and a SHOULD over M seconds, with locally configurable defaults of 5 minutes and 20 seconds, neither of which makes a site, so the two interval rows the summary reads from them are listed below: RFC3948-4-2 carries the intervals, and RFC3948-4-1 is read from the section's indicative opening. | | `5` | Security Considerations | 0 | walked | Security Considerations. A heading with no text of its own. | | `5.1` | Tunnel Mode Conflict | 1 | walked | Tunnel Mode Conflict. The overlapping-inner-address hazard at a security gateway, one MUST and one RECOMMENDED remedy. Its site is mapped below to RFC3948-5.1-1, which the summary carries as a gap: ze is the security gateway of the diagram and assigns no inner address, so nothing it holds prevents the overlap. | | `5.2` | Transport Mode Conflict | 2 | walked | Transport Mode Conflict. The same hazard for two transport-mode clients behind one NAT, with a worked policy example. Two sites, both excluded below. | | `6` | IAB Considerations | 0 | walked | IAB Considerations. One sentence handing the UNSAF questions of RFC 3424 to RFC 3715. It directs no implementation. | | `7` | Acknowledgments | 0 | skipped (acknowledgements) | Acknowledgments. | | `8` | References heading | 0 | skipped (references) | References heading. | | `8.1` | not stated | 0 | skipped (references) | Normative References: RFC 768, RFC 2119, RFC 2401, RFC 2406, RFC 2409, RFC 3947. | | `8.2` | not stated | 0 | skipped (references) | Informative References: RFC 1122, RFC 3193, RFC 3424, RFC 3715 and the IKEv2 draft. | | `A` | not stated | 1 | walked | Appendix A, Clarification of Potential NAT Multiple Client Solutions. Non-normative by its own statement: the subject is 'not a matter of wire protocol, but a matter local implementation' and the mechanisms 'do not belong in the protocol specification itself'. It lists implementation options Tr1 to Tr5 and Tn1 to Tn4. It is walked rather than skipped because its one site is classified below. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The modes this sentence mandates are defined by RFC 3193, Securing L2TP using IPsec, which the sentence cites by name. RFC 3948 states none of them, so the obligation is owed against that document rather than against this summary. Ze's L2TP component (internal/component/l2tp) is an LAC/LNS for PPP subscriber access and never runs a session inside IPsec, and no file in the tree implements RFC 3193. | L2TP/IPsec clients MUST support the modes as defined in [RFC3193]. | | `1:3` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The Introduction restates for the IKE implementation the zero-SPI ban that Section 2.1 states for the packet in its own right, because a zero SPI there is the non-ESP marker. Site 2.1:2 is the sentence RFC3948-2.1-1 maps. | An IKE implementation supporting this protocol specification MUST NOT use the ESP SPI field zero for ESP packets. | | `2.3:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Section 2.3 repeats Section 2.1's three UDP-header bullets for the keepalive packet, and its first bullet points back at Section 2.1 by name. The port rule is RFC3948-2.1-2, which site 2.1:1 maps; the checksum pair is RFC3948-2.1-5 and RFC3948-2.1-4, both recorded on section 2.1. | o the Source Port and Destination Port MUST be the same as used by UDP-ESP encapsulation of Section 2.1, o the IPv4 UDP Checksum SHOULD be transmitted as a zero value, and o receivers MUST NOT depend upon the UDP checksum being a zero value. | | `5.2:2` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The splitter cut this MUST NOT away from the sentence that completes it. The enclosing construction is 'For security guarantees, the above problematic scenario MUST NOT be allowed on servers. For best effort security, this scenario MAY be used.' The pair states an either-or the operator picks between when writing the server's security policy, not an obligation the implementation can meet or fail. | For security guarantees, the above problematic scenario MUST NOT be allowed on servers. | | `A:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | A description of what Sections 5.1 and 5.2 say, reported inside Appendix A. The same paragraph states that the subject is 'not a matter of wire protocol, but a matter local implementation' and that the mechanisms 'do not belong in the protocol specification itself', so the keyword is a report of another section rather than a directive of its own. | Sections 5.1 and 5.2 say that you MUST avoid this problem. | ## Superseded No document obsoletes RFC 3948, so its obligations are stated where they were written. --- ### Page: RFC 3954 - Cisco Systems NetFlow Services Export Version 9 https://ze-software.net/quality/rfc-compliance/rfc3954/ # RFC 3954 - Cisco Systems NetFlow Services Export Version 9 Experimental. Every requirement this repository extracted from RFC 3954, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 22.2% | 2 of 9 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 22.2% | 2 of 9 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 9 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 8 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 9 | of 21 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 4 | of 9 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 44.4% | 4 of 9 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 9 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 9 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 11.1% | 1 of 9 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 9 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 21 | | Gated MUST-level | 9 | | Not applicable, so out of scope | 4 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 8 | | Tagged units | 8 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc3954.md` | | Requirement shard | `rfc/requirements/rfc3954.md` | | RFC text | `rfc/full/rfc3954.txt` | ## Enrolment Enrolled: NetFlow Services Export Version 9 (ze as a v9 exporter): nine MUST-level requirements. Four are met: x-1 (never send a Data FlowSet before its Template) and x-8 (the sequence number is cumulative per observation domain) carry positive+negative tags; x-3 (network byte order) and x-9 (a Template ID is constant for the process lifetime) are {single-polarity: positive}. x-2 (refresh templates on both a time interval and a packet-count interval) is {gap}: ze refreshes on a configurable time interval only, with no packet-count-based interval. x-4, x-5, x-6, x-7 are {not-applicable}: they are NetFlow v9 collector requirements and ze is an exporter only. Disclosed in the docs/features/rfc-status.md RFC 3954 row. ## What the public ledger says **Status:** Experimental **What the ledger says is covered** - 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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc3954.md). **What the ledger says remains:** Template refresh is time-interval-based only, with no packet-count-based refresh interval (RFC3954-x-2 gap). ze is an exporter only; the v9 collector requirements are not applicable. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 7 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **9** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC3954-x-1`](#rfc3954-x-1), [`RFC3954-x-8`](#rfc3954-x-8) **Annotated instead of tested (7):** [`RFC3954-x-2`](#rfc3954-x-2), [`RFC3954-x-3`](#rfc3954-x-3), [`RFC3954-x-4`](#rfc3954-x-4), [`RFC3954-x-5`](#rfc3954-x-5), [`RFC3954-x-6`](#rfc3954-x-6), [`RFC3954-x-7`](#rfc3954-x-7), [`RFC3954-x-9`](#rfc3954-x-9) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC3954-x-1` | Exporter MUST NOT send a Data FlowSet without having sent the corresponding Template FlowSet in a previous or the same export packet (Template Lifecycle) | MUST | x | **positive:** `unit/verify` [`TestWriteExportPacketWithTemplate`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/encoder_test.go#L69). **negative:** `unit/verify` [`TestExporterSendsTemplateBeforeData`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/exporter_test.go#L86) | | `RFC3954-x-2` | Exporter MUST periodically refresh templates using both time-based and packet-count-based intervals, both configurable (Template Lifecycle) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze refreshes NetFlow v9 templates on a time interval only (internal/plugins/flowexport/exporter.go:192-199, config template-refresh seconds); it has no packet-count-based template-refresh interval, so the conjoined time-and-packet-count refresh requirement is only partly met | | `RFC3954-x-3` | All binary integer values MUST be coded in network byte order (big-endian) (Encoding Rules) | MUST | x | **positive:** `unit/verify` [`TestNetflow9DataFlowSet`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/data_test.go#L10). **positive:** `unit/verify` [`TestNetflow9Header`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/encoder_test.go#L10). **negative:** no negative test. **{single-polarity}:** ze only ENCODES NetFlow v9 (exporter-only, internal/plugins/flowexport/netflow9); every multi-octet field is written big-endian via binary.BigEndian and there is no decode or wrong-endianness code path to reject, so only the positive can be tested | | `RFC3954-x-4` | Collector MUST use the FlowSet ID to find the correct template for decoding (Validation) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this is a NetFlow v9 collector requirement (map a Data FlowSet ID to its template); ze is a v9 exporter only (internal/plugins/flowexport/netflow9) with no v9 decode/collect code path | | `RFC3954-x-5` | Collector MUST use the Length field to determine the position of the next FlowSet record (Encoding Rules) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** collector requirement (use FlowSet Length to find the next FlowSet); ze does not collect v9 | | `RFC3954-x-6` | Collector MUST accept padding in Data FlowSets and Options Template FlowSets (Validation) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** collector requirement (accept padding); ze does not collect v9 (its exporter does emit 4-octet padding at internal/plugins/flowexport/netflow9/data.go:38-44) | | `RFC3954-x-7` | Collector MUST override an existing template when a new definition arrives for the same Template ID (Template Lifecycle) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** collector requirement (override a template on redefinition); ze does not collect v9 | | `RFC3954-x-8` | Sequence Number MUST be a cumulative counter per observation domain (Wire Format) | MUST | x | **positive:** `unit/verify` [`TestNetflow9FlowSeqNumPerPacket`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/flow_adapter_test.go#L53). **negative:** `unit/verify` [`TestNetflow9SeqNumNotAdvancedOnSendError`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/adapter_test.go#L58) | | `RFC3954-x-9` | Template ID MUST remain constant for the life of the NetFlow process on the exporter (Template Lifecycle) | MUST | x | **positive:** `unit/verify` [`TestNetflow9FlowTemplate`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/flow_template_test.go#L8). **positive:** `unit/verify` [`TestNetflow9Template`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/template_test.go#L8). **negative:** no negative test. **{single-polarity}:** ze's NetFlow v9 template IDs are compile-time constants (CounterTemplateID=256 in internal/plugins/flowexport/netflow9/template.go, FlowTemplateID=257 and FlowTemplateID6=258 in flow_template.go) that are never reassigned for the life of the process, so there is no ID-change code path to test negatively | | `RFC3954-x-10` | Exporter SHOULD insert padding to 4-octet alignment using zeros (Encoding Rules) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-11` | Exporter SHOULD send templates at an accelerated rate after configuration changes or clock changes (Template Lifecycle) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-12` | Collector SHOULD use (exporter IP, Source ID) to separate different export streams (Wire Format) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-13` | Collector SHOULD buffer data records when the matching template has not yet been received (Validation) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-14` | Collector SHOULD use Sequence Number to detect missing packets (Wire Format) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-15` | Exporter SHOULD NOT reuse a Template ID after configuration changes until the process restarts (Template Lifecycle) | SHOULD NOT | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-16` | Export path SHOULD be a dedicated link or provisioned to handle burst rate without drops (Transport Considerations) | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-17` | Exporter MAY send template-only packets (no data) to pre-populate the collector (Template Lifecycle) | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-18` | Exporter MAY export flows prematurely due to internal constraints (memory pressure, counter wrap) (Exporter Implementation) | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-19` | Exporter MAY include multiple template records in a single Template FlowSet (Template FlowSet) | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-20` | Exporter MAY send templates and data in the same packet (Template Lifecycle) | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC3954-x-21` | Exporters MAY use reserved field type IDs for vendor-specific fields (Reserved Fields) | MAY | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC3954-x-2`](#rfc3954-x-2) Exporter MUST periodically refresh templates using both time-based and packet-count-based intervals, both configurable (Template Lifecycle) | {gap}, no test | ze refreshes NetFlow v9 templates on a time interval only (internal/plugins/flowexport/exporter.go:192-199, config template-refresh seconds); it has no packet-count-based template-refresh interval, so the conjoined time-and-packet-count refresh requirement is only partly met | | [`RFC3954-x-4`](#rfc3954-x-4) Collector MUST use the FlowSet ID to find the correct template for decoding (Validation) | no test | no test carries this requirement id; annotated {not-applicable}: this is a NetFlow v9 collector requirement (map a Data FlowSet ID to its template); ze is a v9 exporter only (internal/plugins/flowexport/netflow9) with no v9 decode/collect code path | | [`RFC3954-x-5`](#rfc3954-x-5) Collector MUST use the Length field to determine the position of the next FlowSet record (Encoding Rules) | no test | no test carries this requirement id; annotated {not-applicable}: collector requirement (use FlowSet Length to find the next FlowSet); ze does not collect v9 | | [`RFC3954-x-6`](#rfc3954-x-6) Collector MUST accept padding in Data FlowSets and Options Template FlowSets (Validation) | no test | no test carries this requirement id; annotated {not-applicable}: collector requirement (accept padding); ze does not collect v9 (its exporter does emit 4-octet padding at internal/plugins/flowexport/netflow9/data.go:38-44) | | [`RFC3954-x-7`](#rfc3954-x-7) Collector MUST override an existing template when a new definition arrives for the same Template ID (Template Lifecycle) | no test | no test carries this requirement id; annotated {not-applicable}: collector requirement (override a template on redefinition); ze does not collect v9 | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC3954-x-1`](#rfc3954-x-1) Exporter MUST NOT send a Data FlowSet without having sent the corresponding Template FlowSet in a previous or the same export packet (Template Lifecycle) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestExporterSendsTemplateBeforeData`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/exporter_test.go#L86) | unit/verify | unproven | | positive | [`TestWriteExportPacketWithTemplate`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/encoder_test.go#L69) | unit/verify | unproven | ### [`RFC3954-x-2`](#rfc3954-x-2) Exporter MUST periodically refresh templates using both time-based and packet-count-based intervals, both configurable (Template Lifecycle) Audit verdict: not audited: no reader has judged these tests No test carries RFC3954-x-2, so no unit is bound to it. ### [`RFC3954-x-3`](#rfc3954-x-3) All binary integer values MUST be coded in network byte order (big-endian) (Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestNetflow9DataFlowSet`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/data_test.go#L10) | unit/verify | unproven | | positive | [`TestNetflow9Header`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/encoder_test.go#L10) | unit/verify | unproven | ### [`RFC3954-x-4`](#rfc3954-x-4) Collector MUST use the FlowSet ID to find the correct template for decoding (Validation) Audit verdict: not audited: no reader has judged these tests No test carries RFC3954-x-4, so no unit is bound to it. ### [`RFC3954-x-5`](#rfc3954-x-5) Collector MUST use the Length field to determine the position of the next FlowSet record (Encoding Rules) Audit verdict: not audited: no reader has judged these tests No test carries RFC3954-x-5, so no unit is bound to it. ### [`RFC3954-x-6`](#rfc3954-x-6) Collector MUST accept padding in Data FlowSets and Options Template FlowSets (Validation) Audit verdict: not audited: no reader has judged these tests No test carries RFC3954-x-6, so no unit is bound to it. ### [`RFC3954-x-7`](#rfc3954-x-7) Collector MUST override an existing template when a new definition arrives for the same Template ID (Template Lifecycle) Audit verdict: not audited: no reader has judged these tests No test carries RFC3954-x-7, so no unit is bound to it. ### [`RFC3954-x-8`](#rfc3954-x-8) Sequence Number MUST be a cumulative counter per observation domain (Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNetflow9SeqNumNotAdvancedOnSendError`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/adapter_test.go#L58) | unit/verify | unproven | | positive | [`TestNetflow9FlowSeqNumPerPacket`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/flow_adapter_test.go#L53) | unit/verify | unproven | ### [`RFC3954-x-9`](#rfc3954-x-9) Template ID MUST remain constant for the life of the NetFlow process on the exporter (Template Lifecycle) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestNetflow9FlowTemplate`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/flow_template_test.go#L8) | unit/verify | unproven | | positive | [`TestNetflow9Template`](https://github.com/ze-software/ze/blob/main/internal/plugins/flowexport/netflow9/template_test.go#L8) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 3954, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 3954, so its obligations are stated where they were written. --- ### Page: RFC 4035 - Protocol Modifications for the DNS Security Extensions https://ze-software.net/quality/rfc-compliance/rfc4035/ # RFC 4035 - Protocol Modifications for the DNS Security Extensions Partial. Every requirement this repository extracted from RFC 4035, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 4.6% | 5 of 108 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 8.3% | 9 of 108 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 108 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 19 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 108 | of 158 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 91 | of 108 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 84.3% | 91 of 108 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 108 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 108 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 2.8% | 3 of 108 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 108 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 158 | | Gated MUST-level | 108 | | Not applicable, so out of scope | 91 | | Declared gaps | 3 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 19 | | Tagged units | 19 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4035.md` | | Requirement shard | `rfc/requirements/rfc4035.md` | | RFC text | `rfc/full/rfc4035.txt` | ## Enrolment Enrolled: Protocol Modifications for the DNS Security Extensions ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4035.md). **What the ledger says remains** Three MUST gaps, each annotated in [`rfc/short/rfc4035.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4035.md): the name server supports no EDNS0 message size extension -- it emits no OPT pseudo-RR and honors no requestor payload size ([`RFC4035-3-1`](#rfc4035-3-1)) -- and its UDP listener reads at most 512 octets because `dns.Server.UDPSize` stays zero ([`RFC4035-3-2`](#rfc4035-3-2), [`internal/core/dnsserver/manager.go`](https://github.com/ze-software/ze/blob/main/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`](#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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 5 | one part of the gated population | | Annotated instead of tested | 103 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **108** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (5):** [`RFC4035-3-8`](#rfc4035-3-8), [`RFC4035-3-9`](#rfc4035-3-9), [`RFC4035-3.1.4.1-1`](#rfc4035-3.1.4.1-1), [`RFC4035-4.6-3`](#rfc4035-4.6-3), [`RFC4035-4.9-1`](#rfc4035-4.9-1) **Annotated instead of tested (103):** [`RFC4035-2.1-2`](#rfc4035-2.1-2), [`RFC4035-2.1-4`](#rfc4035-2.1-4), [`RFC4035-2.1-5`](#rfc4035-2.1-5), [`RFC4035-2.2-1`](#rfc4035-2.2-1), [`RFC4035-2.2-3`](#rfc4035-2.2-3), [`RFC4035-2.2-4`](#rfc4035-2.2-4), [`RFC4035-2.2-5`](#rfc4035-2.2-5), [`RFC4035-2.2-6`](#rfc4035-2.2-6), [`RFC4035-2.2-7`](#rfc4035-2.2-7), [`RFC4035-2.2-8`](#rfc4035-2.2-8), [`RFC4035-2.3-1`](#rfc4035-2.3-1), [`RFC4035-2.3-3`](#rfc4035-2.3-3), [`RFC4035-2.3-4`](#rfc4035-2.3-4), [`RFC4035-2.3-5`](#rfc4035-2.3-5), [`RFC4035-2.3-6`](#rfc4035-2.3-6), [`RFC4035-2.3-7`](#rfc4035-2.3-7), [`RFC4035-2.4-3`](#rfc4035-2.4-3), [`RFC4035-2.4-4`](#rfc4035-2.4-4), [`RFC4035-2.5-1`](#rfc4035-2.5-1), [`RFC4035-2.5-2`](#rfc4035-2.5-2), [`RFC4035-2.6-1`](#rfc4035-2.6-1), [`RFC4035-3-1`](#rfc4035-3-1), [`RFC4035-3-2`](#rfc4035-3-2), [`RFC4035-3-5`](#rfc4035-3-5), [`RFC4035-3-6`](#rfc4035-3-6), [`RFC4035-3.1-1`](#rfc4035-3.1-1), [`RFC4035-3.1-2`](#rfc4035-3.1-2), [`RFC4035-3.1-3`](#rfc4035-3.1-3), [`RFC4035-3.1.1-3`](#rfc4035-3.1.1-3), [`RFC4035-3.1.1-4`](#rfc4035-3.1.1-4), [`RFC4035-3.1.1-5`](#rfc4035-3.1.1-5), [`RFC4035-3.1.1-6`](#rfc4035-3.1.1-6), [`RFC4035-3.1.1-8`](#rfc4035-3.1.1-8), [`RFC4035-3.1.2-3`](#rfc4035-3.1.2-3), [`RFC4035-3.1.2-4`](#rfc4035-3.1.2-4), [`RFC4035-3.1.3-1`](#rfc4035-3.1.3-1), [`RFC4035-3.1.3.1-1`](#rfc4035-3.1.3.1-1), [`RFC4035-3.1.3.2-1`](#rfc4035-3.1.3.2-1), [`RFC4035-3.1.3.3-1`](#rfc4035-3.1.3.3-1), [`RFC4035-3.1.3.3-2`](#rfc4035-3.1.3.3-2), [`RFC4035-3.1.3.4-1`](#rfc4035-3.1.3.4-1), [`RFC4035-3.1.4-1`](#rfc4035-3.1.4-1), [`RFC4035-3.1.4-2`](#rfc4035-3.1.4-2), [`RFC4035-3.1.4-3`](#rfc4035-3.1.4-3), [`RFC4035-3.1.5-2`](#rfc4035-3.1.5-2), [`RFC4035-3.1.5-3`](#rfc4035-3.1.5-3), [`RFC4035-3.1.5-4`](#rfc4035-3.1.5-4), [`RFC4035-3.1.5-5`](#rfc4035-3.1.5-5), [`RFC4035-3.1.5-6`](#rfc4035-3.1.5-6), [`RFC4035-3.1.5-7`](#rfc4035-3.1.5-7), [`RFC4035-3.1.6-2`](#rfc4035-3.1.6-2), [`RFC4035-3.1.6-4`](#rfc4035-3.1.6-4), [`RFC4035-3.1.6-5`](#rfc4035-3.1.6-5), [`RFC4035-3.1.6-6`](#rfc4035-3.1.6-6), [`RFC4035-3.2.1-1`](#rfc4035-3.2.1-1), [`RFC4035-3.2.1-2`](#rfc4035-3.2.1-2), [`RFC4035-3.2.1-3`](#rfc4035-3.2.1-3), [`RFC4035-3.2.2-1`](#rfc4035-3.2.2-1), [`RFC4035-3.2.2-4`](#rfc4035-3.2.2-4), [`RFC4035-3.2.3-2`](#rfc4035-3.2.3-2), [`RFC4035-4.1-1`](#rfc4035-4.1-1), [`RFC4035-4.1-2`](#rfc4035-4.1-2), [`RFC4035-4.1-4`](#rfc4035-4.1-4), [`RFC4035-4.1-5`](#rfc4035-4.1-5), [`RFC4035-4.2-1`](#rfc4035-4.2-1), [`RFC4035-4.2-3`](#rfc4035-4.2-3), [`RFC4035-4.2-5`](#rfc4035-4.2-5), [`RFC4035-4.2-6`](#rfc4035-4.2-6), [`RFC4035-4.3-1`](#rfc4035-4.3-1), [`RFC4035-4.4-1`](#rfc4035-4.4-1), [`RFC4035-4.6-2`](#rfc4035-4.6-2), [`RFC4035-4.7-2`](#rfc4035-4.7-2), [`RFC4035-4.7-3`](#rfc4035-4.7-3), [`RFC4035-4.7-7`](#rfc4035-4.7-7), [`RFC4035-4.8-1`](#rfc4035-4.8-1), [`RFC4035-4.9.1-2`](#rfc4035-4.9.1-2), [`RFC4035-4.9.3-2`](#rfc4035-4.9.3-2), [`RFC4035-5-1`](#rfc4035-5-1), [`RFC4035-5-2`](#rfc4035-5-2), [`RFC4035-5-3`](#rfc4035-5-3), [`RFC4035-5.2-1`](#rfc4035-5.2-1), [`RFC4035-5.2-3`](#rfc4035-5.2-3), [`RFC4035-5.3.1-1`](#rfc4035-5.3.1-1), [`RFC4035-5.3.1-2`](#rfc4035-5.3.1-2), [`RFC4035-5.3.1-3`](#rfc4035-5.3.1-3), [`RFC4035-5.3.1-4`](#rfc4035-5.3.1-4), [`RFC4035-5.3.1-5`](#rfc4035-5.3.1-5), [`RFC4035-5.3.1-6`](#rfc4035-5.3.1-6), [`RFC4035-5.3.1-7`](#rfc4035-5.3.1-7), [`RFC4035-5.3.1-8`](#rfc4035-5.3.1-8), [`RFC4035-5.3.1-9`](#rfc4035-5.3.1-9), [`RFC4035-5.3.1-10`](#rfc4035-5.3.1-10), [`RFC4035-5.3.2-1`](#rfc4035-5.3.2-1), [`RFC4035-5.3.2-2`](#rfc4035-5.3.2-2), [`RFC4035-5.3.2-3`](#rfc4035-5.3.2-3), [`RFC4035-5.3.3-1`](#rfc4035-5.3.3-1), [`RFC4035-5.3.3-2`](#rfc4035-5.3.3-2), [`RFC4035-5.4-1`](#rfc4035-5.4-1), [`RFC4035-5.4-2`](#rfc4035-5.4-2), [`RFC4035-5.4-3`](#rfc4035-5.4-3), [`RFC4035-5.4-4`](#rfc4035-5.4-4), [`RFC4035-5.5-2`](#rfc4035-5.5-2), [`RFC4035-5.5-3`](#rfc4035-5.5-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4035-2.1-2` | A zone key DNSKEY RR MUST have the Zone Key bit of the flags RDATA field set (§2.1) | MUST | 2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.1-4` | Public keys stored in DNSKEY RRs that are not marked as zone keys MUST NOT be used to verify RRSIGs (§2.1) | MUST NOT | 2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.1-5` | For a signed zone usable other than as an island of security, the zone apex MUST contain at least one DNSKEY RR to act as a secure entry point (§2.1) | MUST | 2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.2-1` | For each authoritative RRset in a signed zone there MUST be at least one RRSIG record whose owner, class, Type Covered, Original TTL, TTL, Labels, and Signer's Name match the RRset and identify an apex zone key (§2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.2-3` | An RRSIG RR itself MUST NOT be signed (§2.2) | MUST NOT | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.2-4` | The NS RRset that appears at the zone apex name MUST be signed (§2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.2-5` | The NS RRsets that appear at delegation points MUST NOT be signed (§2.2) | MUST NOT | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.2-6` | Glue address RRsets associated with delegations MUST NOT be signed (§2.2) | MUST NOT | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.2-7` | There MUST be an RRSIG for each RRset using at least one DNSKEY of each algorithm in the zone apex DNSKEY RRset (§2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.2-8` | The apex DNSKEY RRset MUST be signed by each algorithm appearing in the DS RRset at the delegating parent, if any (§2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.3-1` | Each owner name in the zone that has authoritative data or a delegation point NS RRset MUST have an NSEC resource record (§2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.3-3` | An NSEC record and its associated RRSIG RRset MUST NOT be the only RRset at any particular owner name (§2.3) | MUST NOT | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.3-4` | The signing process MUST NOT create NSEC or RRSIG RRs for owner name nodes that were not the owner name of any RRset before the zone was signed (§2.3) | MUST NOT | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.3-5` | The type bitmap of every NSEC RR MUST indicate the presence of both the NSEC record itself and its corresponding RRSIG record (§2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.3-6` | In the NSEC bitmap at a delegation point, bits for the delegation NS RRset and any RRsets for which the parent zone has authoritative data MUST be set (§2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.3-7` | In the NSEC bitmap at a delegation point, bits for any non-NS RRset for which the parent is not authoritative MUST be clear (§2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.4-3` | All DS RRsets in a zone MUST be signed (§2.4) | MUST | 2.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.4-4` | DS RRsets MUST NOT appear at a zone's apex (§2.4) | MUST NOT | 2.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.5-1` | If a CNAME RRset is present at a name in a signed zone, appropriate RRSIG and NSEC RRsets are REQUIRED at that name (§2.5) | REQUIRED | 2.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.5-2` | Types other than CNAME, its RRSIG and NSEC, and a KEY RRset for secure dynamic update MUST NOT be present at a CNAME name (§2.5) | MUST NOT | 2.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-2.6-1` | At the parental side of a zone cut, NSEC RRs are REQUIRED at the owner name (§2.6) | REQUIRED | 2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | `RFC4035-3-1` | A security-aware name server MUST support the EDNS0 message size extension (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's authoritative servers give the EDNS0 message size extension no support -- the only SetEdns0 producer in ze is the stub resolver (internal/component/resolve/dns/resolver.go:261); the server path reads an OPT only for the client-subnet address (internal/core/dnsserver/client.go:23) and writes a reply built by msg.SetReply with no OPT pseudo-RR and no requestor-payload-size handling (internal/core/dnsserver/handler.go:55) | | `RFC4035-3-2` | A security-aware name server MUST support a message size of at least 1220 octets (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the UDP listener accepts at most 512 octets -- dns.Server is constructed with UDPSize left zero (internal/core/dnsserver/manager.go:165), which miekg defaults to MinMsgSize 512 (vendor/github.com/miekg/dns/server.go:287), so a query above 512 octets is never read whole and no reply advertises a larger size | | `RFC4035-3-5` | A name server receiving a query without the EDNS OPT pseudo-RR or with the DO bit clear MUST treat the RRSIG, DNSKEY, and NSEC RRs as it would any other RRset (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3-6` | Such a name server MUST NOT perform any of the DNSSEC additional processing (§3) | MUST NOT | 3 | **positive:** `unit/verify` [`TestRFC4035_NoDNSSECAdditionalProcessing`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L164). **negative:** no negative test. **{single-polarity}:** ze performs no DNSSEC additional processing for any query -- answerQuestions builds A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) and never consults the DO bit, so no processing path exists to drive negatively | | `RFC4035-3-8` | A security-aware name server MUST copy the CD bit from a query into the corresponding response (§3) | MUST | 3 | **positive:** `unit/verify` [`TestRFC4035_CDCopiedADIgnored`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc4035_test.go#L51). **negative:** `unit/verify` [`TestRFC4035_CDCopiedADIgnored`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc4035_test.go#L56) | | `RFC4035-3-9` | A security-aware name server MUST ignore the setting of the AD bit in queries (§3) | MUST | 3 | **positive:** `unit/verify` [`TestRFC4035_CDCopiedADIgnored`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc4035_test.go#L62). **negative:** `unit/verify` [`TestRFC4035_CDCopiedADIgnored`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc4035_test.go#L69) | | `RFC4035-3.1-1` | Upon a DO-set query to a signed zone, RRSIG RRs that can be used to authenticate the response MUST be included (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1-2` | NSEC RRs that provide authenticated denial of existence MUST be included in the response automatically (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1-3` | Either a DS RRset or an NSEC RR proving that no DS RRs exist MUST be included in referrals automatically (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.1-3` | When placing a signed RRset in the Answer section, the name server MUST also place its RRSIG RRs in the Answer section (§3.1.1) | MUST | 3.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.1-4` | If space does not permit inclusion of the RRSIG RRs that must accompany a signed RRset, or of a mandatory NSEC or DS RRset and its RRSIGs, the name server MUST set the TC bit (§3.1.1) | MUST | 3.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.1-5` | When placing a signed RRset in the Authority section, the name server MUST also place its RRSIG RRs in the Authority section (§3.1.1) | MUST | 3.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.1-6` | When placing a signed RRset in the Additional section, the name server MUST also place its RRSIG RRs in the Additional section (§3.1.1) | MUST | 3.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.1-8` | The name server MUST NOT set the TC bit solely because RRSIG RRs did not fit in the Additional section (§3.1.1) | MUST NOT | 3.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.2-3` | If there is not enough space for the apex DNSKEY RRset and its RRSIGs, the name server MUST omit them (§3.1.2) | MUST | 3.1.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.2-4` | The name server MUST NOT set the TC bit solely because the apex DNSKEY and RRSIG RRs did not fit (§3.1.2) | MUST NOT | 3.1.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.3-1` | When responding to a DO-set query, the name server MUST include NSEC RRs in the No Data, Name Error, Wildcard Answer, and Wildcard No Data cases (§3.1.3) | MUST | 3.1.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.3.1-1` | For a No Data response, the name server MUST include the NSEC RR for the queried name and its RRSIGs in the Authority section (§3.1.3.1) | MUST | 3.1.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.3.2-1` | For a Name Error response, the name server MUST include, with their RRSIGs, an NSEC RR proving no exact match and an NSEC RR proving no wildcard match (§3.1.3.2) | MUST | 3.1.3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.3.3-1` | For a Wildcard Answer response, the name server MUST include the wildcard-expanded answer and its wildcard-expanded RRSIG RRs in the Answer section (§3.1.3.3) | MUST | 3.1.3.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.3.3-2` | For a Wildcard Answer response, the name server MUST include in the Authority section an NSEC RR and its RRSIGs proving that no closer match exists (§3.1.3.3) | MUST | 3.1.3.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.3.4-1` | For a Wildcard No Data response, the name server MUST include, with their RRSIGs, an NSEC RR proving no matching type at the wildcard owner name and an NSEC RR proving no closer match (§3.1.3.4) | MUST | 3.1.3.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.4-1` | If a DS RRset is present at the delegation point, the name server MUST return the DS RRset and its RRSIGs in the Authority section with the NS RRset (§3.1.4) | MUST | 3.1.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.4-2` | If no DS RRset is present, the name server MUST return the NSEC RR proving the DS RRset is absent and its RRSIGs with the NS RRset (§3.1.4) | MUST | 3.1.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.4-3` | The name server MUST place the NS RRset before the NSEC RRset and its RRSIGs (§3.1.4) | MUST | 3.1.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | `RFC4035-3.1.4.1-1` | When authoritative for the child zone but not the parent and not offering recursion, on a DS query at the zone cut the name server MUST return an authoritative no-data response (§3.1.4.1) | MUST | 3.1.4.1 | **positive:** `unit/verify` [`TestRFC4035_DSQueryIsAuthoritativeNoData`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L85). **negative:** `unit/verify` [`TestRFC4035_DSQueryIsAuthoritativeNoData`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L112) | | `RFC4035-3.1.5-2` | A name server performing its own zone validation MUST NOT selectively reject some RRs and accept others (§3.1.5) | MUST NOT | 3.1.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | `RFC4035-3.1.5-3` | The DS RRset MUST be included in zone transfers of the parent zone in which it is authoritative data (§3.1.5) | MUST | 3.1.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | `RFC4035-3.1.5-4` | NSEC RRs MUST be included in zone transfers of the zone in which they are authoritative data (§3.1.5) | MUST | 3.1.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | `RFC4035-3.1.5-5` | The parental NSEC RR at a zone cut MUST be included in zone transfers of the parent zone (§3.1.5) | MUST | 3.1.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | `RFC4035-3.1.5-6` | The NSEC at the zone apex of the child zone MUST be included in zone transfers of the child zone (§3.1.5) | MUST | 3.1.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | `RFC4035-3.1.5-7` | RRSIG RRs MUST be included in zone transfers of the zone in which they are authoritative data (§3.1.5) | MUST | 3.1.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | `RFC4035-3.1.6-2` | A security-aware name server MUST NOT set the AD bit in a response unless it considers all RRsets in the Answer and Authority sections to be authentic (§3.1.6) | MUST NOT | 3.1.6 | **positive:** `unit/verify` [`TestRFC4035_NoDNSSECAdditionalProcessing`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L177). **negative:** no negative test. **{single-polarity}:** no ze code sets AuthenticatedData on a reply -- the single non-test AuthenticatedData reference reads the upstream's bit in the stub resolver (internal/component/resolve/dns/resolver.go:278) -- so an AD-asserting response cannot be produced to reject | | `RFC4035-3.1.6-4` | The name server MUST NOT treat authoritative-zone data as authentic unless it obtained the zone via secure means (§3.1.6) | MUST NOT | 3.1.6 | **positive:** `unit/verify` [`TestRFC4035_NoDNSSECAdditionalProcessing`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L179). **negative:** no negative test. **{single-polarity}:** ze takes its zone data from the local running configuration and still serves it with AD clear, because no reply-building path sets AuthenticatedData (internal/core/dnsserver/handler.go:55, internal/plugins/geodns/server.go:168); there is no AD-setting path to drive negatively | | `RFC4035-3.1.6-5` | The name server MUST NOT treat authoritative-zone data as authentic unless this behavior has been configured explicitly (§3.1.6) | MUST NOT | 3.1.6 | **positive:** `unit/verify` [`TestRFC4035_NoDNSSECAdditionalProcessing`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L182). **negative:** no negative test. **{single-polarity}:** ze has no configuration leaf that marks local zone data authentic, so the AD bit stays clear whatever the operator configures (the geodns YANG carries no such leaf and no server path writes AuthenticatedData, internal/plugins/geodns/server.go:168) | | `RFC4035-3.1.6-6` | A security-aware name server that supports recursion MUST follow the recursive-server CD and AD bit rules for data obtained via recursion (§3.1.6) | MUST | 3.1.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-3.2.1-1` | The resolver side of a security-aware recursive name server MUST set the DO bit when sending requests (§3.2.1) | MUST | 3.2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-3.2.1-2` | If the DO bit in an initiating query is not set, the name server side MUST strip any authenticating DNSSEC RRs from the response (§3.2.1) | MUST | 3.2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-3.2.1-3` | The name server side MUST NOT strip any DNSSEC RR types that the initiating query explicitly requested (§3.2.1) | MUST NOT | 3.2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-3.2.2-1` | The name server side MUST pass the state of the CD bit to the resolver side along with the initiating query (§3.2.2) | MUST | 3.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-3.2.2-4` | If the CD bit is not set and the query matches a BAD cache entry, the name server side MUST return RCODE 2 (server failure) (§3.2.2) | MUST | 3.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-3.2.3-2` | The resolver side MUST determine whether the RRs are authentic by following the RFC's authentication procedure (§3.2.3) | MUST | 3.2.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-4.1-1` | A security-aware resolver MUST include an EDNS OPT pseudo-RR with the DO bit set when sending queries (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC4035_QueryCarriesEDNS0DOAndClearAD`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L60). **negative:** no negative test. **{single-polarity}:** the DO bit is emitted rather than parsed -- SetEdns0(4096, validating) sets it whenever dnssec-validation is permissive or strict (internal/component/resolve/dns/resolver.go:261) -- so there is no non-conformant input to reject; off mode is a plain non-security-aware stub, which this requirement does not govern | | `RFC4035-4.1-2` | A security-aware resolver MUST support a message size of at least 1220 octets (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC4035_LargeUDPResponseAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L116). **negative:** no negative test. **{single-polarity}:** the resolver advertises 4096 octets and miekg sizes the receive buffer from that advertisement (vendor/github.com/miekg/dns/client.go:201), so a response above 1220 octets arrives whole; a message-size floor has no non-conformant input to reject | | `RFC4035-4.1-4` | A security-aware resolver MUST use the sender's UDP payload size field in the EDNS OPT pseudo-RR to advertise the message size it will accept (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC4035_QueryCarriesEDNS0DOAndClearAD`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L70). **negative:** no negative test. **{single-polarity}:** the sender's UDP payload size field is emitted, not parsed -- SetEdns0 writes 4096 on every query (internal/component/resolve/dns/resolver.go:261) -- so no malformed input exists to reject | | `RFC4035-4.1-5` | A security-aware resolver's IP layer MUST handle fragmented UDP packets correctly whether received via IPv4 or IPv6 (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** UDP fragment reassembly belongs to the host IP stack, which ze neither implements nor bypasses -- the resolver exchanges datagrams through the ordinary miekg client socket (internal/component/resolve/dns/resolver.go:263) and grep -rniE 'fragment\|reassembl' --include=*.go internal/component/resolve internal/core/dnsserver finds no producer | | `RFC4035-4.2-1` | A security-aware resolver MUST support the signature verification mechanisms the RFC describes (§4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-4.2-3` | A resolver's signature verification support MUST include verification of wildcard owner names (§4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-4.2-5` | When retrieving missing NSEC RRs on the parental side of a zone cut, an iterative-mode resolver MUST query the parent zone name servers, not the child (§4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-4.2-6` | When retrieving a missing DS, an iterative-mode resolver MUST query the parent zone name servers, not the child (§4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-4.3-1` | A security-aware resolver MUST be able to determine whether it should expect a particular RRset to be signed, distinguishing Secure, Insecure, Bogus, and Indeterminate (§4.3) | MUST | 4.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-4.4-1` | A security-aware resolver MUST be capable of being configured with at least one trusted public key or DS RR (§4.4) | MUST | 4.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-4.6-2` | A security-aware resolver MUST clear the AD bit when composing query messages (§4.6) | MUST | 4.6 | **positive:** `unit/verify` [`TestRFC4035_QueryCarriesEDNS0DOAndClearAD`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L75). **negative:** no negative test. **{single-polarity}:** the AD bit of an outgoing query is emitted, not parsed -- the query is composed by new(mdns.Msg) plus SetQuestion (internal/component/resolve/dns/resolver.go:255) and no ze code sets AuthenticatedData on a query -- so no negative input exists | | `RFC4035-4.6-3` | A resolver MUST disregard the meaning of the CD and AD bits in a response unless it was obtained over a secure channel or the resolver was configured to trust them (§4.6) | MUST | 4.6 | **positive:** `unit/verify` [`TestRFC4035_ResponseADBitDisregarded`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L148). **negative:** `unit/verify` [`TestRFC4035_ResponseADBitDisregarded`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L159) | | `RFC4035-4.7-2` | A resolver that implements a BAD cache MUST take steps to prevent the cache being used as a denial-of-service amplifier (§4.7) | MUST | 4.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze keeps no BAD cache: Resolve caches only a non-empty successful answer (internal/component/resolve/dns/resolver.go:192) and a SERVFAIL yields an uncached empty result (internal/component/resolve/dns/resolver.go:285) | | `RFC4035-4.7-3` | Since RRsets that fail to validate lack trustworthy TTLs, the implementation MUST assign a TTL (§4.7) | MUST | 4.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze keeps no BAD cache: Resolve caches only a non-empty successful answer (internal/component/resolve/dns/resolver.go:192) and a SERVFAIL yields an uncached empty result (internal/component/resolve/dns/resolver.go:285) | | `RFC4035-4.7-7` | A resolver MUST NOT return RRsets from the BAD cache unless it is not required to validate their signatures (§4.7) | MUST NOT | 4.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze keeps no BAD cache: Resolve caches only a non-empty successful answer (internal/component/resolve/dns/resolver.go:192) and a SERVFAIL yields an uncached empty result (internal/component/resolve/dns/resolver.go:285) | | `RFC4035-4.8-1` | A validating resolver MUST treat the signature of a valid signed DNAME RR as also covering unsigned CNAME RRs synthesizable from it, at least by not rejecting the message solely for containing them (§4.8) | MUST | 4.8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-4.9-1` | A security-aware stub resolver MUST support the DNSSEC RR types, at least by not mishandling responses that contain them (§4.9) | MUST | 4.9 | **positive:** `unit/verify` [`TestRFC4035_StubHandlesDNSSECRRTypes`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L208). **negative:** `unit/verify` [`TestRFC4035_StubHandlesDNSSECRRTypes`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L219) | | `RFC4035-4.9.1-2` | A validating security-aware stub resolver MUST set the DO bit (§4.9.1) | MUST | 4.9.1 | **positive:** `unit/verify` [`TestRFC4035_QueryCarriesEDNS0DOAndClearAD`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L62). **negative:** no negative test. **{single-polarity}:** ze's stub sets the DO bit under permissive and strict (internal/component/resolve/dns/resolver.go:261) and leaves the signature check to the upstream; the bit is emitted, not parsed, so there is no non-conformant input to reject | | `RFC4035-4.9.3-2` | A security-aware stub resolver MUST NOT place any reliance on signature validation performed on its behalf except when it obtained the data from a trusted recursive name server over a secure channel (§4.9.3) | MUST NOT | 4.9.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's stub rests a security decision on validation performed for it over an unauthenticated channel -- strict mode rejects an answer solely because the configured upstream returned SERVFAIL (internal/component/resolve/dns/resolver.go:103-106) while the client speaks plain UDP with no TLS, TSIG, or other authentication of that server (internal/component/resolve/dns/resolver.go:81) | | `RFC4035-5-1` | To authenticate an apex DNSKEY RRset with an initial key, the resolver MUST verify that the initial DNSKEY RR appears in the apex DNSKEY RRset and has the Zone Key Flag set (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5-2` | To authenticate an apex DNSKEY RRset with an initial key, the resolver MUST verify that some RRSIG RR covers the apex DNSKEY RRset and that it together with the initial DNSKEY authenticates the RRset (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5-3` | The absence of DNSSEC data in a response MUST NOT by itself be taken as an indication that no authentication information exists (§5) | MUST NOT | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.2-1` | A security-aware resolver MUST query the parent zone name servers for the DS RRset if a referral includes neither a DS RRset nor an NSEC RRset proving the DS RRset does not exist (§5.2) | MUST | 5.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.2-3` | A security-aware resolver MUST use the parent NSEC RR when attempting to prove that a DS RRset does not exist (§5.2) | MUST | 5.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-1` | The RRSIG RR and the RRset MUST have the same owner name and the same class (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-2` | The RRSIG RR's Signer's Name field MUST be the name of the zone that contains the RRset (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-3` | The RRSIG RR's Type Covered field MUST equal the RRset's type (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-4` | The number of labels in the RRset owner name MUST be greater than or equal to the value in the RRSIG RR's Labels field (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-5` | The validator's notion of the current time MUST be less than or equal to the RRSIG RR's Expiration field (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-6` | The validator's notion of the current time MUST be greater than or equal to the RRSIG RR's Inception field (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-7` | The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST match the owner name, algorithm, and key tag of some DNSKEY RR in the zone's apex DNSKEY RRset (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-8` | The matching DNSKEY RR MUST be present in the zone's apex DNSKEY RRset (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-9` | The matching DNSKEY RR MUST have the Zone Flag bit set (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.1-10` | If more than one DNSKEY RR matches, the validator MUST try each until the signature validates or the matching keys are exhausted (§5.3.1) | MUST | 5.3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.2-1` | If the RRSIG Labels field is greater than the RRset's label count, the RRSIG did not pass validation and MUST NOT be used to authenticate the RRset (§5.3.2) | MUST NOT | 5.3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.2-2` | When reconstructing the parent-zone NSEC RRset at a delegation, its NSEC RRs MUST NOT be combined with NSEC RRs from the child zone (§5.3.2) | MUST NOT | 5.3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.2-3` | When reconstructing the child-apex NSEC RRset, its NSEC RRs MUST NOT be combined with NSEC RRs from the parent zone (§5.3.2) | MUST NOT | 5.3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.3-1` | When the RRSIG Labels field does not equal the owner name's label count, the resolver MUST verify that wildcard expansion was applied properly before considering the RRset authentic (§5.3.3) | MUST | 5.3.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.3.3-2` | On accepting an RRset as authentic, the validator MUST set the RRSIG RR and each RR's TTL to no greater than the minimum of the RRset TTL, the RRSIG TTL, the RRSIG Original TTL, and the time until the RRSIG expires (§5.3.3) | MUST | 5.3.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.4-1` | A security-aware resolver MUST authenticate the NSEC RRsets that comprise a denial-of-existence proof (§5.4) | MUST | 5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.4-2` | If the complete set of necessary NSEC RRsets is not present in a response, the resolver MUST resend the query to obtain them (§5.4) | MUST | 5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.4-3` | The resolver MUST bound the work it puts into answering any particular query (§5.4) | MUST | 5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.4-4` | A validator MUST ignore the settings of the NSEC and RRSIG bits in an NSEC RR (§5.4) | MUST | 5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | `RFC4035-5.5-2` | When validation was done to service a recursive query, the name server MUST return RCODE 2 to the originating client (§5.5) | MUST | 5.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-5.5-3` | The name server MUST return the full response if and only if the original query had the CD bit set (§5.5) | MUST | 5.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | `RFC4035-2.1-1` | For each private key used to create RRSIGs in a zone, the zone SHOULD include a zone DNSKEY RR containing the corresponding public key (§2.1) | SHOULD | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-2.3-2` | The TTL value for any NSEC RR SHOULD be the same as the minimum TTL value field in the zone SOA RR (§2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-2.4-1` | A DS RRset SHOULD be present at a delegation point when the child zone is signed (§2.4) | SHOULD | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-2.4-5` | A DS RR SHOULD point to a DNSKEY RR that is present in the child's apex DNSKEY RRset (§2.4) | SHOULD | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-2.4-6` | The child's apex DNSKEY RRset SHOULD be signed by the private key corresponding to the DS-referenced DNSKEY (§2.4) | SHOULD | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-2.4-7` | The TTL of a DS RRset SHOULD match the TTL of the delegating NS RRset (§2.4) | SHOULD | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3-3` | A security-aware name server SHOULD support a message size of 4000 octets (§3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3-4` | A security-aware name server SHOULD ensure UDP datagrams it transmits over IPv6 are fragmented at the minimum IPv6 MTU when necessary, unless the path MTU is known (§3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3-10` | A name server that synthesizes CNAME RRs from DNAME RRs SHOULD NOT generate signatures for the synthesized CNAME RRs (§3) | SHOULD NOT | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.1-1` | When responding to a DO-set query, the name server SHOULD attempt to send RRSIG RRs a resolver can use to authenticate the response (§3.1.1) | SHOULD | 3.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.1-2` | The name server SHOULD make every attempt to keep an RRset and its associated RRSIGs together in a response (§3.1.1) | SHOULD | 3.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.2-2` | The name server SHOULD NOT include the apex DNSKEY RRset unless there is enough space for both it and its associated RRSIGs (§3.1.2) | SHOULD NOT | 3.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.3.2-2` | If a single NSEC RR proves both required points, the name server SHOULD include that NSEC RR and its RRSIGs only once in the Authority section (§3.1.3.2) | SHOULD | 3.1.3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.3.4-2` | If a single NSEC RR proves both required points, the name server SHOULD include that NSEC RR and its RRSIGs only once in the Authority section (§3.1.3.4) | SHOULD | 3.1.3.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.6-1` | A security-aware name server SHOULD clear the CD bit when composing an authoritative response (§3.1.6) | SHOULD | 3.1.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.2.2-2` | When the CD bit is set, the recursive name server SHOULD, if possible, return the requested data even if its local policy would reject the records (§3.2.2) | SHOULD | 3.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.2.2-3` | If the CD bit is set and the query matches a BAD cache entry, the name server side SHOULD return the data from the BAD cache (§3.2.2) | SHOULD | 3.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.2.3-1` | The name server side SHOULD set the AD bit if and only if the resolver side considers all Answer RRsets and any relevant negative Authority RRs authentic (§3.2.3) | SHOULD | 3.2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.1-3` | A security-aware resolver SHOULD support a message size of 4000 octets (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.2-2` | A security-aware resolver SHOULD apply the signature verification mechanisms to every received response, except the enumerated cases (§4.2) | SHOULD | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.4-2` | A security-aware resolver SHOULD be capable of being configured with multiple trusted public keys or DS RRs (§4.4) | SHOULD | 4.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.4-3` | A security-aware resolver SHOULD have a reasonably robust mechanism for obtaining trust anchor keys when it boots (§4.4) | SHOULD | 4.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.5-1` | A security-aware resolver SHOULD cache each response as a single atomic entry containing the entire answer and its associated DNSSEC RRs (§4.5) | SHOULD | 4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.5-2` | The resolver SHOULD discard the entire atomic cache entry when any RR contained in it expires (§4.5) | SHOULD | 4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.7-4` | The TTL assigned to a failed-validation RRset SHOULD be small, to limit the effect of caching an attack's results (§4.7) | SHOULD | 4.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.7-5` | Resolvers SHOULD track queries that result in validation failures (§4.7) | SHOULD | 4.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.7-6` | Resolvers SHOULD answer from the BAD cache only after the failure count for the query exceeds a threshold value (§4.7) | SHOULD | 4.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.9.2-1` | A non-validating security-aware stub resolver SHOULD NOT set the CD bit unless requested by the application layer (§4.9.2) | SHOULD NOT | 4.9.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.9.2-2` | A validating security-aware stub resolver SHOULD set the CD bit (§4.9.2) | SHOULD | 4.9.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.9.3-3` | A validating security-aware stub resolver SHOULD NOT examine the setting of the AD bit (§4.9.3) | SHOULD NOT | 4.9.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-5-4` | A resolver SHOULD expect authentication information from signed zones (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-5-5` | A resolver SHOULD believe a zone is signed if it is configured with the zone's public key, or the parent is signed and the delegation contains a DS RRset (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-5.1-1` | If a validator cannot obtain an initial authenticated key for an island of security, it SHOULD operate as if the island's zones are unsigned (§5.1) | SHOULD | 5.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-5.2-4` | If the resolver supports none of the algorithms in an authenticated DS RRset, it SHOULD treat the child zone as if it were unsigned (§5.2) | SHOULD | 5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-5.5-1` | If none of the RRSIGs can be validated, the response SHOULD be considered BAD (§5.5) | SHOULD | 5.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-2.1-3` | Public keys associated with other DNS operations MAY be stored in DNSKEY RRs that are not marked as zone keys (§2.1) | MAY | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-2.2-2` | An RRset MAY have multiple RRSIG RRs associated with it (§2.2) | MAY | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-2.4-2` | A DS RRset MAY contain multiple records, each referencing a public key in the child zone (§2.4) | MAY | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3-7` | For explicit security-RR-type queries matching more than one served zone, as long as responses stay self-consistent, the name server MAY return one of the enumerated response forms (§3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.1-7` | When both a signed RRset and its RRSIGs will not fit in the Additional section, the name server MAY retain the RRset and drop the RRSIG RRs (§3.1.1) | MAY | 3.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.2-1` | When a DO-set query requests the SOA or NS RRs at a signed zone's apex, the name server MAY return the apex DNSKEY RRset in the Additional section (§3.1.2) | MAY | 3.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.1.5-1` | An authoritative name server MAY reject an entire zone transfer if the zone fails to meet the signing requirements (§3.1.5) | MAY | 3.1.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-3.2.3-3` | For backward compatibility, a recursive name server MAY set the AD bit when a response includes unsigned CNAME RRs demonstrably synthesizable from an authentic DNAME RR also in the response (§3.2.3) | MAY | 3.2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.2-4` | Security-aware resolvers MAY query for missing security RRs in an attempt to perform validation (§4.2) | MAY | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.6-1` | A security-aware resolver MAY set a query's CD bit to indicate that it takes responsibility for authentication (§4.6) | MAY | 4.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.7-1` | Security-aware resolvers MAY cache data with invalid signatures, subject to restrictions (§4.7) | MAY | 4.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.8-2` | A resolver MAY retain synthesized CNAME RRs in its cache or in the answers it hands back (§4.8) | MAY | 4.8 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.9.1-1` | A non-validating security-aware stub resolver MAY include the DNSSEC RRs returned by a recursive name server in the data it hands back to the application (§4.9.1) | MAY | 4.9.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-4.9.3-1` | A non-validating security-aware stub resolver MAY examine the AD bit to see whether the recursive name server claims to have verified the data (§4.9.3) | MAY | 4.9.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4035-5.2-2` | If an authenticated NSEC proves that no DS exists, an initial DNSKEY or DS RR for the child zone or a delegation below it MAY be used to re-establish an authentication path (§5.2) | MAY | 5.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4035-2.1-2`](#rfc4035-2.1-2) A zone key DNSKEY RR MUST have the Zone Key bit of the flags RDATA field set (§2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.1-4`](#rfc4035-2.1-4) Public keys stored in DNSKEY RRs that are not marked as zone keys MUST NOT be used to verify RRSIGs (§2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.1-5`](#rfc4035-2.1-5) For a signed zone usable other than as an island of security, the zone apex MUST contain at least one DNSKEY RR to act as a secure entry point (§2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.2-1`](#rfc4035-2.2-1) For each authoritative RRset in a signed zone there MUST be at least one RRSIG record whose owner, class, Type Covered, Original TTL, TTL, Labels, and Signer's Name match the RRset and identify an apex zone key (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.2-3`](#rfc4035-2.2-3) An RRSIG RR itself MUST NOT be signed (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.2-4`](#rfc4035-2.2-4) The NS RRset that appears at the zone apex name MUST be signed (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.2-5`](#rfc4035-2.2-5) The NS RRsets that appear at delegation points MUST NOT be signed (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.2-6`](#rfc4035-2.2-6) Glue address RRsets associated with delegations MUST NOT be signed (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.2-7`](#rfc4035-2.2-7) There MUST be an RRSIG for each RRset using at least one DNSKEY of each algorithm in the zone apex DNSKEY RRset (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.2-8`](#rfc4035-2.2-8) The apex DNSKEY RRset MUST be signed by each algorithm appearing in the DS RRset at the delegating parent, if any (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.3-1`](#rfc4035-2.3-1) Each owner name in the zone that has authoritative data or a delegation point NS RRset MUST have an NSEC resource record (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.3-3`](#rfc4035-2.3-3) An NSEC record and its associated RRSIG RRset MUST NOT be the only RRset at any particular owner name (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.3-4`](#rfc4035-2.3-4) The signing process MUST NOT create NSEC or RRSIG RRs for owner name nodes that were not the owner name of any RRset before the zone was signed (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.3-5`](#rfc4035-2.3-5) The type bitmap of every NSEC RR MUST indicate the presence of both the NSEC record itself and its corresponding RRSIG record (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.3-6`](#rfc4035-2.3-6) In the NSEC bitmap at a delegation point, bits for the delegation NS RRset and any RRsets for which the parent zone has authoritative data MUST be set (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.3-7`](#rfc4035-2.3-7) In the NSEC bitmap at a delegation point, bits for any non-NS RRset for which the parent is not authoritative MUST be clear (§2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.4-3`](#rfc4035-2.4-3) All DS RRsets in a zone MUST be signed (§2.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.4-4`](#rfc4035-2.4-4) DS RRsets MUST NOT appear at a zone's apex (§2.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.5-1`](#rfc4035-2.5-1) If a CNAME RRset is present at a name in a signed zone, appropriate RRSIG and NSEC RRsets are REQUIRED at that name (§2.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.5-2`](#rfc4035-2.5-2) Types other than CNAME, its RRSIG and NSEC, and a KEY RRset for secure dynamic update MUST NOT be present at a CNAME name (§2.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-2.6-1`](#rfc4035-2.6-1) At the parental side of a zone cut, NSEC RRs are REQUIRED at the owner name (§2.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze creates no DNSKEY, RRSIG, NSEC, or DS record for this rule to constrain -- ze signs no zone: grep -rnE 'TypeRRSIG\|TypeDNSKEY\|TypeNSEC\|TypeDS' --include=*.go internal/ pkg/ cmd/ matches only the RFC 4035 conformance tests, and answerQuestions synthesizes A/AAAA/SRV/SOA/NS records only (internal/plugins/geodns/server.go:168) | | [`RFC4035-3-1`](#rfc4035-3-1) A security-aware name server MUST support the EDNS0 message size extension (§3) | {gap}, no test | ze's authoritative servers give the EDNS0 message size extension no support -- the only SetEdns0 producer in ze is the stub resolver (internal/component/resolve/dns/resolver.go:261); the server path reads an OPT only for the client-subnet address (internal/core/dnsserver/client.go:23) and writes a reply built by msg.SetReply with no OPT pseudo-RR and no requestor-payload-size handling (internal/core/dnsserver/handler.go:55) | | [`RFC4035-3-2`](#rfc4035-3-2) A security-aware name server MUST support a message size of at least 1220 octets (§3) | {gap}, no test | the UDP listener accepts at most 512 octets -- dns.Server is constructed with UDPSize left zero (internal/core/dnsserver/manager.go:165), which miekg defaults to MinMsgSize 512 (vendor/github.com/miekg/dns/server.go:287), so a query above 512 octets is never read whole and no reply advertises a larger size | | [`RFC4035-3-5`](#rfc4035-3-5) A name server receiving a query without the EDNS OPT pseudo-RR or with the DO bit clear MUST treat the RRSIG, DNSKEY, and NSEC RRs as it would any other RRset (§3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1-1`](#rfc4035-3.1-1) Upon a DO-set query to a signed zone, RRSIG RRs that can be used to authenticate the response MUST be included (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1-2`](#rfc4035-3.1-2) NSEC RRs that provide authenticated denial of existence MUST be included in the response automatically (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1-3`](#rfc4035-3.1-3) Either a DS RRset or an NSEC RR proving that no DS RRs exist MUST be included in referrals automatically (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.1-3`](#rfc4035-3.1.1-3) When placing a signed RRset in the Answer section, the name server MUST also place its RRSIG RRs in the Answer section (§3.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.1-4`](#rfc4035-3.1.1-4) If space does not permit inclusion of the RRSIG RRs that must accompany a signed RRset, or of a mandatory NSEC or DS RRset and its RRSIGs, the name server MUST set the TC bit (§3.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.1-5`](#rfc4035-3.1.1-5) When placing a signed RRset in the Authority section, the name server MUST also place its RRSIG RRs in the Authority section (§3.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.1-6`](#rfc4035-3.1.1-6) When placing a signed RRset in the Additional section, the name server MUST also place its RRSIG RRs in the Additional section (§3.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.1-8`](#rfc4035-3.1.1-8) The name server MUST NOT set the TC bit solely because RRSIG RRs did not fit in the Additional section (§3.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.2-3`](#rfc4035-3.1.2-3) If there is not enough space for the apex DNSKEY RRset and its RRSIGs, the name server MUST omit them (§3.1.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.2-4`](#rfc4035-3.1.2-4) The name server MUST NOT set the TC bit solely because the apex DNSKEY and RRSIG RRs did not fit (§3.1.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.3-1`](#rfc4035-3.1.3-1) When responding to a DO-set query, the name server MUST include NSEC RRs in the No Data, Name Error, Wildcard Answer, and Wildcard No Data cases (§3.1.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.3.1-1`](#rfc4035-3.1.3.1-1) For a No Data response, the name server MUST include the NSEC RR for the queried name and its RRSIGs in the Authority section (§3.1.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.3.2-1`](#rfc4035-3.1.3.2-1) For a Name Error response, the name server MUST include, with their RRSIGs, an NSEC RR proving no exact match and an NSEC RR proving no wildcard match (§3.1.3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.3.3-1`](#rfc4035-3.1.3.3-1) For a Wildcard Answer response, the name server MUST include the wildcard-expanded answer and its wildcard-expanded RRSIG RRs in the Answer section (§3.1.3.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.3.3-2`](#rfc4035-3.1.3.3-2) For a Wildcard Answer response, the name server MUST include in the Authority section an NSEC RR and its RRSIGs proving that no closer match exists (§3.1.3.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.3.4-1`](#rfc4035-3.1.3.4-1) For a Wildcard No Data response, the name server MUST include, with their RRSIGs, an NSEC RR proving no matching type at the wildcard owner name and an NSEC RR proving no closer match (§3.1.3.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.4-1`](#rfc4035-3.1.4-1) If a DS RRset is present at the delegation point, the name server MUST return the DS RRset and its RRSIGs in the Authority section with the NS RRset (§3.1.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.4-2`](#rfc4035-3.1.4-2) If no DS RRset is present, the name server MUST return the NSEC RR proving the DS RRset is absent and its RRSIGs with the NS RRset (§3.1.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.4-3`](#rfc4035-3.1.4-3) The name server MUST place the NS RRset before the NSEC RRset and its RRSIGs (§3.1.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze's authoritative servers hold no RRSIG, NSEC, DS, or DNSKEY record to place in any section: answerQuestions builds A/AAAA/SRV/SOA/NS answers from local host-sets (internal/plugins/geodns/server.go:168) and as112 answers only negatively (internal/plugins/as112/server.go:86) | | [`RFC4035-3.1.5-2`](#rfc4035-3.1.5-2) A name server performing its own zone validation MUST NOT selectively reject some RRs and accept others (§3.1.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | [`RFC4035-3.1.5-3`](#rfc4035-3.1.5-3) The DS RRset MUST be included in zone transfers of the parent zone in which it is authoritative data (§3.1.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | [`RFC4035-3.1.5-4`](#rfc4035-3.1.5-4) NSEC RRs MUST be included in zone transfers of the zone in which they are authoritative data (§3.1.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | [`RFC4035-3.1.5-5`](#rfc4035-3.1.5-5) The parental NSEC RR at a zone cut MUST be included in zone transfers of the parent zone (§3.1.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | [`RFC4035-3.1.5-6`](#rfc4035-3.1.5-6) The NSEC at the zone apex of the child zone MUST be included in zone transfers of the child zone (§3.1.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | [`RFC4035-3.1.5-7`](#rfc4035-3.1.5-7) RRSIG RRs MUST be included in zone transfers of the zone in which they are authoritative data (§3.1.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze serves no zone transfer: grep -rniE 'axfr\|ixfr' --include=*.go internal/ finds no producer, and no ze DNS server holds DNSSEC records to transfer | | [`RFC4035-3.1.6-6`](#rfc4035-3.1.6-6) A security-aware name server that supports recursion MUST follow the recursive-server CD and AD bit rules for data obtained via recursion (§3.1.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | [`RFC4035-3.2.1-1`](#rfc4035-3.2.1-1) The resolver side of a security-aware recursive name server MUST set the DO bit when sending requests (§3.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | [`RFC4035-3.2.1-2`](#rfc4035-3.2.1-2) If the DO bit in an initiating query is not set, the name server side MUST strip any authenticating DNSSEC RRs from the response (§3.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | [`RFC4035-3.2.1-3`](#rfc4035-3.2.1-3) The name server side MUST NOT strip any DNSSEC RR types that the initiating query explicitly requested (§3.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | [`RFC4035-3.2.2-1`](#rfc4035-3.2.2-1) The name server side MUST pass the state of the CD bit to the resolver side along with the initiating query (§3.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | [`RFC4035-3.2.2-4`](#rfc4035-3.2.2-4) If the CD bit is not set and the query matches a BAD cache entry, the name server side MUST return RCODE 2 (server failure) (§3.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | [`RFC4035-3.2.3-2`](#rfc4035-3.2.3-2) The resolver side MUST determine whether the RRs are authentic by following the RFC's authentication procedure (§3.2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | [`RFC4035-4.1-5`](#rfc4035-4.1-5) A security-aware resolver's IP layer MUST handle fragmented UDP packets correctly whether received via IPv4 or IPv6 (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: UDP fragment reassembly belongs to the host IP stack, which ze neither implements nor bypasses -- the resolver exchanges datagrams through the ordinary miekg client socket (internal/component/resolve/dns/resolver.go:263) and grep -rniE 'fragment\|reassembl' --include=*.go internal/component/resolve internal/core/dnsserver finds no producer | | [`RFC4035-4.2-1`](#rfc4035-4.2-1) A security-aware resolver MUST support the signature verification mechanisms the RFC describes (§4.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-4.2-3`](#rfc4035-4.2-3) A resolver's signature verification support MUST include verification of wildcard owner names (§4.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-4.2-5`](#rfc4035-4.2-5) When retrieving missing NSEC RRs on the parental side of a zone cut, an iterative-mode resolver MUST query the parent zone name servers, not the child (§4.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-4.2-6`](#rfc4035-4.2-6) When retrieving a missing DS, an iterative-mode resolver MUST query the parent zone name servers, not the child (§4.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-4.3-1`](#rfc4035-4.3-1) A security-aware resolver MUST be able to determine whether it should expect a particular RRset to be signed, distinguishing Secure, Insecure, Bogus, and Indeterminate (§4.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-4.4-1`](#rfc4035-4.4-1) A security-aware resolver MUST be capable of being configured with at least one trusted public key or DS RR (§4.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-4.7-2`](#rfc4035-4.7-2) A resolver that implements a BAD cache MUST take steps to prevent the cache being used as a denial-of-service amplifier (§4.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze keeps no BAD cache: Resolve caches only a non-empty successful answer (internal/component/resolve/dns/resolver.go:192) and a SERVFAIL yields an uncached empty result (internal/component/resolve/dns/resolver.go:285) | | [`RFC4035-4.7-3`](#rfc4035-4.7-3) Since RRsets that fail to validate lack trustworthy TTLs, the implementation MUST assign a TTL (§4.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze keeps no BAD cache: Resolve caches only a non-empty successful answer (internal/component/resolve/dns/resolver.go:192) and a SERVFAIL yields an uncached empty result (internal/component/resolve/dns/resolver.go:285) | | [`RFC4035-4.7-7`](#rfc4035-4.7-7) A resolver MUST NOT return RRsets from the BAD cache unless it is not required to validate their signatures (§4.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze keeps no BAD cache: Resolve caches only a non-empty successful answer (internal/component/resolve/dns/resolver.go:192) and a SERVFAIL yields an uncached empty result (internal/component/resolve/dns/resolver.go:285) | | [`RFC4035-4.8-1`](#rfc4035-4.8-1) A validating resolver MUST treat the signature of a valid signed DNAME RR as also covering unsigned CNAME RRs synthesizable from it, at least by not rejecting the message solely for containing them (§4.8) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-4.9.3-2`](#rfc4035-4.9.3-2) A security-aware stub resolver MUST NOT place any reliance on signature validation performed on its behalf except when it obtained the data from a trusted recursive name server over a secure channel (§4.9.3) | {gap}, no test | ze's stub rests a security decision on validation performed for it over an unauthenticated channel -- strict mode rejects an answer solely because the configured upstream returned SERVFAIL (internal/component/resolve/dns/resolver.go:103-106) while the client speaks plain UDP with no TLS, TSIG, or other authentication of that server (internal/component/resolve/dns/resolver.go:81) | | [`RFC4035-5-1`](#rfc4035-5-1) To authenticate an apex DNSKEY RRset with an initial key, the resolver MUST verify that the initial DNSKEY RR appears in the apex DNSKEY RRset and has the Zone Key Flag set (§5) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5-2`](#rfc4035-5-2) To authenticate an apex DNSKEY RRset with an initial key, the resolver MUST verify that some RRSIG RR covers the apex DNSKEY RRset and that it together with the initial DNSKEY authenticates the RRset (§5) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5-3`](#rfc4035-5-3) The absence of DNSSEC data in a response MUST NOT by itself be taken as an indication that no authentication information exists (§5) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.2-1`](#rfc4035-5.2-1) A security-aware resolver MUST query the parent zone name servers for the DS RRset if a referral includes neither a DS RRset nor an NSEC RRset proving the DS RRset does not exist (§5.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.2-3`](#rfc4035-5.2-3) A security-aware resolver MUST use the parent NSEC RR when attempting to prove that a DS RRset does not exist (§5.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-1`](#rfc4035-5.3.1-1) The RRSIG RR and the RRset MUST have the same owner name and the same class (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-2`](#rfc4035-5.3.1-2) The RRSIG RR's Signer's Name field MUST be the name of the zone that contains the RRset (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-3`](#rfc4035-5.3.1-3) The RRSIG RR's Type Covered field MUST equal the RRset's type (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-4`](#rfc4035-5.3.1-4) The number of labels in the RRset owner name MUST be greater than or equal to the value in the RRSIG RR's Labels field (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-5`](#rfc4035-5.3.1-5) The validator's notion of the current time MUST be less than or equal to the RRSIG RR's Expiration field (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-6`](#rfc4035-5.3.1-6) The validator's notion of the current time MUST be greater than or equal to the RRSIG RR's Inception field (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-7`](#rfc4035-5.3.1-7) The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST match the owner name, algorithm, and key tag of some DNSKEY RR in the zone's apex DNSKEY RRset (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-8`](#rfc4035-5.3.1-8) The matching DNSKEY RR MUST be present in the zone's apex DNSKEY RRset (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-9`](#rfc4035-5.3.1-9) The matching DNSKEY RR MUST have the Zone Flag bit set (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.1-10`](#rfc4035-5.3.1-10) If more than one DNSKEY RR matches, the validator MUST try each until the signature validates or the matching keys are exhausted (§5.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.2-1`](#rfc4035-5.3.2-1) If the RRSIG Labels field is greater than the RRset's label count, the RRSIG did not pass validation and MUST NOT be used to authenticate the RRset (§5.3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.2-2`](#rfc4035-5.3.2-2) When reconstructing the parent-zone NSEC RRset at a delegation, its NSEC RRs MUST NOT be combined with NSEC RRs from the child zone (§5.3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.2-3`](#rfc4035-5.3.2-3) When reconstructing the child-apex NSEC RRset, its NSEC RRs MUST NOT be combined with NSEC RRs from the parent zone (§5.3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.3-1`](#rfc4035-5.3.3-1) When the RRSIG Labels field does not equal the owner name's label count, the resolver MUST verify that wildcard expansion was applied properly before considering the RRset authentic (§5.3.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.3.3-2`](#rfc4035-5.3.3-2) On accepting an RRset as authentic, the validator MUST set the RRSIG RR and each RR's TTL to no greater than the minimum of the RRset TTL, the RRSIG TTL, the RRSIG Original TTL, and the time until the RRSIG expires (§5.3.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.4-1`](#rfc4035-5.4-1) A security-aware resolver MUST authenticate the NSEC RRsets that comprise a denial-of-existence proof (§5.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.4-2`](#rfc4035-5.4-2) If the complete set of necessary NSEC RRsets is not present in a response, the resolver MUST resend the query to obtain them (§5.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.4-3`](#rfc4035-5.4-3) The resolver MUST bound the work it puts into answering any particular query (§5.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.4-4`](#rfc4035-5.4-4) A validator MUST ignore the settings of the NSEC and RRSIG bits in an NSEC RR (§5.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no local validator: the resolver's whole DNSSEC surface is dnssecDecision (internal/component/resolve/dns/resolver.go:99), which inspects the rcode alone -- no signature verification, trust-anchor store, DS chain, or NSEC proof exists in ze | | [`RFC4035-5.5-2`](#rfc4035-5.5-2) When validation was done to service a recursive query, the name server MUST return RCODE 2 to the originating client (§5.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | | [`RFC4035-5.5-3`](#rfc4035-5.5-3) The name server MUST return the full response if and only if the original query had the CD bit set (§5.5) | no test | no test carries this requirement id; annotated {not-applicable}: ze's DNS servers never recurse and have no resolver side: shapeAuthoritative clears RecursionAvailable on every reply (internal/core/dnsserver/handler.go:74) and each AnswerFunc answers from local state alone, forwarding nothing upstream (internal/plugins/geodns/server.go:221, internal/plugins/as112/server.go:86) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4035-2.1-2`](#rfc4035-2.1-2) A zone key DNSKEY RR MUST have the Zone Key bit of the flags RDATA field set (§2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.1-2, so no unit is bound to it. ### [`RFC4035-2.1-4`](#rfc4035-2.1-4) Public keys stored in DNSKEY RRs that are not marked as zone keys MUST NOT be used to verify RRSIGs (§2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.1-4, so no unit is bound to it. ### [`RFC4035-2.1-5`](#rfc4035-2.1-5) For a signed zone usable other than as an island of security, the zone apex MUST contain at least one DNSKEY RR to act as a secure entry point (§2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.1-5, so no unit is bound to it. ### [`RFC4035-2.2-1`](#rfc4035-2.2-1) For each authoritative RRset in a signed zone there MUST be at least one RRSIG record whose owner, class, Type Covered, Original TTL, TTL, Labels, and Signer's Name match the RRset and identify an apex zone key (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.2-1, so no unit is bound to it. ### [`RFC4035-2.2-3`](#rfc4035-2.2-3) An RRSIG RR itself MUST NOT be signed (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.2-3, so no unit is bound to it. ### [`RFC4035-2.2-4`](#rfc4035-2.2-4) The NS RRset that appears at the zone apex name MUST be signed (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.2-4, so no unit is bound to it. ### [`RFC4035-2.2-5`](#rfc4035-2.2-5) The NS RRsets that appear at delegation points MUST NOT be signed (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.2-5, so no unit is bound to it. ### [`RFC4035-2.2-6`](#rfc4035-2.2-6) Glue address RRsets associated with delegations MUST NOT be signed (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.2-6, so no unit is bound to it. ### [`RFC4035-2.2-7`](#rfc4035-2.2-7) There MUST be an RRSIG for each RRset using at least one DNSKEY of each algorithm in the zone apex DNSKEY RRset (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.2-7, so no unit is bound to it. ### [`RFC4035-2.2-8`](#rfc4035-2.2-8) The apex DNSKEY RRset MUST be signed by each algorithm appearing in the DS RRset at the delegating parent, if any (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.2-8, so no unit is bound to it. ### [`RFC4035-2.3-1`](#rfc4035-2.3-1) Each owner name in the zone that has authoritative data or a delegation point NS RRset MUST have an NSEC resource record (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.3-1, so no unit is bound to it. ### [`RFC4035-2.3-3`](#rfc4035-2.3-3) An NSEC record and its associated RRSIG RRset MUST NOT be the only RRset at any particular owner name (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.3-3, so no unit is bound to it. ### [`RFC4035-2.3-4`](#rfc4035-2.3-4) The signing process MUST NOT create NSEC or RRSIG RRs for owner name nodes that were not the owner name of any RRset before the zone was signed (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.3-4, so no unit is bound to it. ### [`RFC4035-2.3-5`](#rfc4035-2.3-5) The type bitmap of every NSEC RR MUST indicate the presence of both the NSEC record itself and its corresponding RRSIG record (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.3-5, so no unit is bound to it. ### [`RFC4035-2.3-6`](#rfc4035-2.3-6) In the NSEC bitmap at a delegation point, bits for the delegation NS RRset and any RRsets for which the parent zone has authoritative data MUST be set (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.3-6, so no unit is bound to it. ### [`RFC4035-2.3-7`](#rfc4035-2.3-7) In the NSEC bitmap at a delegation point, bits for any non-NS RRset for which the parent is not authoritative MUST be clear (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.3-7, so no unit is bound to it. ### [`RFC4035-2.4-3`](#rfc4035-2.4-3) All DS RRsets in a zone MUST be signed (§2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.4-3, so no unit is bound to it. ### [`RFC4035-2.4-4`](#rfc4035-2.4-4) DS RRsets MUST NOT appear at a zone's apex (§2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.4-4, so no unit is bound to it. ### [`RFC4035-2.5-1`](#rfc4035-2.5-1) If a CNAME RRset is present at a name in a signed zone, appropriate RRSIG and NSEC RRsets are REQUIRED at that name (§2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.5-1, so no unit is bound to it. ### [`RFC4035-2.5-2`](#rfc4035-2.5-2) Types other than CNAME, its RRSIG and NSEC, and a KEY RRset for secure dynamic update MUST NOT be present at a CNAME name (§2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.5-2, so no unit is bound to it. ### [`RFC4035-2.6-1`](#rfc4035-2.6-1) At the parental side of a zone cut, NSEC RRs are REQUIRED at the owner name (§2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-2.6-1, so no unit is bound to it. ### [`RFC4035-3-1`](#rfc4035-3-1) A security-aware name server MUST support the EDNS0 message size extension (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3-1, so no unit is bound to it. ### [`RFC4035-3-2`](#rfc4035-3-2) A security-aware name server MUST support a message size of at least 1220 octets (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3-2, so no unit is bound to it. ### [`RFC4035-3-5`](#rfc4035-3-5) A name server receiving a query without the EDNS OPT pseudo-RR or with the DO bit clear MUST treat the RRSIG, DNSKEY, and NSEC RRs as it would any other RRset (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3-5, so no unit is bound to it. ### [`RFC4035-3-6`](#rfc4035-3-6) Such a name server MUST NOT perform any of the DNSSEC additional processing (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_NoDNSSECAdditionalProcessing`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L164) | unit/verify | unproven | ### [`RFC4035-3-8`](#rfc4035-3-8) A security-aware name server MUST copy the CD bit from a query into the corresponding response (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4035_CDCopiedADIgnored`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc4035_test.go#L56) | unit/verify | unproven | | positive | [`TestRFC4035_CDCopiedADIgnored`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc4035_test.go#L51) | unit/verify | unproven | ### [`RFC4035-3-9`](#rfc4035-3-9) A security-aware name server MUST ignore the setting of the AD bit in queries (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4035_CDCopiedADIgnored`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc4035_test.go#L69) | unit/verify | unproven | | positive | [`TestRFC4035_CDCopiedADIgnored`](https://github.com/ze-software/ze/blob/main/internal/core/dnsserver/rfc4035_test.go#L62) | unit/verify | unproven | ### [`RFC4035-3.1-1`](#rfc4035-3.1-1) Upon a DO-set query to a signed zone, RRSIG RRs that can be used to authenticate the response MUST be included (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1-1, so no unit is bound to it. ### [`RFC4035-3.1-2`](#rfc4035-3.1-2) NSEC RRs that provide authenticated denial of existence MUST be included in the response automatically (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1-2, so no unit is bound to it. ### [`RFC4035-3.1-3`](#rfc4035-3.1-3) Either a DS RRset or an NSEC RR proving that no DS RRs exist MUST be included in referrals automatically (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1-3, so no unit is bound to it. ### [`RFC4035-3.1.1-3`](#rfc4035-3.1.1-3) When placing a signed RRset in the Answer section, the name server MUST also place its RRSIG RRs in the Answer section (§3.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.1-3, so no unit is bound to it. ### [`RFC4035-3.1.1-4`](#rfc4035-3.1.1-4) If space does not permit inclusion of the RRSIG RRs that must accompany a signed RRset, or of a mandatory NSEC or DS RRset and its RRSIGs, the name server MUST set the TC bit (§3.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.1-4, so no unit is bound to it. ### [`RFC4035-3.1.1-5`](#rfc4035-3.1.1-5) When placing a signed RRset in the Authority section, the name server MUST also place its RRSIG RRs in the Authority section (§3.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.1-5, so no unit is bound to it. ### [`RFC4035-3.1.1-6`](#rfc4035-3.1.1-6) When placing a signed RRset in the Additional section, the name server MUST also place its RRSIG RRs in the Additional section (§3.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.1-6, so no unit is bound to it. ### [`RFC4035-3.1.1-8`](#rfc4035-3.1.1-8) The name server MUST NOT set the TC bit solely because RRSIG RRs did not fit in the Additional section (§3.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.1-8, so no unit is bound to it. ### [`RFC4035-3.1.2-3`](#rfc4035-3.1.2-3) If there is not enough space for the apex DNSKEY RRset and its RRSIGs, the name server MUST omit them (§3.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.2-3, so no unit is bound to it. ### [`RFC4035-3.1.2-4`](#rfc4035-3.1.2-4) The name server MUST NOT set the TC bit solely because the apex DNSKEY and RRSIG RRs did not fit (§3.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.2-4, so no unit is bound to it. ### [`RFC4035-3.1.3-1`](#rfc4035-3.1.3-1) When responding to a DO-set query, the name server MUST include NSEC RRs in the No Data, Name Error, Wildcard Answer, and Wildcard No Data cases (§3.1.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.3-1, so no unit is bound to it. ### [`RFC4035-3.1.3.1-1`](#rfc4035-3.1.3.1-1) For a No Data response, the name server MUST include the NSEC RR for the queried name and its RRSIGs in the Authority section (§3.1.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.3.1-1, so no unit is bound to it. ### [`RFC4035-3.1.3.2-1`](#rfc4035-3.1.3.2-1) For a Name Error response, the name server MUST include, with their RRSIGs, an NSEC RR proving no exact match and an NSEC RR proving no wildcard match (§3.1.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.3.2-1, so no unit is bound to it. ### [`RFC4035-3.1.3.3-1`](#rfc4035-3.1.3.3-1) For a Wildcard Answer response, the name server MUST include the wildcard-expanded answer and its wildcard-expanded RRSIG RRs in the Answer section (§3.1.3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.3.3-1, so no unit is bound to it. ### [`RFC4035-3.1.3.3-2`](#rfc4035-3.1.3.3-2) For a Wildcard Answer response, the name server MUST include in the Authority section an NSEC RR and its RRSIGs proving that no closer match exists (§3.1.3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.3.3-2, so no unit is bound to it. ### [`RFC4035-3.1.3.4-1`](#rfc4035-3.1.3.4-1) For a Wildcard No Data response, the name server MUST include, with their RRSIGs, an NSEC RR proving no matching type at the wildcard owner name and an NSEC RR proving no closer match (§3.1.3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.3.4-1, so no unit is bound to it. ### [`RFC4035-3.1.4-1`](#rfc4035-3.1.4-1) If a DS RRset is present at the delegation point, the name server MUST return the DS RRset and its RRSIGs in the Authority section with the NS RRset (§3.1.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.4-1, so no unit is bound to it. ### [`RFC4035-3.1.4-2`](#rfc4035-3.1.4-2) If no DS RRset is present, the name server MUST return the NSEC RR proving the DS RRset is absent and its RRSIGs with the NS RRset (§3.1.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.4-2, so no unit is bound to it. ### [`RFC4035-3.1.4-3`](#rfc4035-3.1.4-3) The name server MUST place the NS RRset before the NSEC RRset and its RRSIGs (§3.1.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.4-3, so no unit is bound to it. ### [`RFC4035-3.1.4.1-1`](#rfc4035-3.1.4.1-1) When authoritative for the child zone but not the parent and not offering recursion, on a DS query at the zone cut the name server MUST return an authoritative no-data response (§3.1.4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4035_DSQueryIsAuthoritativeNoData`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L112) | unit/verify | unproven | | positive | [`TestRFC4035_DSQueryIsAuthoritativeNoData`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L85) | unit/verify | unproven | ### [`RFC4035-3.1.5-2`](#rfc4035-3.1.5-2) A name server performing its own zone validation MUST NOT selectively reject some RRs and accept others (§3.1.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.5-2, so no unit is bound to it. ### [`RFC4035-3.1.5-3`](#rfc4035-3.1.5-3) The DS RRset MUST be included in zone transfers of the parent zone in which it is authoritative data (§3.1.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.5-3, so no unit is bound to it. ### [`RFC4035-3.1.5-4`](#rfc4035-3.1.5-4) NSEC RRs MUST be included in zone transfers of the zone in which they are authoritative data (§3.1.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.5-4, so no unit is bound to it. ### [`RFC4035-3.1.5-5`](#rfc4035-3.1.5-5) The parental NSEC RR at a zone cut MUST be included in zone transfers of the parent zone (§3.1.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.5-5, so no unit is bound to it. ### [`RFC4035-3.1.5-6`](#rfc4035-3.1.5-6) The NSEC at the zone apex of the child zone MUST be included in zone transfers of the child zone (§3.1.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.5-6, so no unit is bound to it. ### [`RFC4035-3.1.5-7`](#rfc4035-3.1.5-7) RRSIG RRs MUST be included in zone transfers of the zone in which they are authoritative data (§3.1.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.5-7, so no unit is bound to it. ### [`RFC4035-3.1.6-2`](#rfc4035-3.1.6-2) A security-aware name server MUST NOT set the AD bit in a response unless it considers all RRsets in the Answer and Authority sections to be authentic (§3.1.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_NoDNSSECAdditionalProcessing`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L177) | unit/verify | unproven | ### [`RFC4035-3.1.6-4`](#rfc4035-3.1.6-4) The name server MUST NOT treat authoritative-zone data as authentic unless it obtained the zone via secure means (§3.1.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_NoDNSSECAdditionalProcessing`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L179) | unit/verify | unproven | ### [`RFC4035-3.1.6-5`](#rfc4035-3.1.6-5) The name server MUST NOT treat authoritative-zone data as authentic unless this behavior has been configured explicitly (§3.1.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_NoDNSSECAdditionalProcessing`](https://github.com/ze-software/ze/blob/main/internal/plugins/geodns/rfc4035_server_test.go#L182) | unit/verify | unproven | ### [`RFC4035-3.1.6-6`](#rfc4035-3.1.6-6) A security-aware name server that supports recursion MUST follow the recursive-server CD and AD bit rules for data obtained via recursion (§3.1.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.1.6-6, so no unit is bound to it. ### [`RFC4035-3.2.1-1`](#rfc4035-3.2.1-1) The resolver side of a security-aware recursive name server MUST set the DO bit when sending requests (§3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.2.1-1, so no unit is bound to it. ### [`RFC4035-3.2.1-2`](#rfc4035-3.2.1-2) If the DO bit in an initiating query is not set, the name server side MUST strip any authenticating DNSSEC RRs from the response (§3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.2.1-2, so no unit is bound to it. ### [`RFC4035-3.2.1-3`](#rfc4035-3.2.1-3) The name server side MUST NOT strip any DNSSEC RR types that the initiating query explicitly requested (§3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.2.1-3, so no unit is bound to it. ### [`RFC4035-3.2.2-1`](#rfc4035-3.2.2-1) The name server side MUST pass the state of the CD bit to the resolver side along with the initiating query (§3.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.2.2-1, so no unit is bound to it. ### [`RFC4035-3.2.2-4`](#rfc4035-3.2.2-4) If the CD bit is not set and the query matches a BAD cache entry, the name server side MUST return RCODE 2 (server failure) (§3.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.2.2-4, so no unit is bound to it. ### [`RFC4035-3.2.3-2`](#rfc4035-3.2.3-2) The resolver side MUST determine whether the RRs are authentic by following the RFC's authentication procedure (§3.2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-3.2.3-2, so no unit is bound to it. ### [`RFC4035-4.1-1`](#rfc4035-4.1-1) A security-aware resolver MUST include an EDNS OPT pseudo-RR with the DO bit set when sending queries (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_QueryCarriesEDNS0DOAndClearAD`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L60) | unit/verify | unproven | ### [`RFC4035-4.1-2`](#rfc4035-4.1-2) A security-aware resolver MUST support a message size of at least 1220 octets (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_LargeUDPResponseAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L116) | unit/verify | unproven | ### [`RFC4035-4.1-4`](#rfc4035-4.1-4) A security-aware resolver MUST use the sender's UDP payload size field in the EDNS OPT pseudo-RR to advertise the message size it will accept (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_QueryCarriesEDNS0DOAndClearAD`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L70) | unit/verify | unproven | ### [`RFC4035-4.1-5`](#rfc4035-4.1-5) A security-aware resolver's IP layer MUST handle fragmented UDP packets correctly whether received via IPv4 or IPv6 (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.1-5, so no unit is bound to it. ### [`RFC4035-4.2-1`](#rfc4035-4.2-1) A security-aware resolver MUST support the signature verification mechanisms the RFC describes (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.2-1, so no unit is bound to it. ### [`RFC4035-4.2-3`](#rfc4035-4.2-3) A resolver's signature verification support MUST include verification of wildcard owner names (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.2-3, so no unit is bound to it. ### [`RFC4035-4.2-5`](#rfc4035-4.2-5) When retrieving missing NSEC RRs on the parental side of a zone cut, an iterative-mode resolver MUST query the parent zone name servers, not the child (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.2-5, so no unit is bound to it. ### [`RFC4035-4.2-6`](#rfc4035-4.2-6) When retrieving a missing DS, an iterative-mode resolver MUST query the parent zone name servers, not the child (§4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.2-6, so no unit is bound to it. ### [`RFC4035-4.3-1`](#rfc4035-4.3-1) A security-aware resolver MUST be able to determine whether it should expect a particular RRset to be signed, distinguishing Secure, Insecure, Bogus, and Indeterminate (§4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.3-1, so no unit is bound to it. ### [`RFC4035-4.4-1`](#rfc4035-4.4-1) A security-aware resolver MUST be capable of being configured with at least one trusted public key or DS RR (§4.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.4-1, so no unit is bound to it. ### [`RFC4035-4.6-2`](#rfc4035-4.6-2) A security-aware resolver MUST clear the AD bit when composing query messages (§4.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_QueryCarriesEDNS0DOAndClearAD`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L75) | unit/verify | unproven | ### [`RFC4035-4.6-3`](#rfc4035-4.6-3) A resolver MUST disregard the meaning of the CD and AD bits in a response unless it was obtained over a secure channel or the resolver was configured to trust them (§4.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4035_ResponseADBitDisregarded`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L159) | unit/verify | unproven | | positive | [`TestRFC4035_ResponseADBitDisregarded`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L148) | unit/verify | unproven | ### [`RFC4035-4.7-2`](#rfc4035-4.7-2) A resolver that implements a BAD cache MUST take steps to prevent the cache being used as a denial-of-service amplifier (§4.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.7-2, so no unit is bound to it. ### [`RFC4035-4.7-3`](#rfc4035-4.7-3) Since RRsets that fail to validate lack trustworthy TTLs, the implementation MUST assign a TTL (§4.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.7-3, so no unit is bound to it. ### [`RFC4035-4.7-7`](#rfc4035-4.7-7) A resolver MUST NOT return RRsets from the BAD cache unless it is not required to validate their signatures (§4.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.7-7, so no unit is bound to it. ### [`RFC4035-4.8-1`](#rfc4035-4.8-1) A validating resolver MUST treat the signature of a valid signed DNAME RR as also covering unsigned CNAME RRs synthesizable from it, at least by not rejecting the message solely for containing them (§4.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.8-1, so no unit is bound to it. ### [`RFC4035-4.9-1`](#rfc4035-4.9-1) A security-aware stub resolver MUST support the DNSSEC RR types, at least by not mishandling responses that contain them (§4.9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4035_StubHandlesDNSSECRRTypes`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L219) | unit/verify | unproven | | positive | [`TestRFC4035_StubHandlesDNSSECRRTypes`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L208) | unit/verify | unproven | ### [`RFC4035-4.9.1-2`](#rfc4035-4.9.1-2) A validating security-aware stub resolver MUST set the DO bit (§4.9.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4035_QueryCarriesEDNS0DOAndClearAD`](https://github.com/ze-software/ze/blob/main/internal/component/resolve/dns/rfc4035_test.go#L62) | unit/verify | unproven | ### [`RFC4035-4.9.3-2`](#rfc4035-4.9.3-2) A security-aware stub resolver MUST NOT place any reliance on signature validation performed on its behalf except when it obtained the data from a trusted recursive name server over a secure channel (§4.9.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-4.9.3-2, so no unit is bound to it. ### [`RFC4035-5-1`](#rfc4035-5-1) To authenticate an apex DNSKEY RRset with an initial key, the resolver MUST verify that the initial DNSKEY RR appears in the apex DNSKEY RRset and has the Zone Key Flag set (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5-1, so no unit is bound to it. ### [`RFC4035-5-2`](#rfc4035-5-2) To authenticate an apex DNSKEY RRset with an initial key, the resolver MUST verify that some RRSIG RR covers the apex DNSKEY RRset and that it together with the initial DNSKEY authenticates the RRset (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5-2, so no unit is bound to it. ### [`RFC4035-5-3`](#rfc4035-5-3) The absence of DNSSEC data in a response MUST NOT by itself be taken as an indication that no authentication information exists (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5-3, so no unit is bound to it. ### [`RFC4035-5.2-1`](#rfc4035-5.2-1) A security-aware resolver MUST query the parent zone name servers for the DS RRset if a referral includes neither a DS RRset nor an NSEC RRset proving the DS RRset does not exist (§5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.2-1, so no unit is bound to it. ### [`RFC4035-5.2-3`](#rfc4035-5.2-3) A security-aware resolver MUST use the parent NSEC RR when attempting to prove that a DS RRset does not exist (§5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.2-3, so no unit is bound to it. ### [`RFC4035-5.3.1-1`](#rfc4035-5.3.1-1) The RRSIG RR and the RRset MUST have the same owner name and the same class (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-1, so no unit is bound to it. ### [`RFC4035-5.3.1-2`](#rfc4035-5.3.1-2) The RRSIG RR's Signer's Name field MUST be the name of the zone that contains the RRset (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-2, so no unit is bound to it. ### [`RFC4035-5.3.1-3`](#rfc4035-5.3.1-3) The RRSIG RR's Type Covered field MUST equal the RRset's type (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-3, so no unit is bound to it. ### [`RFC4035-5.3.1-4`](#rfc4035-5.3.1-4) The number of labels in the RRset owner name MUST be greater than or equal to the value in the RRSIG RR's Labels field (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-4, so no unit is bound to it. ### [`RFC4035-5.3.1-5`](#rfc4035-5.3.1-5) The validator's notion of the current time MUST be less than or equal to the RRSIG RR's Expiration field (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-5, so no unit is bound to it. ### [`RFC4035-5.3.1-6`](#rfc4035-5.3.1-6) The validator's notion of the current time MUST be greater than or equal to the RRSIG RR's Inception field (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-6, so no unit is bound to it. ### [`RFC4035-5.3.1-7`](#rfc4035-5.3.1-7) The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST match the owner name, algorithm, and key tag of some DNSKEY RR in the zone's apex DNSKEY RRset (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-7, so no unit is bound to it. ### [`RFC4035-5.3.1-8`](#rfc4035-5.3.1-8) The matching DNSKEY RR MUST be present in the zone's apex DNSKEY RRset (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-8, so no unit is bound to it. ### [`RFC4035-5.3.1-9`](#rfc4035-5.3.1-9) The matching DNSKEY RR MUST have the Zone Flag bit set (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-9, so no unit is bound to it. ### [`RFC4035-5.3.1-10`](#rfc4035-5.3.1-10) If more than one DNSKEY RR matches, the validator MUST try each until the signature validates or the matching keys are exhausted (§5.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.1-10, so no unit is bound to it. ### [`RFC4035-5.3.2-1`](#rfc4035-5.3.2-1) If the RRSIG Labels field is greater than the RRset's label count, the RRSIG did not pass validation and MUST NOT be used to authenticate the RRset (§5.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.2-1, so no unit is bound to it. ### [`RFC4035-5.3.2-2`](#rfc4035-5.3.2-2) When reconstructing the parent-zone NSEC RRset at a delegation, its NSEC RRs MUST NOT be combined with NSEC RRs from the child zone (§5.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.2-2, so no unit is bound to it. ### [`RFC4035-5.3.2-3`](#rfc4035-5.3.2-3) When reconstructing the child-apex NSEC RRset, its NSEC RRs MUST NOT be combined with NSEC RRs from the parent zone (§5.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.2-3, so no unit is bound to it. ### [`RFC4035-5.3.3-1`](#rfc4035-5.3.3-1) When the RRSIG Labels field does not equal the owner name's label count, the resolver MUST verify that wildcard expansion was applied properly before considering the RRset authentic (§5.3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.3-1, so no unit is bound to it. ### [`RFC4035-5.3.3-2`](#rfc4035-5.3.3-2) On accepting an RRset as authentic, the validator MUST set the RRSIG RR and each RR's TTL to no greater than the minimum of the RRset TTL, the RRSIG TTL, the RRSIG Original TTL, and the time until the RRSIG expires (§5.3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.3.3-2, so no unit is bound to it. ### [`RFC4035-5.4-1`](#rfc4035-5.4-1) A security-aware resolver MUST authenticate the NSEC RRsets that comprise a denial-of-existence proof (§5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.4-1, so no unit is bound to it. ### [`RFC4035-5.4-2`](#rfc4035-5.4-2) If the complete set of necessary NSEC RRsets is not present in a response, the resolver MUST resend the query to obtain them (§5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.4-2, so no unit is bound to it. ### [`RFC4035-5.4-3`](#rfc4035-5.4-3) The resolver MUST bound the work it puts into answering any particular query (§5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.4-3, so no unit is bound to it. ### [`RFC4035-5.4-4`](#rfc4035-5.4-4) A validator MUST ignore the settings of the NSEC and RRSIG bits in an NSEC RR (§5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.4-4, so no unit is bound to it. ### [`RFC4035-5.5-2`](#rfc4035-5.5-2) When validation was done to service a recursive query, the name server MUST return RCODE 2 to the originating client (§5.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.5-2, so no unit is bound to it. ### [`RFC4035-5.5-3`](#rfc4035-5.5-3) The name server MUST return the full response if and only if the original query had the CD bit set (§5.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4035-5.5-3, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 4035, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4035, so its obligations are stated where they were written. --- ### Page: RFC 4090 - Fast Reroute Extensions to RSVP-TE for LSP Tunnels https://ze-software.net/quality/rfc-compliance/rfc4090/ # RFC 4090 - Fast Reroute Extensions to RSVP-TE for LSP Tunnels Experimental. Every requirement this repository extracted from RFC 4090, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 12 of 12 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 12 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 12 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 12 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 26 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 12 | of 14 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 12 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 12 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 12 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 12 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 12 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 14 | | Gated MUST-level | 12 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 26 | | Tagged units | 26 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4090.md` | | Requirement shard | `rfc/requirements/rfc4090.md` | | RFC text | `rfc/full/rfc4090.txt` | ## Enrolment Enrolled: Fast Reroute Extensions to RSVP-TE for LSP Tunnels: 12 MUSTs all MET (FAST_REROUTE/SESSION_ATTRIBUTE flags, RRO protection flags, PLR local repair, label-stacking bypass, merge-point selection, PathErr Notify, and the Section 4.2 rejection of a PATH carrying a DETOUR object at an LSR without one-to-one backup support), positive+negative tags ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** 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. **What the ledger says remains:** One-to-one detour backup was explicitly split to later work. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 12 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **12** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (12):** [`RFC4090-4.1-1`](#rfc4090-4.1-1), [`RFC4090-4.3-1`](#rfc4090-4.3-1), [`RFC4090-4.3-2`](#rfc4090-4.3-2), [`RFC4090-4.1-2`](#rfc4090-4.1-2), [`RFC4090-4.4-1`](#rfc4090-4.4-1), [`RFC4090-4.4-2`](#rfc4090-4.4-2), [`RFC4090-4.4-3`](#rfc4090-4.4-3), [`RFC4090-6.5-1`](#rfc4090-6.5-1), [`RFC4090-6.5-2`](#rfc4090-6.5-2), [`RFC4090-3.2-1`](#rfc4090-3.2-1), [`RFC4090-3.2-2`](#rfc4090-3.2-2), [`RFC4090-4.2-1`](#rfc4090-4.2-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4090-4.1-1` | FAST_REROUTE object uses Class-Num 205, C-Type 1, object Length 24 (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestEncodeDecodeFastReroute`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L35). **negative:** `unit/verify` [`TestFastRerouteShortBody`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L49) | | `RFC4090-4.3-1` | "Local protection desired" (0x01) set in SESSION_ATTRIBUTE when protection is requested (S4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestBuildPathIncludesFastReroute`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L166). **negative:** `unit/verify` [`TestBuildPathNoProtection`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L181) | | `RFC4090-4.3-2` | "Node protection desired" (0x10) set in SESSION_ATTRIBUTE for node protection (S4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestBuildPathIncludesFastReroute`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L168). **positive:** `unit/verify` [`TestSessionAttributeProtectionFlags`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L63). **negative:** `unit/verify` [`TestSessionAttributeEmptyName`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L91) | | `RFC4090-4.1-2` | FAST_REROUTE Flags: facility backup (0x02) or one-to-one backup (0x01) set per the requested method (S4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestBuildPathIncludesFastReroute`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L161). **negative:** `unit/verify` [`TestFastRerouteOneToOneMethodFlag`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L833) | | `RFC4090-4.4-1` | PLR sets RRO "local protection available" (0x01) once a backup is armed (S4.4) | MUST | 4.4 | **positive:** `unit/verify` [`TestPLRArmsBypass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L323). **negative:** `unit/verify` [`TestPLRNoBypassWithoutProtection`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L341) | | `RFC4090-4.4-2` | PLR sets RRO "local protection in use" (0x02) once traffic is on the backup (S4.4) | MUST | 4.4 | **positive:** `unit/verify` [`TestRROProtectionFlagsReflectState`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L857). **negative:** `unit/verify` [`TestRROProtectionFlagsReflectState`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L849) | | `RFC4090-4.4-3` | PLR sets RRO "node protection" (0x08) when the backup protects the next node (S4.4) | MUST | 4.4 | **positive:** `unit/verify` [`TestRROProtectionFlagsReflectState`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L862). **negative:** `unit/verify` [`TestRROProtectionFlagsReflectState`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L851) | | `RFC4090-6.5-1` | On local repair the PLR sends PathErr Error Code 25 (Notify), Error Value sub-code 3 (Tunnel locally repaired) toward the head-end (S6.5) | MUST | 6.5 | **positive:** `unit/verify` [`TestLocalRepairSendsNotify`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L447). **negative:** `unit/verify` [`TestLocalRepairFallsBackToTeardown`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L469) | | `RFC4090-6.5-2` | On local repair the PLR MUST NOT tear down the protected LSP (S6.5) | MUST | 6.5 | **positive:** `unit/verify` [`TestLocalRepairSwitchesFIB`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L421). **negative:** `unit/verify` [`TestLocalRepairFallsBackToTeardown`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L465) | | `RFC4090-3.2-1` | Facility backup pushes the bypass label on top of the protected LSP label (label stacking) (S3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestLocalRepairSwitchesFIB`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L417). **negative:** `unit/verify` [`TestLocalRepairFallsBackToTeardown`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L462) | | `RFC4090-3.2-2` | The merge point is the NHOP for link protection and the NNHOP for node protection (S3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestNodeProtectionLocalRepair`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L750). **positive:** `unit/verify` [`TestPLRArmsBypass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L307). **negative:** `unit/verify` [`TestNodeProtectionNeedsNodeBypass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L791) | | `RFC4090-4.2-1` | An LSR that does not support the DETOUR object MUST reject any Path message containing a DETOUR object and send a PathErr to notify the PLR, generated as [RSVP] specifies for unknown objects with a Class-Num of the form "0bbbbbbb" (S4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestEnginePathWithDetourRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L885). **negative:** `unit/verify` [`TestEnginePathWithDetourRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L904) | | `RFC4090-6.5-3` | The head-end re-optimizes (make-before-break) onto a fresh path after a Notify and tears the repaired LSP (S6.5) | SHOULD | 6.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4090-4.4-4` | DETOUR object (Class-Num 63) signals one-to-one backup detour LSPs (S4.4) | MAY | 4.4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 4090 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4090-4.1-1`](#rfc4090-4.1-1) FAST_REROUTE object uses Class-Num 205, C-Type 1, object Length 24 (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFastRerouteShortBody`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L49) | unit/verify | unproven | | positive | [`TestEncodeDecodeFastReroute`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L35) | unit/verify | unproven | ### [`RFC4090-4.3-1`](#rfc4090-4.3-1) "Local protection desired" (0x01) set in SESSION_ATTRIBUTE when protection is requested (S4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildPathNoProtection`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L181) | unit/verify | unproven | | positive | [`TestBuildPathIncludesFastReroute`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L166) | unit/verify | unproven | ### [`RFC4090-4.3-2`](#rfc4090-4.3-2) "Node protection desired" (0x10) set in SESSION_ATTRIBUTE for node protection (S4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSessionAttributeEmptyName`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L91) | unit/verify | unproven | | positive | [`TestBuildPathIncludesFastReroute`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L168) | unit/verify | unproven | | positive | [`TestSessionAttributeProtectionFlags`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L63) | unit/verify | unproven | ### [`RFC4090-4.1-2`](#rfc4090-4.1-2) FAST_REROUTE Flags: facility backup (0x02) or one-to-one backup (0x01) set per the requested method (S4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFastRerouteOneToOneMethodFlag`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L833) | unit/verify | unproven | | positive | [`TestBuildPathIncludesFastReroute`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L161) | unit/verify | unproven | ### [`RFC4090-4.4-1`](#rfc4090-4.4-1) PLR sets RRO "local protection available" (0x01) once a backup is armed (S4.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPLRNoBypassWithoutProtection`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L341) | unit/verify | unproven | | positive | [`TestPLRArmsBypass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L323) | unit/verify | unproven | ### [`RFC4090-4.4-2`](#rfc4090-4.4-2) PLR sets RRO "local protection in use" (0x02) once traffic is on the backup (S4.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRROProtectionFlagsReflectState`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L849) | unit/verify | unproven | | positive | [`TestRROProtectionFlagsReflectState`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L857) | unit/verify | unproven | ### [`RFC4090-4.4-3`](#rfc4090-4.4-3) PLR sets RRO "node protection" (0x08) when the backup protects the next node (S4.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRROProtectionFlagsReflectState`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L851) | unit/verify | unproven | | positive | [`TestRROProtectionFlagsReflectState`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L862) | unit/verify | unproven | ### [`RFC4090-6.5-1`](#rfc4090-6.5-1) On local repair the PLR sends PathErr Error Code 25 (Notify), Error Value sub-code 3 (Tunnel locally repaired) toward the head-end (S6.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLocalRepairFallsBackToTeardown`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L469) | unit/verify | unproven | | positive | [`TestLocalRepairSendsNotify`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L447) | unit/verify | unproven | ### [`RFC4090-6.5-2`](#rfc4090-6.5-2) On local repair the PLR MUST NOT tear down the protected LSP (S6.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLocalRepairFallsBackToTeardown`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L465) | unit/verify | unproven | | positive | [`TestLocalRepairSwitchesFIB`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L421) | unit/verify | unproven | ### [`RFC4090-3.2-1`](#rfc4090-3.2-1) Facility backup pushes the bypass label on top of the protected LSP label (label stacking) (S3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLocalRepairFallsBackToTeardown`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L462) | unit/verify | unproven | | positive | [`TestLocalRepairSwitchesFIB`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L417) | unit/verify | unproven | ### [`RFC4090-3.2-2`](#rfc4090-3.2-2) The merge point is the NHOP for link protection and the NNHOP for node protection (S3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNodeProtectionNeedsNodeBypass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L791) | unit/verify | unproven | | positive | [`TestNodeProtectionLocalRepair`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L750) | unit/verify | unproven | | positive | [`TestPLRArmsBypass`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L307) | unit/verify | unproven | ### [`RFC4090-4.2-1`](#rfc4090-4.2-1) An LSR that does not support the DETOUR object MUST reject any Path message containing a DETOUR object and send a PathErr to notify the PLR, generated as [RSVP] specifies for unknown objects with a Class-Num of the form "0bbbbbbb" (S4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEnginePathWithDetourRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L904) | unit/verify | unproven | | positive | [`TestEnginePathWithDetourRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/rsvpte/frr_test.go#L885) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 4090, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4090, so its obligations are stated where they were written. --- ### Page: RFC 4213 - Basic Transition Mechanisms for IPv6 Hosts and Routers https://ze-software.net/quality/rfc-compliance/rfc4213/ # RFC 4213 - Basic Transition Mechanisms for IPv6 Hosts and Routers No row in the public ledger. Every requirement this repository extracted from RFC 4213, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 23 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 23 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 23 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 23 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 23 | of 47 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 23 | of 23 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 23 of 23 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 23 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 23 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 23 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 47 | | Gated MUST-level | 23 | | Not applicable, so out of scope | 23 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4213.md` | | Requirement shard | `rfc/requirements/rfc4213.md` | | RFC text | `rfc/full/rfc4213.txt` | ## Enrolment Enrolled: Basic Transition Mechanisms for IPv6 Hosts and Routers (RFC 4213): dual-stack plus configured 6in4 tunnels (protocol 41). All 23 gated MUSTs not-applicable -- ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go, Proto = IPPROTO_IPV6) and carries no sit VPP backend (netlink-only). The kernel sit module owns the 6in4 datapath (encapsulation, decapsulation, outer-source verification, MTU/fragmentation, link-local assignment, neighbor discovery); the two DNS section 2.2 obligations belong to a host stub-resolver library ze does not provide. Same delegation pattern as enrolled RFC 2003 and RFC 2473 ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 4213. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 23 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **23** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (23):** [`RFC4213-2.2-1`](#rfc4213-2.2-1), [`RFC4213-2.2-2`](#rfc4213-2.2-2), [`RFC4213-3.2-1`](#rfc4213-3.2-1), [`RFC4213-3.2-2`](#rfc4213-3.2-2), [`RFC4213-3.2.1-1`](#rfc4213-3.2.1-1), [`RFC4213-3.2.1-2`](#rfc4213-3.2.1-2), [`RFC4213-3.2.1-3`](#rfc4213-3.2.1-3), [`RFC4213-3.2.1-4`](#rfc4213-3.2.1-4), [`RFC4213-3.2-3`](#rfc4213-3.2-3), [`RFC4213-3.6-1`](#rfc4213-3.6-1), [`RFC4213-3.6-2`](#rfc4213-3.6-2), [`RFC4213-3.6-3`](#rfc4213-3.6-3), [`RFC4213-3.6-4`](#rfc4213-3.6-4), [`RFC4213-3.6-5`](#rfc4213-3.6-5), [`RFC4213-3.6-6`](#rfc4213-3.6-6), [`RFC4213-3.6-7`](#rfc4213-3.6-7), [`RFC4213-3.7-1`](#rfc4213-3.7-1), [`RFC4213-3.8-1`](#rfc4213-3.8-1), [`RFC4213-3.8-2`](#rfc4213-3.8-2), [`RFC4213-5-1`](#rfc4213-5-1), [`RFC4213-5-2`](#rfc4213-5-2), [`RFC4213-5-3`](#rfc4213-5-3), [`RFC4213-5-4`](#rfc4213-5-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4213-2.2-1` | DNS resolver libraries on IPv6/IPv4 nodes MUST be capable of handling both AAAA and A records (§2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is a routing daemon, not a host stub-resolver library that applications call for dual-stack connection establishment; its resolve component exposes distinct operator-facing A and AAAA query verbs (ResolveA/ResolveAAAA, internal/component/resolve/dns/resolver.go:224), not a merged getaddrinfo that returns both families unfiltered, so this resolver-library obligation has no ze code path | | `RFC4213-2.2-2` | If the application has requested both address families, the resolver library MUST NOT filter out any records (§2.2) | MUST NOT | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is a routing daemon, not a host stub-resolver library that applications call for dual-stack connection establishment; its resolve component exposes distinct operator-facing A and AAAA query verbs (ResolveA/ResolveAAAA, internal/component/resolve/dns/resolver.go:224), not a merged getaddrinfo that returns both families unfiltered, so this resolver-library obligation has no ze code path | | `RFC4213-3.2-1` | Encapsulator MUST NOT treat the tunnel as having an MTU of 64 kilobytes; must use static or dynamic MTU determination (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module owns the 6in4 datapath and ze runs no per-packet encapsulation/decapsulation code path (proof-of-absence: no 6in4 encap/decap producer outside the netlink config across internal/*.go), so this tunnel-MTU determination obligation has no ze code path | | `RFC4213-3.2-2` | The naive scheme of viewing the tunnel as a very large MTU link MUST NOT be used (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module owns the 6in4 datapath and ze runs no per-packet encapsulation/decapsulation code path (proof-of-absence: no 6in4 encap/decap producer outside the netlink config across internal/*.go), so this tunnel-MTU determination obligation has no ze code path | | `RFC4213-3.2.1-1` | Static tunnel MTU: by default, the MTU MUST be between 1280 and 1480 bytes inclusive (§3.2.1) | MUST | 3.2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module derives and owns the static tunnel MTU; ze selects no tunnel MTU value and runs no per-packet encapsulation code path, so this static-MTU-range obligation has no ze code path | | `RFC4213-3.2.1-2` | If the default static MTU is not 1280 bytes, the implementation MUST have a configuration knob to change it (§3.2.1) | MUST | 3.2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the antecedent never fires in ze -- ze selects no static tunnel MTU default; it programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220), and the kernel sit module owns the tunnel MTU derivation. VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only), so this static-MTU-knob obligation has no ze code path | | `RFC4213-3.2.1-3` | IPv4 reassembly and IPv6 MRU requirements MUST be supported by all decapsulators (§3.2.1) | MUST | 3.2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module owns IPv4 reassembly and the IPv6 MRU on the tunnel; ze runs no per-packet decapsulation code path, so this reassembly/MRU obligation has no ze code path | | `RFC4213-3.2.1-4` | When using static tunnel MTU, the Don't Fragment bit MUST NOT be set in the encapsulating IPv4 header (§3.2.1) | MUST NOT | 3.2.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41; the PMtuDisc flag is set at tunnel_linux.go:234-238 but the DF bit on the wire is written by the kernel sit datapath). VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). ze runs no per-packet encapsulation code path, so this outer-header DF obligation has no ze code path | | `RFC4213-3.2-3` | Encapsulator MUST NOT treat the tunnel as an interface with an MTU of 64 kilobytes (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module owns the 6in4 datapath and ze runs no per-packet encapsulation/decapsulation code path (proof-of-absence: no 6in4 encap/decap producer outside the netlink config across internal/*.go), so this tunnel-MTU determination obligation has no ze code path | | `RFC4213-3.6-1` | The decapsulator MUST verify that the tunnel source address matches the configured remote endpoint (anti-spoofing) (§3.6) | MUST | 3.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, setting the configured Remote endpoint); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath performs the per-packet outer-source verification against that configured remote; ze runs no per-packet decapsulation code path, so this decapsulation source-verification obligation has no ze code path | | `RFC4213-3.6-2` | Packets for which the IPv4 source address does not match MUST be discarded (§3.6) | MUST | 3.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, setting the configured Remote endpoint); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath discards packets whose outer IPv4 source does not match; ze runs no per-packet decapsulation code path, so this discard obligation has no ze code path | | `RFC4213-3.6-3` | The decapsulator MUST be capable of having an IPv6 MRU of at least max(1500 bytes, largest IPv6 interface MTU) on tunnel interfaces (§3.6) | MUST | 3.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath owns the IPv6 MRU on the tunnel; ze runs no per-packet decapsulation code path, so this MRU obligation has no ze code path | | `RFC4213-3.6-4` | The decapsulator MUST be capable of reassembling an IPv4 packet up to max(1500 bytes, largest IPv4 interface MTU) (§3.6) | MUST | 3.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath owns IPv4 reassembly on the tunnel; ze runs no per-packet decapsulation code path, so this reassembly obligation has no ze code path | | `RFC4213-3.6-5` | Tunnel reassembly buffer MUST NOT be set below the required minimum (§3.6) | MUST NOT | 3.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath sizes the tunnel reassembly buffer; ze exposes no such buffer and runs no per-packet decapsulation code path, so this reassembly-buffer obligation has no ze code path | | `RFC4213-3.6-6` | When reconstructing the IPv6 packet, the length MUST be determined from the IPv6 payload length, not the IPv4 length (§3.6) | MUST | 3.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath reconstructs the inner IPv6 packet; ze runs no per-packet decapsulation code path, so this length-reconstruction obligation has no ze code path | | `RFC4213-3.6-7` | After decapsulation, the node MUST silently discard packets with invalid IPv6 source addresses (§3.6) | MUST | 3.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath silently discards inner packets with invalid IPv6 source addresses; ze runs no per-packet decapsulation code path, so this source-filtering obligation has no ze code path | | `RFC4213-3.7-1` | Configured tunnels MUST have link-local addresses (§3.7) | MUST | 3.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel assigns the IPv6 link-local address to the sit netdev when it comes up; ze writes no link-local address on the tunnel, so this link-local obligation has no ze code path | | `RFC4213-3.8-1` | Configured tunnel implementations MUST at least accept and respond to NUD probe packets (§3.8) | MUST | 3.8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel neighbor-discovery datapath accepts and responds to NUD probes on the tunnel; ze runs no per-packet ND code path on tunnels, so this NUD obligation has no ze code path | | `RFC4213-3.8-2` | The receiver MUST silently ignore the content of any Source/Target Link Layer Address options received on the tunnel link (§3.8) | MUST | 3.8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel neighbor-discovery datapath processes (and ignores) SLLA/TLLA options on the tunnel link; ze runs no per-packet ND code path on tunnels, so this option-handling obligation has no ze code path | | `RFC4213-5-1` | IPv4 source address of the packet MUST be the same as configured for the tunnel end-point (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, setting the configured Remote endpoint); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath enforces the outer IPv4 source against the configured endpoint per packet; ze runs no per-packet decapsulation code path, so this source-verification obligation has no ze code path | | `RFC4213-5-2` | IPv6 packets with obviously invalid source addresses received from the tunnel MUST be discarded (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath discards inner IPv6 packets with invalid source addresses; ze runs no per-packet decapsulation code path, so this source-filtering obligation has no ze code path | | `RFC4213-5-3` | An implementation MUST treat interfaces to different links as separate (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs the sit tunnel as its own distinct netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, one link per configured tunnel); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel forwarding datapath keeps per-interface scope and treats the tunnel and native links as separate; ze runs no per-packet forwarding code path, so this per-interface separation obligation has no ze code path | | `RFC4213-5-4` | Packets failing tunnel source address verification MUST be discarded (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, setting the configured Remote endpoint); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath discards packets that fail outer-source verification; ze runs no per-packet decapsulation code path, so this discard obligation has no ze code path | | `RFC4213-3.2.1-5` | Static tunnel MTU SHOULD be 1280 bytes by default (§3.2.1) | SHOULD | 3.2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.2-4` | If both static and dynamic MTU mechanisms are implemented, the choice SHOULD be configurable per tunnel endpoint (§3.2) | SHOULD | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.2.2-1` | If dynamic MTU is implemented, it SHOULD have the behavior described in the document (§3.2.2) | SHOULD | 3.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.2.2-2` | The encapsulator SHOULD employ the specified algorithm for dynamic MTU determination (§3.2.2) | SHOULD | 3.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.5-1` | It SHOULD be possible to administratively specify the source address of a tunnel (§3.5) | SHOULD | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-8` | An ICMP message SHOULD NOT be generated when discarding packets that fail source address verification (§3.6) | SHOULD NOT | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-9` | The list of invalid IPv6 source addresses SHOULD include at least multicast, loopback, IPv4-compatible, and IPv4-mapped addresses (§3.6) | SHOULD | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.8-3` | Implementations SHOULD send NUD probe packets to detect tunnel failures (§3.8) | SHOULD | 3.8 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.8-4` | The sender of Neighbor Discovery packets SHOULD NOT include Source/Target Link Layer Address options on the tunnel link (§3.8) | SHOULD NOT | 3.8 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-5-5` | An ICMP error message SHOULD NOT be generated for packets failing tunnel source verification (§5) | SHOULD NOT | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-2.2-3` | The applications SHOULD be able to specify whether they want IPv4, IPv6, or both records (§2.2) | SHOULD | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-10` | Packets caught by optional RPF check SHOULD be discarded (§3.6) | SHOULD | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-11` | An ICMP message SHOULD NOT be generated for packets caught by the RPF check (§3.6) | SHOULD NOT | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-12` | IPv4 ingress filtering (RPF check), if done, is RECOMMENDED to be disabled by default (§3.6) | RECOMMENDED | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-13` | Implementations SHOULD provide a single knob to enable strict ingress filtering toward edge networks (§3.6) | RECOMMENDED | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-2-1` | IPv6/IPv4 nodes MAY provide a configuration switch to disable either their IPv4 or IPv6 stack (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-2.2-4` | The resolver library MAY order results to influence IP version preference (§2.2) | MAY | 2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.3-1` | Implementations MAY provide a mechanism to allow the administrator to configure the IPv4 TTL (§3.3) | MAY | 3.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.4-1` | Encapsulator MAY extract the encapsulated IPv6 packet to generate ICMPv6 errors (§3.4) | MAY | 3.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.4-2` | Originating node MAY send an ICMPv6 "unreachable" error to the IPv6 source (§3.4) | MAY | 3.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-14` | Implementation MAY perform IPv4 ingress filtering (RPF check) on tunnel packets (§3.6) | MAY | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-15` | Implementation MAY have a configuration knob to set larger tunnel reassembly buffers (§3.6) | MAY | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.6-16` | If the implementation normally sends ICMP for unknown protocols, such an error message MAY be sent (§3.6, §5) | MAY | 3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4213-3.2.2-3` | Implementations MAY use dynamic MTU determination (§3.2.2) | MAY | 3.2.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4213-2.2-1`](#rfc4213-2.2-1) DNS resolver libraries on IPv6/IPv4 nodes MUST be capable of handling both AAAA and A records (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze is a routing daemon, not a host stub-resolver library that applications call for dual-stack connection establishment; its resolve component exposes distinct operator-facing A and AAAA query verbs (ResolveA/ResolveAAAA, internal/component/resolve/dns/resolver.go:224), not a merged getaddrinfo that returns both families unfiltered, so this resolver-library obligation has no ze code path | | [`RFC4213-2.2-2`](#rfc4213-2.2-2) If the application has requested both address families, the resolver library MUST NOT filter out any records (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze is a routing daemon, not a host stub-resolver library that applications call for dual-stack connection establishment; its resolve component exposes distinct operator-facing A and AAAA query verbs (ResolveA/ResolveAAAA, internal/component/resolve/dns/resolver.go:224), not a merged getaddrinfo that returns both families unfiltered, so this resolver-library obligation has no ze code path | | [`RFC4213-3.2-1`](#rfc4213-3.2-1) Encapsulator MUST NOT treat the tunnel as having an MTU of 64 kilobytes; must use static or dynamic MTU determination (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module owns the 6in4 datapath and ze runs no per-packet encapsulation/decapsulation code path (proof-of-absence: no 6in4 encap/decap producer outside the netlink config across internal/*.go), so this tunnel-MTU determination obligation has no ze code path | | [`RFC4213-3.2-2`](#rfc4213-3.2-2) The naive scheme of viewing the tunnel as a very large MTU link MUST NOT be used (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module owns the 6in4 datapath and ze runs no per-packet encapsulation/decapsulation code path (proof-of-absence: no 6in4 encap/decap producer outside the netlink config across internal/*.go), so this tunnel-MTU determination obligation has no ze code path | | [`RFC4213-3.2.1-1`](#rfc4213-3.2.1-1) Static tunnel MTU: by default, the MTU MUST be between 1280 and 1480 bytes inclusive (§3.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module derives and owns the static tunnel MTU; ze selects no tunnel MTU value and runs no per-packet encapsulation code path, so this static-MTU-range obligation has no ze code path | | [`RFC4213-3.2.1-2`](#rfc4213-3.2.1-2) If the default static MTU is not 1280 bytes, the implementation MUST have a configuration knob to change it (§3.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: the antecedent never fires in ze -- ze selects no static tunnel MTU default; it programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220), and the kernel sit module owns the tunnel MTU derivation. VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only), so this static-MTU-knob obligation has no ze code path | | [`RFC4213-3.2.1-3`](#rfc4213-3.2.1-3) IPv4 reassembly and IPv6 MRU requirements MUST be supported by all decapsulators (§3.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module owns IPv4 reassembly and the IPv6 MRU on the tunnel; ze runs no per-packet decapsulation code path, so this reassembly/MRU obligation has no ze code path | | [`RFC4213-3.2.1-4`](#rfc4213-3.2.1-4) When using static tunnel MTU, the Don't Fragment bit MUST NOT be set in the encapsulating IPv4 header (§3.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41; the PMtuDisc flag is set at tunnel_linux.go:234-238 but the DF bit on the wire is written by the kernel sit datapath). VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). ze runs no per-packet encapsulation code path, so this outer-header DF obligation has no ze code path | | [`RFC4213-3.2-3`](#rfc4213-3.2-3) Encapsulator MUST NOT treat the tunnel as an interface with an MTU of 64 kilobytes (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit module owns the 6in4 datapath and ze runs no per-packet encapsulation/decapsulation code path (proof-of-absence: no 6in4 encap/decap producer outside the netlink config across internal/*.go), so this tunnel-MTU determination obligation has no ze code path | | [`RFC4213-3.6-1`](#rfc4213-3.6-1) The decapsulator MUST verify that the tunnel source address matches the configured remote endpoint (anti-spoofing) (§3.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, setting the configured Remote endpoint); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath performs the per-packet outer-source verification against that configured remote; ze runs no per-packet decapsulation code path, so this decapsulation source-verification obligation has no ze code path | | [`RFC4213-3.6-2`](#rfc4213-3.6-2) Packets for which the IPv4 source address does not match MUST be discarded (§3.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, setting the configured Remote endpoint); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath discards packets whose outer IPv4 source does not match; ze runs no per-packet decapsulation code path, so this discard obligation has no ze code path | | [`RFC4213-3.6-3`](#rfc4213-3.6-3) The decapsulator MUST be capable of having an IPv6 MRU of at least max(1500 bytes, largest IPv6 interface MTU) on tunnel interfaces (§3.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath owns the IPv6 MRU on the tunnel; ze runs no per-packet decapsulation code path, so this MRU obligation has no ze code path | | [`RFC4213-3.6-4`](#rfc4213-3.6-4) The decapsulator MUST be capable of reassembling an IPv4 packet up to max(1500 bytes, largest IPv4 interface MTU) (§3.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath owns IPv4 reassembly on the tunnel; ze runs no per-packet decapsulation code path, so this reassembly obligation has no ze code path | | [`RFC4213-3.6-5`](#rfc4213-3.6-5) Tunnel reassembly buffer MUST NOT be set below the required minimum (§3.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath sizes the tunnel reassembly buffer; ze exposes no such buffer and runs no per-packet decapsulation code path, so this reassembly-buffer obligation has no ze code path | | [`RFC4213-3.6-6`](#rfc4213-3.6-6) When reconstructing the IPv6 packet, the length MUST be determined from the IPv6 payload length, not the IPv4 length (§3.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath reconstructs the inner IPv6 packet; ze runs no per-packet decapsulation code path, so this length-reconstruction obligation has no ze code path | | [`RFC4213-3.6-7`](#rfc4213-3.6-7) After decapsulation, the node MUST silently discard packets with invalid IPv6 source addresses (§3.6) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath silently discards inner packets with invalid IPv6 source addresses; ze runs no per-packet decapsulation code path, so this source-filtering obligation has no ze code path | | [`RFC4213-3.7-1`](#rfc4213-3.7-1) Configured tunnels MUST have link-local addresses (§3.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel assigns the IPv6 link-local address to the sit netdev when it comes up; ze writes no link-local address on the tunnel, so this link-local obligation has no ze code path | | [`RFC4213-3.8-1`](#rfc4213-3.8-1) Configured tunnel implementations MUST at least accept and respond to NUD probe packets (§3.8) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel neighbor-discovery datapath accepts and responds to NUD probes on the tunnel; ze runs no per-packet ND code path on tunnels, so this NUD obligation has no ze code path | | [`RFC4213-3.8-2`](#rfc4213-3.8-2) The receiver MUST silently ignore the content of any Source/Target Link Layer Address options received on the tunnel link (§3.8) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel neighbor-discovery datapath processes (and ignores) SLLA/TLLA options on the tunnel link; ze runs no per-packet ND code path on tunnels, so this option-handling obligation has no ze code path | | [`RFC4213-5-1`](#rfc4213-5-1) IPv4 source address of the packet MUST be the same as configured for the tunnel end-point (§5) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, setting the configured Remote endpoint); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath enforces the outer IPv4 source against the configured endpoint per packet; ze runs no per-packet decapsulation code path, so this source-verification obligation has no ze code path | | [`RFC4213-5-2`](#rfc4213-5-2) IPv6 packets with obviously invalid source addresses received from the tunnel MUST be discarded (§5) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, Proto = IPPROTO_IPV6 / protocol 41); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath discards inner IPv6 packets with invalid source addresses; ze runs no per-packet decapsulation code path, so this source-filtering obligation has no ze code path | | [`RFC4213-5-3`](#rfc4213-5-3) An implementation MUST treat interfaces to different links as separate (§5) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs the sit tunnel as its own distinct netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, one link per configured tunnel); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel forwarding datapath keeps per-interface scope and treats the tunnel and native links as separate; ze runs no per-packet forwarding code path, so this per-interface separation obligation has no ze code path | | [`RFC4213-5-4`](#rfc4213-5-4) Packets failing tunnel source address verification MUST be discarded (§5) | no test | no test carries this requirement id; annotated {not-applicable}: ze programs only the sit tunnel netdev via netlink buildSittun (internal/plugins/iface/netlink/tunnel_linux.go:220, setting the configured Remote endpoint); VPP carries no sit backend (internal/plugins/iface/vpp/tunnel.go:7, netlink-only). The kernel sit datapath discards packets that fail outer-source verification; ze runs no per-packet decapsulation code path, so this discard obligation has no ze code path | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4213-2.2-1`](#rfc4213-2.2-1) DNS resolver libraries on IPv6/IPv4 nodes MUST be capable of handling both AAAA and A records (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-2.2-1, so no unit is bound to it. ### [`RFC4213-2.2-2`](#rfc4213-2.2-2) If the application has requested both address families, the resolver library MUST NOT filter out any records (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-2.2-2, so no unit is bound to it. ### [`RFC4213-3.2-1`](#rfc4213-3.2-1) Encapsulator MUST NOT treat the tunnel as having an MTU of 64 kilobytes; must use static or dynamic MTU determination (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.2-1, so no unit is bound to it. ### [`RFC4213-3.2-2`](#rfc4213-3.2-2) The naive scheme of viewing the tunnel as a very large MTU link MUST NOT be used (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.2-2, so no unit is bound to it. ### [`RFC4213-3.2.1-1`](#rfc4213-3.2.1-1) Static tunnel MTU: by default, the MTU MUST be between 1280 and 1480 bytes inclusive (§3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.2.1-1, so no unit is bound to it. ### [`RFC4213-3.2.1-2`](#rfc4213-3.2.1-2) If the default static MTU is not 1280 bytes, the implementation MUST have a configuration knob to change it (§3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.2.1-2, so no unit is bound to it. ### [`RFC4213-3.2.1-3`](#rfc4213-3.2.1-3) IPv4 reassembly and IPv6 MRU requirements MUST be supported by all decapsulators (§3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.2.1-3, so no unit is bound to it. ### [`RFC4213-3.2.1-4`](#rfc4213-3.2.1-4) When using static tunnel MTU, the Don't Fragment bit MUST NOT be set in the encapsulating IPv4 header (§3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.2.1-4, so no unit is bound to it. ### [`RFC4213-3.2-3`](#rfc4213-3.2-3) Encapsulator MUST NOT treat the tunnel as an interface with an MTU of 64 kilobytes (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.2-3, so no unit is bound to it. ### [`RFC4213-3.6-1`](#rfc4213-3.6-1) The decapsulator MUST verify that the tunnel source address matches the configured remote endpoint (anti-spoofing) (§3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.6-1, so no unit is bound to it. ### [`RFC4213-3.6-2`](#rfc4213-3.6-2) Packets for which the IPv4 source address does not match MUST be discarded (§3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.6-2, so no unit is bound to it. ### [`RFC4213-3.6-3`](#rfc4213-3.6-3) The decapsulator MUST be capable of having an IPv6 MRU of at least max(1500 bytes, largest IPv6 interface MTU) on tunnel interfaces (§3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.6-3, so no unit is bound to it. ### [`RFC4213-3.6-4`](#rfc4213-3.6-4) The decapsulator MUST be capable of reassembling an IPv4 packet up to max(1500 bytes, largest IPv4 interface MTU) (§3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.6-4, so no unit is bound to it. ### [`RFC4213-3.6-5`](#rfc4213-3.6-5) Tunnel reassembly buffer MUST NOT be set below the required minimum (§3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.6-5, so no unit is bound to it. ### [`RFC4213-3.6-6`](#rfc4213-3.6-6) When reconstructing the IPv6 packet, the length MUST be determined from the IPv6 payload length, not the IPv4 length (§3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.6-6, so no unit is bound to it. ### [`RFC4213-3.6-7`](#rfc4213-3.6-7) After decapsulation, the node MUST silently discard packets with invalid IPv6 source addresses (§3.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.6-7, so no unit is bound to it. ### [`RFC4213-3.7-1`](#rfc4213-3.7-1) Configured tunnels MUST have link-local addresses (§3.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.7-1, so no unit is bound to it. ### [`RFC4213-3.8-1`](#rfc4213-3.8-1) Configured tunnel implementations MUST at least accept and respond to NUD probe packets (§3.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.8-1, so no unit is bound to it. ### [`RFC4213-3.8-2`](#rfc4213-3.8-2) The receiver MUST silently ignore the content of any Source/Target Link Layer Address options received on the tunnel link (§3.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-3.8-2, so no unit is bound to it. ### [`RFC4213-5-1`](#rfc4213-5-1) IPv4 source address of the packet MUST be the same as configured for the tunnel end-point (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-5-1, so no unit is bound to it. ### [`RFC4213-5-2`](#rfc4213-5-2) IPv6 packets with obviously invalid source addresses received from the tunnel MUST be discarded (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-5-2, so no unit is bound to it. ### [`RFC4213-5-3`](#rfc4213-5-3) An implementation MUST treat interfaces to different links as separate (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-5-3, so no unit is bound to it. ### [`RFC4213-5-4`](#rfc4213-5-4) Packets failing tunnel source address verification MUST be discarded (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4213-5-4, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 4213, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4213, so its obligations are stated where they were written. --- ### Page: RFC 4271 - A Border Gateway Protocol 4 (BGP-4) https://ze-software.net/quality/rfc-compliance/rfc4271/ # RFC 4271 - A Border Gateway Protocol 4 (BGP-4) Partial. Every requirement this repository extracted from RFC 4271, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 75.2% | 76 of 101 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 3.0% | 3 of 101 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 101 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.9% | 2 of 233 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 101 | of 135 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 7 | of 101 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 6.9% | 7 of 101 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 101 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 101 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 14.9% | 15 of 101 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 101 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 135 | | Gated MUST-level | 101 | | Not applicable, so out of scope | 7 | | Declared gaps | 15 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 233 | | Tagged units | 233 | | Recorded audit verdicts | 0 | | Discrimination records | 2 | | Summary | `rfc/short/rfc4271.md` | | Requirement shard | `rfc/requirements/rfc4271.md` | | RFC text | `rfc/full/rfc4271.txt` | ## Enrolment Enrolled: A Border Gateway Protocol 4 (BGP-4): Section 5.1.4 requires a local-configuration mechanism that removes MULTI_EXIT_DISC from a route. Ze exposes the full obligation as `modify NAME { del { med; } }` on an import chain, before Decision Process phases 1 and 2. ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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`](#rfc4271-8.2.2-1)), the ConnectRetryTimer zeroed ([`RFC4271-8.2.2-2`](#rfc4271-8.2.2-2)), the BGP resources released ([`RFC4271-8.2.2-3`](#rfc4271-8.2.2-3)), the TCP connection dropped ([`RFC4271-8.2.2-4`](#rfc4271-8.2.2-4)) and the state changed to Idle ([`RFC4271-8.2.2-5`](#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`](#rfc4271-8.2.2-18), [`internal/component/bgp/reactor/reactor.go`](https://github.com/ze-software/ze/blob/main/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`](#rfc4271-5.1.4-1), `applyFactsMED`, [`internal/component/bgp/reactor/forward_med.go`](https://github.com/ze-software/ze/blob/main/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`](#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`](#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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/filter_delta.go); `appendMEDRemove`, [`internal/component/bgp/plugins/filter_modify/filter_modify.go`](https://github.com/ze-software/ze/blob/main/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`](#rfc4271-5-3), `publishBase`, [`internal/component/bgp/reactor/session_validation.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_validation.go), and `SetPartialOnUnrecognizedTransitive`, [`internal/core/bgp/attribute/partial.go`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/partial.go)), while a well-known attribute, an optional non-transitive one and an attribute ze does recognize keep the bit the sender chose, and a bit an earlier AS set is never cleared - the Section 5.1.3 NEXT_HOP loop rules, which are the two halves of one hazard: on egress a route is withheld from the peer whose OWN address the NEXT_HOP names, whether ze RELAYS that route or ORIGINATES it ([`RFC4271-5.1.3-1`](#rfc4271-5.1.3-1)). A relayed route is answered on both forward rails by `egressNextHopIsPeerOwn` ([`internal/component/bgp/reactor/forward_next_hop.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/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`](#rfc4271-5.1.3-2), `gatherCandidatesLocked`, [`internal/component/bgp/plugins/rib/rib_commands.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4271.md). **What the ledger says remains** Fifteen MUST/SHALL-level gaps, each annotated in [`rfc/short/rfc4271.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4271.md). - **Header error reporting:** [`RFC4271-6.1-1`](#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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/header.go) and [`internal/component/bgp/reactor/session_read.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers.go)). - **OPEN error reporting:** [`RFC4271-6.2-3`](#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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers.go); every other OPEN error rail does send Error Code 2). - **Next-hop resolvability:** [`RFC4271-3.1-2`](#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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_commands.go)). - **Adj-RIB-Out:** [`RFC4271-9.2-2`](#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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rs.go)). - **Timers:** [`RFC4271-9.2.1.1-2`](#rfc4271-9.2.1.1-2) and 9.2.1.1-3 (no MinRouteAdvertisementIntervalTimer, [`internal/component/bgp/fsm/timer.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/timer.go)). - **Connections:** [`RFC4271-8.2.1-3`](#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`](https://github.com/ze-software/ze/blob/main/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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 76 | one part of the gated population | | Annotated instead of tested | 25 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **101** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (76):** [`RFC4271-4.1-1`](#rfc4271-4.1-1), [`RFC4271-4.1-2`](#rfc4271-4.1-2), [`RFC4271-4.1-3`](#rfc4271-4.1-3), [`RFC4271-4.3-1`](#rfc4271-4.3-1), [`RFC4271-4.3-2`](#rfc4271-4.3-2), [`RFC4271-4.3-4`](#rfc4271-4.3-4), [`RFC4271-4.4-1`](#rfc4271-4.4-1), [`RFC4271-4.4-2`](#rfc4271-4.4-2), [`RFC4271-6-1`](#rfc4271-6-1), [`RFC4271-4.2-1`](#rfc4271-4.2-1), [`RFC4271-4.2-2`](#rfc4271-4.2-2), [`RFC4271-6.2-1`](#rfc4271-6.2-1), [`RFC4271-6.2-2`](#rfc4271-6.2-2), [`RFC4271-5-1`](#rfc4271-5-1), [`RFC4271-5-2`](#rfc4271-5-2), [`RFC4271-5-3`](#rfc4271-5-3), [`RFC4271-5-4`](#rfc4271-5-4), [`RFC4271-5-5`](#rfc4271-5-5), [`RFC4271-5-6`](#rfc4271-5-6), [`RFC4271-5.1.3-1`](#rfc4271-5.1.3-1), [`RFC4271-5.1.3-2`](#rfc4271-5.1.3-2), [`RFC4271-5.1.3-3`](#rfc4271-5.1.3-3), [`RFC4271-5.1.4-1`](#rfc4271-5.1.4-1), [`RFC4271-5.1.4-4`](#rfc4271-5.1.4-4), [`RFC4271-5.1.4-2`](#rfc4271-5.1.4-2), [`RFC4271-5.1.5-1`](#rfc4271-5.1.5-1), [`RFC4271-5.1.5-2`](#rfc4271-5.1.5-2), [`RFC4271-5.1.5-3`](#rfc4271-5.1.5-3), [`RFC4271-5.1.5-4`](#rfc4271-5.1.5-4), [`RFC4271-6.3-1`](#rfc4271-6.3-1), [`RFC4271-6.7-1`](#rfc4271-6.7-1), [`RFC4271-8.2.1-1`](#rfc4271-8.2.1-1), [`RFC4271-8.2.1-2`](#rfc4271-8.2.1-2), [`RFC4271-8.2.2-1`](#rfc4271-8.2.2-1), [`RFC4271-8.2.2-2`](#rfc4271-8.2.2-2), [`RFC4271-8.2.2-3`](#rfc4271-8.2.2-3), [`RFC4271-8.2.2-4`](#rfc4271-8.2.2-4), [`RFC4271-8.2.2-5`](#rfc4271-8.2.2-5), [`RFC4271-8.2.2-7`](#rfc4271-8.2.2-7), [`RFC4271-8.2.2-8`](#rfc4271-8.2.2-8), [`RFC4271-8.2.2-9`](#rfc4271-8.2.2-9), [`RFC4271-8.2.2-10`](#rfc4271-8.2.2-10), [`RFC4271-8.2.2-11`](#rfc4271-8.2.2-11), [`RFC4271-8.2.2-12`](#rfc4271-8.2.2-12), [`RFC4271-8.2.2-13`](#rfc4271-8.2.2-13), [`RFC4271-8.2.2-14`](#rfc4271-8.2.2-14), [`RFC4271-8.2.2-15`](#rfc4271-8.2.2-15), [`RFC4271-8.2.2-16`](#rfc4271-8.2.2-16), [`RFC4271-8.2.2-17`](#rfc4271-8.2.2-17), [`RFC4271-8.2.2-18`](#rfc4271-8.2.2-18), [`RFC4271-10-1`](#rfc4271-10-1), [`RFC4271-5.1.2-2`](#rfc4271-5.1.2-2), [`RFC4271-5.1.2-3`](#rfc4271-5.1.2-3), [`RFC4271-5.1.5-5`](#rfc4271-5.1.5-5), [`RFC4271-6.7-4`](#rfc4271-6.7-4), [`RFC4271-6.8-1`](#rfc4271-6.8-1), [`RFC4271-6.8-2`](#rfc4271-6.8-2), [`RFC4271-9-1`](#rfc4271-9-1), [`RFC4271-9-2`](#rfc4271-9-2), [`RFC4271-9-3`](#rfc4271-9-3), [`RFC4271-9.1.1-1`](#rfc4271-9.1.1-1), [`RFC4271-9.1.1-2`](#rfc4271-9.1.1-2), [`RFC4271-9.1.2-2`](#rfc4271-9.1.2-2), [`RFC4271-9.1.2-3`](#rfc4271-9.1.2-3), [`RFC4271-9.1.2.1-1`](#rfc4271-9.1.2.1-1), [`RFC4271-9.1.2.2-1`](#rfc4271-9.1.2.2-1), [`RFC4271-9.1.2.2-3`](#rfc4271-9.1.2.2-3), [`RFC4271-9.1.2.2-4`](#rfc4271-9.1.2.2-4), [`RFC4271-9.2-4`](#rfc4271-9.2-4), [`RFC4271-9.2-5`](#rfc4271-9.2-5), [`RFC4271-Security-1`](#rfc4271-security-1), [`RFC4271-9.2-6`](#rfc4271-9.2-6), [`RFC4271-9.2-7`](#rfc4271-9.2-7), [`RFC4271-9.2-8`](#rfc4271-9.2-8), [`RFC4271-9.2-9`](#rfc4271-9.2-9), [`RFC4271-9.2-10`](#rfc4271-9.2-10) **Annotated instead of tested (25):** [`RFC4271-4.3-3`](#rfc4271-4.3-3), [`RFC4271-4.3-5`](#rfc4271-4.3-5), [`RFC4271-5.1.6-1`](#rfc4271-5.1.6-1), [`RFC4271-6.1-1`](#rfc4271-6.1-1), [`RFC4271-6.1-2`](#rfc4271-6.1-2), [`RFC4271-6.1-3`](#rfc4271-6.1-3), [`RFC4271-6.1-4`](#rfc4271-6.1-4), [`RFC4271-6.2-3`](#rfc4271-6.2-3), [`RFC4271-8.2.1-3`](#rfc4271-8.2.1-3), [`RFC4271-3.1-2`](#rfc4271-3.1-2), [`RFC4271-5.1.4-3`](#rfc4271-5.1.4-3), [`RFC4271-5.1.7-1`](#rfc4271-5.1.7-1), [`RFC4271-9.1.2-1`](#rfc4271-9.1.2-1), [`RFC4271-9.1.2-4`](#rfc4271-9.1.2-4), [`RFC4271-9.1.2.1-2`](#rfc4271-9.1.2.1-2), [`RFC4271-9.1.2.2-2`](#rfc4271-9.1.2.2-2), [`RFC4271-9.2-2`](#rfc4271-9.2-2), [`RFC4271-9.2-3`](#rfc4271-9.2-3), [`RFC4271-9.2.1.1-2`](#rfc4271-9.2.1.1-2), [`RFC4271-9.2.2.2-1`](#rfc4271-9.2.2.2-1), [`RFC4271-9.2.2.2-2`](#rfc4271-9.2.2.2-2), [`RFC4271-9.2.2.2-3`](#rfc4271-9.2.2.2-3), [`RFC4271-9.2.2.2-4`](#rfc4271-9.2.2.2-4), [`RFC4271-9.2.2.2-5`](#rfc4271-9.2.2.2-5), [`RFC4271-9.2.1.1-3`](#rfc4271-9.2.1.1-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4271-4.1-1` | Marker field MUST be set to all ones (16 bytes of 0xFF) (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC4271MarkerAllOnesOnSend`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L19). **negative:** `unit/verify` [`TestRFC4271MarkerNotAllOnesRejected`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L42) | | `RFC4271-4.1-2` | Length field MUST have the smallest value required given the rest of the message (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC4271SmallestLengthOnSend`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L62). **negative:** `unit/verify` [`TestRFC4271NonSmallestLengthRejected`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L95) | | `RFC4271-4.1-3` | Message Length MUST be between 19 and 4096 octets (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestRFC4271MessageLengthWithinBounds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L124). **negative:** `unit/verify` [`TestRFC4271MessageLengthOutOfBounds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L150) | | `RFC4271-4.3-1` | For well-known attributes, the Transitive bit MUST be set to 1 (§4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestRFC4271WellKnownAttributesAreTransitive`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L31). **negative:** `unit/verify` [`TestRFC4271WellKnownAttributeErrorsAreCaught`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L500) | | `RFC4271-4.3-2` | Partial bit MUST be set to 0 for well-known and optional non-transitive attributes (§4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestRFC4271PartialBitClearOnSend`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L55). **positive:** `unit/verify` [`TestRFC4271PartialNotSetOnRecognizedOrNonTransitive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1116). **negative:** `unit/verify` [`TestRFC4271PartialBitClearedOnReadvertisedWellKnown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L47). **negative:** `unit/verify` [`TestRFC4271PartialNotStampedOnExcludedClasses`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L170) | | `RFC4271-4.3-3` | Lower-order four bits of attribute flags MUST be zero when sent (§4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestRFC4271AttributeFlagsLowNibbleZeroOnSend`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L80). **negative:** no negative test. **{single-polarity}:** the obligation is on the sender -- the flags octet ze writes must have its low-order four bits zero -- so there is no non-conformant input to reject. The receive-side mirror of the same rule ("MUST be ignored when received") is RFC4271-4.3-4 and is proven both ways there | | `RFC4271-4.3-4` | Lower-order four bits of attribute flags MUST be ignored when received (§4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestRFC4271AttrFlagsLowNibbleIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L232). **negative:** `unit/verify` [`TestRFC4271AttrFlagsHighBitsNotIgnored`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L259) | | `RFC4271-4.4-1` | KEEPALIVE messages MUST NOT be sent more frequently than one per second (§4.4) | MUST NOT | 4.4 | **positive:** `unit/verify` [`TestRFC4271KeepaliveNotFasterThanOnePerSecond`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_test.go#L18). **negative:** `unit/verify` [`TestRFC4271KeepaliveIntervalNeverSubSecond`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_test.go#L50) | | `RFC4271-4.4-2` | If the negotiated Hold Time is zero, periodic KEEPALIVE messages MUST NOT be sent (§4.4) | MUST NOT | 4.4 | **positive:** `unit/verify` [`TestTimersKeepaliveTimer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/timer_test.go#L144). **negative:** `unit/verify` [`TestKeepaliveWithZeroHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/timer_test.go#L411) | | `RFC4271-6-1` | If no Error Subcode is specified, a zero MUST be used (§6) | MUST | 6 | **positive:** `unit/verify` [`TestRFC4271NotificationUnspecifiedSubcodeIsZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L353). **negative:** `unit/verify` [`TestRFC4271NotificationSpecifiedSubcodePreserved`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L376) | | `RFC4271-4.2-1` | Hold Time MUST be either zero or at least three seconds (§4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestOpenValidateHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/open_test.go#L339). **negative:** `unit/verify` [`TestOpenValidateHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/open_test.go#L341). **positive:** `functional/verify` [`open-hold-time-peer-lower-wins.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/open-hold-time-peer-lower-wins.ci#L3) | | `RFC4271-4.2-2` | BGP speaker MUST calculate Hold Timer by using the smaller of its configured Hold Time and the received Hold Time (§4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestNegotiateWith_HoldTimeMinOfBoth`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L45). **negative:** `unit/verify` [`TestNegotiateWith_HoldTimeZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L74) | | `RFC4271-6.2-1` | An implementation MUST reject Hold Time values of one or two seconds (§6.2) | MUST | 6.2 | **positive:** `unit/verify` [`TestOpenValidateHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/open_test.go#L343). **negative:** `unit/verify` [`TestOpenValidateHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/open_test.go#L345) | | `RFC4271-6.2-2` | An implementation that accepts a Hold Time MUST use the negotiated value (§6.2) | MUST | 6.2 | **positive:** `unit/verify` [`TestRFC4271NegotiatedHoldTimeDrivesTimers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L171). **negative:** `unit/verify` [`TestRFC4271LocalHoldTimeNotUsedWhenPeerProposesSmaller`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L192) | | `RFC4271-5-1` | BGP implementations MUST recognize all well-known attributes (§5) | MUST | 5 | **positive:** `unit/verify` [`TestRFC4271WellKnownAttributesAreRecognized`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L463). **negative:** `unit/verify` [`TestRFC4271WellKnownAttributeErrorsAreCaught`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L492) | | `RFC4271-5-2` | Well-known mandatory attributes MUST be included in every UPDATE containing NLRI (§5) | MUST | 5 | **positive:** `unit/verify` [`TestRFC4271WellKnownAttributesAreRecognized`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L466). **negative:** `unit/verify` [`TestRFC4271WellKnownAttributeErrorsAreCaught`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L497) | | `RFC4271-5-3` | Unrecognized transitive optional attributes MUST be passed along with the Partial bit set to 1 (§5) | MUST | 5 | **positive:** `unit/verify` [`TestRFC4271PartialSetOnUnrecognizedTransitiveOptional`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1084). **positive:** `unit/verify` [`TestRFC4271PartialStampedOnUnrecognizedTransitive`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L132). **negative:** `unit/verify` [`TestRFC4271PartialNotSetOnRecognizedOrNonTransitive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1113). **negative:** `unit/verify` [`TestRFC4271PartialNotStampedOnExcludedClasses`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L167). **positive:** `functional/verify` [`rfc4271-partial-unknown-transitive.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/rfc4271-partial-unknown-transitive.ci#L27) | | `RFC4271-5-4` | Partial bit set to 1 by a previous AS MUST NOT be set back to 0 (§5) | MUST NOT | 5 | **positive:** `unit/verify` [`TestRFC4271PartialBitPreservedOnUnknownTransitive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L82). **positive:** `unit/verify` [`TestRFC4271PartialFromPreviousASNeverCleared`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1152). **negative:** `unit/verify` [`TestRFC4271PartialBitSurvivesLengthReframing`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L115). **negative:** `unit/verify` [`TestRFC4271PartialFromPreviousASNotCleared`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L197) | | `RFC4271-5-5` | Unrecognized non-transitive optional attributes MUST be quietly ignored (§5) | MUST | 5 | **positive:** `unit/verify` [`TestRFC4271UnrecognizedNonTransitiveIsNotPassedAlong`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1210). **negative:** `unit/verify` [`TestRFC4271TheNonTransitiveDropSparesEveryOtherClass`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1251) | | `RFC4271-5-6` | Receiver of an UPDATE MUST be prepared to handle path attributes that are out of order (§5) | MUST | 5 | **positive:** `unit/verify` [`TestRFC4271AttributesOutOfOrderAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L293). **negative:** `unit/verify` [`TestRFC4271OutOfOrderDoesNotMaskMalformation`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L323) | | `RFC4271-4.3-5` | A BGP speaker MUST be able to process UPDATE messages with the same prefix in both WITHDRAWN and NLRI (§4.3) | MUST | 4.3 | **positive:** `unit/verify` [`TestRIBInjectSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L119). **positive:** `unit/verify` [`TestRIBPoolPathSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L85). **positive:** `unit/verify` [`TestRIBSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L39). **negative:** no negative test. **{single-polarity}:** the obligation is to ACCEPT a message shape, so there is no non-conformant input to reject -- every UPDATE of this form must be processed, and a negative case would have to assert the absence of an error, which proves nothing (ai/rules/testing.md). The consequence the same paragraph asks for, treating the UPDATE as though WITHDRAWN did not contain the prefix, is RFC4271-4.3-7 and is proven by the same test | | `RFC4271-5.1.3-1` | A route originated by a BGP speaker SHALL NOT be advertised to a peer using that peer's address as NEXT_HOP (§5.1.3) | SHALL NOT | 5.1.3 | **positive:** `unit/verify` [`TestEgressNextHopIsPeerOwnReadsTheRewrittenAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L282). **positive:** `unit/verify` [`TestForwardRSWithholdsRouteWhoseNextHopIsTheClientsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L252). **positive:** `unit/verify` [`TestForwardWithdrawsFromDestinationWhoseNextHopIsItsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L217). **positive:** `unit/verify` [`TestForwardWithholdsRouteWhoseNextHopIsTheDestinationsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L181). **positive:** `unit/verify` [`TestSendAnnounceWithholdsRouteWithPeerOwnNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L464). **positive:** `unit/verify` [`TestSendUpdateWithholdsOriginatedRouteWithPeerOwnNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L425). **negative:** `unit/verify` [`TestEgressNextHopIsPeerOwnReadsTheRewrittenAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L286). **negative:** `unit/verify` [`TestForwardRSWithholdsRouteWhoseNextHopIsTheClientsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L255). **negative:** `unit/verify` [`TestForwardWithholdsRouteWhoseNextHopIsTheDestinationsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L186). **negative:** `unit/verify` [`TestSendAnnounceWithholdsRouteWithPeerOwnNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L466). **negative:** `unit/verify` [`TestSendUpdateWithholdsOriginatedRouteWithPeerOwnNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L429). **positive:** `functional/verify` [`originated-nexthop-peer-own.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/originated-nexthop-peer-own.ci#L7). **negative:** `functional/verify` [`originated-nexthop-peer-own.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/originated-nexthop-peer-own.ci#L10). **positive:** `interop/nightly` [`checkSelfNextHopWithheld`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L961). **negative:** `interop/nightly` [`checkSelfNextHopWithheld`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L962) | | `RFC4271-5.1.3-2` | A BGP speaker SHALL NOT install a route with itself as the next hop (§5.1.3) | SHALL NOT | 5.1.3 | **positive:** `unit/verify` [`TestRFC4271SelfNextHopRouteIsNotInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L43). **positive:** `unit/verify` [`TestRFC4271SelfNextHopSetComesFromPeerEvents`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L122). **negative:** `unit/verify` [`TestRFC4271SelfNextHopDoesNotShadowASoundAlternative`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L89). **negative:** `unit/verify` [`TestRFC4271SelfNextHopRouteIsNotInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L47). **negative:** `unit/verify` [`TestRFC4271SelfNextHopSetComesFromPeerEvents`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L126) | | `RFC4271-5.1.3-3` | A BGP speaker MUST be able to support disabling advertisement of third-party NEXT_HOP attributes (§5.1.3) | MUST | 5.1.3 | **positive:** `unit/verify` [`TestRFC4271ThirdPartyNextHopCanBeDisabled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L341). **negative:** `unit/verify` [`TestRFC4271ThirdPartyNextHopDisableFailsClosed`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L373) | | `RFC4271-5.1.4-1` | MULTI_EXIT_DISC received from a neighboring AS MUST NOT be propagated to other neighboring ASes (§5.1.4) | MUST NOT | 5.1.4 | **positive:** `unit/verify` [`TestForwardSuppressesReceivedMEDToAnotherAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L221). **negative:** `unit/verify` [`TestForwardKeepsFilterSetMED`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L322). **negative:** `unit/verify` [`TestForwardSuppressesReceivedMEDToAnotherAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L226). **negative:** `unit/verify` [`TestForwardWritesLocallySetMED`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L289). **negative:** `unit/verify` [`TestMEDPropagationAllowedTo`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L426). **positive:** `functional/verify` [`med-not-propagated-across-as.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-not-propagated-across-as.ci#L4). **negative:** `functional/verify` [`med-locally-set-reaches-peer.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-locally-set-reaches-peer.ci#L4). **negative:** `functional/verify` [`med-not-propagated-across-as.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-not-propagated-across-as.ci#L8). **positive:** `interop/nightly` [`checkMEDAcrossAS`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L196). **negative:** `interop/nightly` [`checkMEDAcrossAS`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L200) | | `RFC4271-5.1.4-4` | A BGP speaker MUST implement a mechanism (based on local configuration) that allows the MULTI_EXIT_DISC attribute to be removed from a route (§5.1.4) | MUST | 5.1.4 | **positive:** `unit/verify` [`TestMEDRemovalMechanismIsConfigurable`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L468). **positive:** `unit/verify` [`TestParseModifyDefsMEDRemove`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/filter_modify/modify_test.go#L642). **negative:** `unit/verify` [`TestMEDRemovalMechanismIsConfigurable`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L475). **negative:** `unit/verify` [`TestMEDRemoveDirectiveIsValueless`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L568). **negative:** `unit/verify` [`TestParseModifyDefsMEDRemove`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/filter_modify/modify_test.go#L647). **positive:** `functional/verify` [`med-removal-configured.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-configured.ci#L4). **positive:** `interop/nightly` [`checkMEDRemovalConfiguration`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L318). **negative:** `interop/nightly` [`checkMEDRemovalConfiguration`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L322) | | `RFC4271-5.1.4-2` | MULTI_EXIT_DISC removal from routes MUST be done before the route is used in Phase 2 of the decision process (§5.1.4) | MUST | 5.1.4 | **positive:** `unit/verify` [`TestHandleFilterUpdateMEDRemoveIsImportOnly`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/filter_modify/modify_test.go#L715). **positive:** `unit/verify` [`TestMEDRemovalMechanismIsConfigurable`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L480). **negative:** `unit/verify` [`TestHandleFilterUpdateMEDRemoveIsImportOnly`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/filter_modify/modify_test.go#L722). **negative:** `unit/verify` [`TestMEDRemovalMechanismIsConfigurable`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L488). **positive:** `functional/verify` [`med-removal-before-decision.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-before-decision.ci#L4). **positive:** `functional/verify` [`med-removal-configured.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-configured.ci#L8). **negative:** `functional/verify` [`med-removal-before-decision.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-before-decision.ci#L10). **negative:** `functional/verify` [`med-removal-export-refused.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-export-refused.ci#L4) | | `RFC4271-5.1.5-1` | LOCAL_PREF SHALL be included in all UPDATE messages sent to internal peers (§5.1.5) | SHALL | 5.1.5 | **positive:** `unit/verify` [`TestRFC4271LocalPrefIncludedForInternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L81). **negative:** `unit/verify` [`TestAnnounceStripsLocalPrefTowardExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_origin_test.go#L307). **negative:** `unit/verify` [`TestForwardLocalPrefStrippedToExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_local_pref_test.go#L45). **negative:** `unit/verify` [`TestRFC4271LocalPrefOmittedForExternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L107) | | `RFC4271-5.1.5-2` | A BGP speaker MUST NOT include LOCAL_PREF in UPDATE messages sent to external peers (§5.1.5) | MUST NOT | 5.1.5 | **positive:** `unit/verify` [`TestAnnounceStripsLocalPrefTowardExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_origin_test.go#L305). **positive:** `unit/verify` [`TestForwardLocalPrefStripBeatsAFilterSet`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_local_pref_test.go#L113). **positive:** `unit/verify` [`TestForwardLocalPrefStrippedToExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_local_pref_test.go#L41). **positive:** `unit/verify` [`TestRFC4271LocalPrefOmittedForExternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L104). **negative:** `unit/verify` [`TestLocalPrefAllowedToIsTheOnlyAnswer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_local_pref_test.go#L146). **negative:** `unit/verify` [`TestRFC4271LocalPrefIncludedForInternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L83). **positive:** `functional/verify` [`local-pref-strip-ebgp.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/local-pref-strip-ebgp.ci#L14). **negative:** `functional/verify` [`local-pref-strip-ebgp.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/local-pref-strip-ebgp.ci#L19). **positive:** `interop/nightly` [`checkLocalPrefStrip`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L96) | | `RFC4271-5.1.5-3` | If LOCAL_PREF is received over EBGP, it MUST be ignored (§5.1.5) | MUST | 5.1.5 | **positive:** `unit/verify` [`TestRFC4271LocalPrefKeptOnInternalSession`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L566). **negative:** `unit/verify` [`TestRFC4271LocalPrefIgnoredOnExternalSession`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L589) | | `RFC4271-5.1.5-4` | The higher degree of preference (LOCAL_PREF) MUST be preferred (§5.1.5) | MUST | 5.1.5 | **positive:** `unit/verify` [`TestBestPath_LocalPref`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L197). **negative:** `unit/verify` [`TestBestPath_LocalPref`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L199) | | `RFC4271-5.1.6-1` | A BGP speaker that receives a route with the ATOMIC_AGGREGATE attribute MUST NOT make any NLRI of that route more specific when advertising this route to other BGP speakers (§5.1.6) | MUST NOT | 5.1.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the obligation binds the RECEIVER/re-advertiser, and ze is one -- it stores a received ATOMIC_AGGREGATE and copies it through on readvertisement (internal/component/bgp/reactor/peer_rib_routes.go:141) -- but the prohibited act has no producer. `grep -rniE "more specific\|deaggregat\|de-aggregat\|disaggregat" --include=*.go internal/component/bgp/ \| grep -v _test` returns only substring hits inside `encodeAggregatorValue` and `attrCodeAggregator` (internal/component/bgp/reactor/filter_delta.go:294,396, internal/component/bgp/message/rfc7606.go:64,421); no code path splits a prefix. Both readvertisement encoders write the stored route's own prefix verbatim through nlri.WriteNLRI (internal/component/bgp/reactor/peer_rib_routes.go:103-104), so the advertised NLRI is byte-identical to what was received and can be neither more nor less specific. With no length-altering producer there is no behavior to exercise in either polarity | | `RFC4271-6.1-1` | All header errors MUST be indicated by sending NOTIFICATION with Error Code Message Header Error (§6.1) | MUST | 6.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** one class of header error is detected but never reported. A bad marker or a Length below 19 makes ParseHeader return a bare sentinel (internal/component/bgp/message/header.go:96-108), and the read loop turns that into an FSM event and a returned error with no NOTIFICATION sent (internal/component/bgp/reactor/session_read.go:98-102). The per-type and over-maximum length errors on the following lines do send Message Header Error (session_read.go:105-117) | | `RFC4271-6.1-2` | Marker not all ones: Error Subcode MUST be Connection Not Synchronized (§6.1) | MUST | 6.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** NotifyHeaderConnectionNotSync is declared (internal/component/bgp/message/notification.go:52) but no producer ever sends it. ParseHeader returns ErrInvalidMarker, a plain sentinel carrying no NOTIFICATION (internal/component/bgp/message/header.go:96-99), and the read loop's marker-error branch sends nothing before returning (internal/component/bgp/reactor/session_read.go:98-102) | | `RFC4271-6.1-3` | Invalid length: Error Subcode MUST be Bad Message Length; Data field MUST contain the erroneous Length field (§6.1) | MUST | 6.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** RFC 4271 §6.1 lists five length conditions and ze reports only four of them. The per-type minima and the 4096/65535 ceiling do produce a conformant Notification -- ValidateLength and ValidateLengthWithMax return a *Notification carrying NotifyHeaderBadLength and the two big-endian octets of the offending Length (internal/component/bgp/message/header.go:155-171 and :207-213), which the read loop sends before closing (internal/component/bgp/reactor/session_read.go:105-117). The first listed condition, "Length field of the message header is less than 19", does not: ParseHeader returns the bare sentinel ErrInvalidLength with no Notification and no Data (internal/component/bgp/message/header.go:106-108), and the read loop logs an FSM event and returns without writing anything (internal/component/bgp/reactor/session_read.go:98-102). The same code fact is recorded as the NOTIFICATION-absence gap on RFC4271-6.1-1. Disclosed in docs/features/rfc-status.md RFC 4271 row | | `RFC4271-6.1-4` | Invalid type: Error Subcode MUST be Bad Message Type; Data field MUST contain the erroneous Type field (§6.1) | MUST | 6.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** an unknown message type is reported with the wrong subcode and the wrong Data. handleUnknownType sends Message Header Error with subcode 0 and a human-readable text string rather than subcode 3 (Bad Message Type) with the erroneous Type octet (internal/component/bgp/reactor/session_handlers.go:20-36); NotifyHeaderBadType is declared at internal/component/bgp/message/notification.go:54 and has no producer | | `RFC4271-6.2-3` | All OPEN errors MUST be indicated by NOTIFICATION with Error Code OPEN Message Error (§6.2) | MUST | 6.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** one class of OPEN error is detected and never reported. UnpackOpen returns the bare sentinel ErrShortRead when the body is under 10 octets or when the Optional Parameters Length (standard or RFC 9072 extended) overruns the body (internal/component/bgp/message/open.go:167-168, :193-194, :199-200, :209-210), and handleOpen turns that into an FSM event and a returned error, writing no NOTIFICATION and not even closing the connection (internal/component/bgp/reactor/session_handlers.go:43-47); session_read.go:264 only propagates it. Every other OPEN error path does send Error Code 2 -- unsupported version (session_handlers.go:54-60), unacceptable Hold Time (:70-77) and a malformed capability (rejectOpenCapabilityError, :185-199) -- so the obligation holds everywhere except the decode failure. Disclosed in docs/features/rfc-status.md RFC 4271 row | | `RFC4271-6.3-1` | All UPDATE errors MUST be indicated by NOTIFICATION with Error Code UPDATE Message Error (§6.3) | MUST | 6.3 | **positive:** `unit/verify` [`TestRFC4271UpdateErrorReportedAsUpdateMessageError`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L633). **negative:** `unit/verify` [`TestRFC4271ConformantUpdateSendsNoUpdateError`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L696) | | `RFC4271-6.7-1` | Cease NOTIFICATION MUST NOT be used when a fatal error does exist (§6.7) | MUST NOT | 6.7 | **positive:** `unit/verify` [`TestPrefixExceedTeardown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L110). **negative:** `unit/verify` [`TestRFC4271UpdateErrorReportedAsUpdateMessageError`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L636) | | `RFC4271-8.2.1-1` | BGP MUST maintain a separate FSM for each configured peer (§8.2.1) | MUST | 8.2.1 | **positive:** `unit/verify` [`TestRFC4271SeparateFSMPerPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L213). **negative:** `unit/verify` [`TestRFC4271PerPeerFSMDoesNotShareTimers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L237) | | `RFC4271-8.2.1-2` | A BGP implementation MUST connect to and listen on TCP port 179 (§8.2.1) | MUST | 8.2.1 | **positive:** `unit/verify` [`TestRFC4271DefaultBGPPortIs179`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L306). **negative:** `unit/verify` [`TestRFC4271ExplicitPortOverridesDefault`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L322) | | `RFC4271-8.2.1-3` | For each incoming connection, a state machine MUST be instantiated (§8.2.1) | MUST | 8.2.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** an incoming connection does not get its own state machine. acceptOrReject hands the accepted connection to the peer's existing session (internal/component/bgp/reactor/reactor_connection.go:117-163), and a connection queued for collision resolution is read raw by handlePendingCollision with no FSM behind it (internal/component/bgp/reactor/reactor_connection.go:196-249). An FSM is created per session, i.e. per connection attempt of a configured peer (internal/component/bgp/reactor/session.go:396), not per inbound connection | | `RFC4271-8.2.2-1` | Event 10 (HoldTimer_Expires) lists "sends a NOTIFICATION message with the error code Hold Timer Expired" before "drops the TCP connection", in OpenSent, OpenConfirm and Established alike; a peer whose connection is dropped without it is told nothing. §8 states the FSM description is conceptual but binds "the same externally visible behavior", and the NOTIFICATION is the externally visible part, so the level is MUST (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271HoldTimerExpirySendsNotification`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L807). **negative:** `unit/verify` [`TestRFC4271HoldTimerNotYetExpiredSendsNoNotification`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L836). **positive:** `functional/verify` [`deadpeer-holddown.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/deadpeer-holddown.ci#L3) | | `RFC4271-8.2.2-2` | Event 10 (HoldTimer_Expires) lists "sets the ConnectRetryTimer to zero", in OpenSent, OpenConfirm and Established alike. A ConnectRetryTimer left running past the teardown fires against a connection that no longer exists, and the retry it schedules is externally visible as an unexpected connection attempt, so the level is MUST on the same §8 "same externally visible behavior" ground as -1 (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271HoldExpiryRunsTheEvent10ActionList`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L929). **negative:** `unit/verify` [`TestRFC4271NoHoldExpiryLeavesTheSessionIntact`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1000) | | `RFC4271-8.2.2-3` | Event 10 (HoldTimer_Expires) lists "releases all BGP resources", in OpenSent, OpenConfirm and Established alike. The externally visible part is the KeepaliveTimer: a session torn down for silence that keeps its keepalive chain running writes KEEPALIVEs after the teardown, so the level is MUST (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271HoldExpiryRunsTheEvent10ActionList`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L932). **negative:** `unit/verify` [`TestRFC4271NoHoldExpiryLeavesTheSessionIntact`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1002) | | `RFC4271-8.2.2-4` | Event 10 (HoldTimer_Expires) lists "drops the TCP connection", in OpenSent, OpenConfirm and Established alike. The peer must see the connection go away and not merely stop receiving; a half-open socket is externally visible, so the level is MUST (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271HoldExpiryRunsTheEvent10ActionList`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L935). **negative:** `unit/verify` [`TestRFC4271NoHoldExpiryLeavesTheSessionIntact`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1005) | | `RFC4271-8.2.2-5` | Event 10 (HoldTimer_Expires) lists "changes its state to Idle", in OpenSent, OpenConfirm and Established alike. The Established->Idle transition is what deletes the routes learned over the connection, which is externally visible to every other peer, so the level is MUST (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271HoldExpiryRunsTheEvent10ActionList`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L938). **negative:** `unit/verify` [`TestRFC4271NoHoldExpiryLeavesTheSessionIntact`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1007) | | `RFC4271-8.2.2-6` | Event 10 (HoldTimer_Expires) lists "(optionally) performs peer oscillation damping if the DampPeerOscillations attribute is set to TRUE" (§8.2.2) | MAY | 8.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-8.2.2-7` | ManualStart (Event 1) in Idle lists "sets ConnectRetryCounter to zero", once for the Connect branch (Events 1 and 3) and again, in the same words, for the passive Active branch (Events 4 and 5). §8 makes ConnectRetryCounter a mandatory session attribute and defines it as "the number of times a BGP peer has tried to establish a peer session", so an operator start that left a stale retry history behind would misreport that number; the level is MUST (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterZeroedOnManualStart`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L42). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterSurvivesDampedStart`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L75) | | `RFC4271-8.2.2-8` | ManualStop (Event 2) lists "sets the ConnectRetryCounter to zero", in Connect, Active, OpenSent, OpenConfirm and Established alike. Same §8 mandatory-attribute ground as -7: the operator stopping the peer ends the run of attempts the counter was counting (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterZeroedOnManualStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L108). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterNotZeroedByIdleManualStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L130) | | `RFC4271-8.2.2-9` | HoldTimer_Expires (Event 10) lists "increments the ConnectRetryCounter", in OpenSent, OpenConfirm and Established alike (OpenSent omits the words "by 1" that the other two carry; the counter is an integer count, so the step is one). A session lost to silence is an attempt that failed, and §8 makes the count mandatory (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterIncrementsOnHoldTimerExpiry`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L151). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterQuietOnHealthyEstablishedTraffic`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L174) | | `RFC4271-8.2.2-10` | BGPHeaderErr (Event 21) and BGPOpenMsgErr (Event 22) list "increments the ConnectRetryCounter by 1" in Connect, Active, OpenSent and OpenConfirm, and in Established through "In response to any other event (Events 9, 12-13, 20-22)", which names both (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterIncrementsOnHeaderAndOpenErrors`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L210). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterNotIncrementedByIdleErrors`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L236) | | `RFC4271-8.2.2-11` | NotifMsg (Event 25) leads to "increments the ConnectRetryCounter by 1" in every state that can see it: explicitly in OpenConfirm ("a TcpConnectionFails event (Event 18) [...] or a NOTIFICATION message (Event 25)") and Established ("a NOTIFICATION message (Event 24 or Event 25)"), and through the "any other event" list that names Event 25 in Connect, Active and OpenSent (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterIncrementsOnNotification`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L258). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterStepsByExactlyOnePerNotification`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L283) | | `RFC4271-8.2.2-12` | NotifMsgVerErr (Event 24) increments the ConnectRetryCounter in Connect, Active and Established, and MUST NOT in OpenSent or OpenConfirm. In Connect and Active the clause sits in the DelayOpenTimer-is-not-running branch, which is the only branch a speaker without a DelayOpenTimer can take, and Established groups Event 24 with Events 25 and 18; in OpenSent and OpenConfirm the Event 24 action list is four items long and has no ConnectRetryCounter line at all (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterOnVersionErrorPerState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L308). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterQuietOnVersionErrorInOpenStates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L332) | | `RFC4271-8.2.2-13` | TcpConnectionFails (Event 18) increments the ConnectRetryCounter in Active, OpenConfirm and Established, and MUST NOT in Connect or OpenSent. Connect's Event 18 has two branches and neither carries the clause, and OpenSent's Event 18 leaves for Active rather than tearing the peering down (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterOnTCPFailurePerState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L355). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterQuietOnTCPFailureInConnectAndOpenSent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L378) | | `RFC4271-8.2.2-14` | UpdateMsgErr (Event 28) in Established lists "increments the ConnectRetryCounter by 1". An UPDATE error ends the session, which makes it a failed attempt by the §8 definition of the attribute (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterIncrementsOnUpdateError`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L401). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterQuietOnGoodUpdate`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L421) | | `RFC4271-8.2.2-15` | The "any other event" action list lists "increments the ConnectRetryCounter by 1" ("by one" in Active) in all five non-Idle states: Connect and Active (Events 8, 10-11, 13, 19, 23, 25-28), OpenSent (Events 9, 11-13, 20, 25-28), OpenConfirm (Events 9, 12-13, 20, 27-28) and Established (Events 9, 12-13, 20-22). Idle is excluded on purpose: its own "any other event" clause says the event "does not cause change in the state of the local system" and lists no actions (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterIncrementsOnAnyOtherEvent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L443). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterIdleDefaultArmCountsNothing`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L478) | | `RFC4271-8.2.2-16` | AutomaticStop (Event 8) lists "increments the ConnectRetryCounter by 1" in OpenSent, OpenConfirm and Established, and Connect and Active name Event 8 in their "any other events" list, which carries the same line. Its action list differs from ManualStop's by that one line, and the difference is the point: §8 defines the attribute as "the number of times a BGP peer has tried to establish a peer session", so a stop the local system chose records a failed attempt where an operator stop ends the count (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterIncrementsOnAutomaticStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L549). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterAutomaticStopIsNotAManualStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L575) | | `RFC4271-8.2.2-17` | OpenCollisionDump (Event 23) lists "increments the ConnectRetryCounter by 1" in OpenSent, OpenConfirm and Established, and Connect and Active name Event 23 in their "any other events" list, which carries the same line. A connection closed by §6.8 collision resolution is an attempt that did not become a session (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestRFC4271ConnectRetryCounterIncrementsOnOpenCollisionDump`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L606). **negative:** `unit/verify` [`TestRFC4271ConnectRetryCounterCollisionDumpIsQuietInIdle`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L630) | | `RFC4271-8.2.2-18` | ManualStop (Event 2) lists the Cease NOTIFICATION FIRST in its action list, in every state that has a connection to send it on: OpenSent writes "sends the NOTIFICATION with a Cease", OpenConfirm and Established write "sends the NOTIFICATION message with a Cease", and all three list it before "drops the TCP connection". The level is MUST on the same §8 ground as -1: the FSM description is conceptual, but an implementation "MUST support the described functionality and exhibit the same externally visible behavior", and the NOTIFICATION is the externally visible part -- it is the ONLY signal that says the operator stopped this speaker rather than the network dropping it. §6.7's MAY governs a different act, a speaker CHOOSING to Cease "at any given time" of its own accord; once the operator has issued Event 2 the action list is what the FSM owes. Connect and Active list no Cease clause and are excluded: neither state holds a BGP connection (§8.2.2) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestShutdownNotifySendsCeaseFromEveryConnectedState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/shutdown_notify_test.go#L174). **negative:** `unit/verify` [`TestRFC4271NoCeaseWithoutAManualStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/shutdown_notify_test.go#L207). **positive:** `functional/verify` [`signal-stop-cease.ci`](https://github.com/ze-software/ze/blob/main/test/reload/signal-stop-cease.ci#L3) | | `RFC4271-10-1` | An implementation MUST allow the HoldTimer to be configurable on a per-peer basis (§10) | MUST | 10 | **positive:** `unit/verify` [`TestRFC4271HoldTimeConfigurablePerPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L261). **negative:** `unit/verify` [`TestRFC4271PerPeerHoldTimeSurvivesNegotiation`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L285) | | `RFC4271-3-1` | A BGP speaker SHOULD retain current routes from all peers or use Route Refresh (RFC 2918) (§3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-5-7` | Path attributes SHOULD be ordered in ascending order of attribute type (§5) | SHOULD | 5 | **positive:** `unit/verify` [`TestAnnounceBatchRail_AS4PathOrderedAgainstLargeCommunity`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_batch_attr_order_test.go#L328). **positive:** `unit/verify` [`TestAnnounceBatchRail_AscendingTypeCodeOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_batch_attr_order_test.go#L268). **positive:** `unit/verify` [`TestAnnounceQueuedRail_AscendingTypeCodeOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_batch_attr_order_test.go#L289). **positive:** `unit/verify` [`TestSplitMP_PreservesAscendingAttributeOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/update_split_attr_order_test.go#L72). **negative:** no negative test | | `RFC4271-5.1.6-2` | A BGP speaker SHOULD include ATOMIC_AGGREGATE when an aggregate excludes AS numbers (§5.1.6) | SHOULD | 5.1.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-5.1.6-3` | A BGP speaker SHOULD NOT remove ATOMIC_AGGREGATE when propagating the route (§5.1.6) | SHOULD NOT | 5.1.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-6.3-2` | Semantically incorrect NEXT_HOP SHOULD be logged and the route SHOULD be ignored (§6.3) | SHOULD | 6.3 | **positive:** `unit/verify` [`TestRFC4271SelfNextHopRouteIsNotInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L52). **negative:** no negative test | | `RFC4271-6.3-3` | For semantically incorrect NEXT_HOP, a NOTIFICATION SHOULD NOT be sent (§6.3) | SHOULD NOT | 6.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.2-1` | A BGP speaker SHOULD NOT advertise a feasible route if it would produce a duplicate UPDATE (§9.2) | SHOULD NOT | 9.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.2.1.1-1` | MinRouteAdvertisementIntervalTimer SHOULD NOT apply to routes sent to internal peers (§9.2.1.1) | SHOULD NOT | 9.2.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-10-2` | Jitter SHOULD be applied to timers (§10) | SHOULD | 10 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-5.1.1-1` | ORIGIN value SHOULD NOT be changed by any other speaker (§5.1.1) | SHOULD | 5.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-4.2-3` | A BGP speaker MAY reject connections on the basis of the Hold Time (§4.2) | MAY | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-3.1-1` | A BGP speaker MAY add to or modify path attributes before advertising (§3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-5.1.2-1` | A BGP speaker MAY include/prepend more than one instance of its own AS number in AS_PATH (§5.1.2) | MAY | 5.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-6.7-2` | A BGP speaker MAY support imposing a locally-configured upper bound on address prefixes (§6.7) | MAY | 6.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-10-3` | An implementation MAY allow other timers to be configurable (§10) | MAY | 10 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-6.2-4` | An implementation MAY reject any proposed Hold Time (§6.2) | MAY | 6.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-6.7-3` | The speaker MAY also log a Cease locally (§6.7) | MAY | 6.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-3.1-2` | Next hop for routes in Loc-RIB MUST be resolvable via the local BGP speaker's Routing Table (§3.1) | MUST | 3.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the BGP Loc-RIB install performs no reachability check on the route's next hop. mirrorToLocRIB inserts the winning path with whatever next-hop address the attribute carried (internal/component/bgp/plugins/rib/rib_bestchange.go:797-830), and the candidate gather step filters only on SRv6 ineligibility (internal/component/bgp/plugins/rib/rib_commands.go:1039-1057). Resolvability is enforced downstream at FIB-install time, which removes the route from the routing table but leaves it in the Loc-RIB | | `RFC4271-5.1.2-2` | When advertising a route to an internal peer, the speaker SHALL NOT modify the AS_PATH attribute (§5.1.2) | SHALL NOT | 5.1.2 | **positive:** `unit/verify` [`TestEstablishedAnnounce_ExplicitASPath_IBGPVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_batch_test.go#L569). **positive:** `unit/verify` [`TestRFC4271ASPathUnmodifiedTowardInternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L131). **negative:** `unit/verify` [`TestRFC4271ASPathPrependedTowardExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L148) | | `RFC4271-5.1.2-3` | When advertising a route to an external peer, the speaker prepends its own AS number to the leading AS_SEQUENCE of AS_PATH (creating one when the path is empty or led by an AS_SET) (§5.1.2) | SHALL | 5.1.2 | **positive:** `unit/verify` [`TestASPathSlotPrependOnlyWhenAdvertising`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/advertise_test.go#L97). **positive:** `unit/verify` [`TestEstablishedAnnounce_ExplicitASPath_PrependsLocalAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_batch_test.go#L543). **negative:** `unit/verify` [`TestASPathSlotPrependOnlyWhenAdvertising`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/advertise_test.go#L99). **negative:** `unit/verify` [`TestEstablishedAnnounce_ExplicitASPath_IBGPVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_batch_test.go#L567). **positive:** `interop/nightly` [`checkRelayWithdrawalShape`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L431). **negative:** `interop/nightly` [`checkRelayWithdrawalShape`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L432) | | `RFC4271-5.1.4-3` | If altering MULTI_EXIT_DISC received over EBGP, alteration MUST be done prior to decision process phases 1 and 2 (§5.1.4) | MUST | 5.1.4 | **positive:** `unit/verify` [`TestRFC4271MEDAlterationHappensAtIngress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L452). **negative:** no negative test. **{single-polarity}:** the requirement constrains only the ORDER of an alteration that a speaker chooses to make, so there is no non-conformant input a receiver could reject. ze's only place to alter a received MULTI_EXIT_DISC is the ingress filter chain, whose rewritten payload replaces the WireUpdate before the UPDATE is dispatched to the RIB plugin that runs phases 1 and 2 (internal/component/bgp/reactor/reactor_notify.go:427-466) | | `RFC4271-5.1.5-5` | A BGP speaker SHALL calculate the degree of preference for each external route based on locally-configured policy (§5.1.5) | SHALL | 5.1.5 | **positive:** `unit/verify` [`TestRFC4271ExternalRouteDegreeOfPreferenceFromLocalPolicy`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L141). **negative:** `unit/verify` [`TestRFC4271DegreeOfPreferenceNotAHardcodedConstant`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L168) | | `RFC4271-5.1.7-1` | A BGP speaker that performs aggregation and adds AGGREGATOR SHALL include its own AS number and IP address (§5.1.7) | SHALL | 5.1.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never performs aggregation, so it never adds an AGGREGATOR of its own. The same grep as RFC4271-5.1.6-1 finds no aggregation producer; AGGREGATOR is only interned from the wire (internal/component/bgp/plugins/rib/storage/attrparse.go:96-102), replayed on readvertise (internal/component/bgp/plugins/rib/storage/familyrib.go:817-819) or emitted from operator configuration (internal/component/bgp/message/update_build_grouped.go:141-148) | | `RFC4271-6.7-4` | When terminating due to prefix limit, speaker MUST send NOTIFICATION with Error Code Cease (§6.7) | MUST | 6.7 | **positive:** `unit/verify` [`TestPrefixExceedTeardown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L107). **negative:** `unit/verify` [`TestPrefixExceedDrop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L144) | | `RFC4271-6.8-1` | In the event of connection collision, one of the connections MUST be closed (§6.8) | MUST | 6.8 | **positive:** `unit/verify` [`TestCollisionOpenConfirmLocalWins`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/collision_test.go#L127). **negative:** `unit/verify` [`TestCollisionOpenSentNoCollision`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/collision_test.go#L177) | | `RFC4271-6.8-2` | Upon receipt of an OPEN message, the local system MUST examine all connections in OpenConfirm state for collision (§6.8) | MUST | 6.8 | **positive:** `unit/verify` [`TestCollisionOpenConfirmLocalWins`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/collision_test.go#L130). **negative:** `unit/verify` [`TestCollisionNonCollisionStates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/collision_test.go#L536) | | `RFC4271-9-1` | Withdrawn routes SHALL be removed from the Adj-RIB-In and the Decision Process SHALL be run (§9) | SHALL | 9 | **positive:** `unit/verify` [`TestRFC4271WithdrawRemovesFromAdjRIBIn`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L215). **negative:** `unit/verify` [`TestRFC4271WithdrawRemovesFromAdjRIBIn`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L220) | | `RFC4271-9-2` | A new route with identical NLRI to an existing route SHALL replace the older route in Adj-RIB-In (§9) | SHALL | 9 | **positive:** `unit/verify` [`TestRFC4271SamePrefixReplacesRatherThanAccumulates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L189). **negative:** `unit/verify` [`TestRFC4271WithdrawRemovesFromAdjRIBIn`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L217) | | `RFC4271-9-3` | Once the Adj-RIB-In is updated, the speaker SHALL run its Decision Process (§9) | SHALL | 9 | **positive:** `unit/verify` [`TestRIBBestChangeWithdraw`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L686). **negative:** `unit/verify` [`TestRIBBestChangeNoPublishSameBest`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L650) | | `RFC4271-9.1.1-1` | The degree of preference function SHALL NOT use the existence or attributes of other routes as inputs (§9.1.1) | SHALL NOT | 9.1.1 | **positive:** `unit/verify` [`TestRFC4271DegreeOfPreferenceIgnoresOtherRoutes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L33). **negative:** `unit/verify` [`TestRFC4271DegreeOfPreferenceFollowsOwnAttributes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L68) | | `RFC4271-9.1.1-2` | For external routes, the computed degree of preference MUST be used as the LOCAL_PREF value in IBGP readvertisement (§9.1.1) | MUST | 9.1.1 | **positive:** `unit/verify` [`TestRFC4271LocalPrefIncludedForInternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L86). **negative:** `unit/verify` [`TestRFC4271LocalPrefOmittedForExternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L110) | | `RFC4271-9.1.2-1` | If NEXT_HOP is not resolvable, the BGP route MUST be excluded from Phase 2 decision function (§9.1.2) | MUST | 9.1.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** an unresolvable NEXT_HOP does not exclude the route from Phase 2. gatherCandidatesLocked skips only SRv6-ineligible entries (internal/component/bgp/plugins/rib/rib_commands.go:1039-1057), and extractCandidate uses the next hop solely to look up an IGP cost (internal/component/bgp/plugins/rib/rib_commands.go:1123-1131), so an unreachable next hop yields a cost of zero and the route competes normally | | `RFC4271-9.1.2-2` | The local speaker SHALL install the best route in the Loc-RIB (§9.1.2) | SHALL | 9.1.2 | **positive:** `unit/verify` [`TestLocRIBMirror`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L1513). **negative:** `unit/verify` [`TestRIBBestChangeWithdraw`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L691) | | `RFC4271-9.1.2-3` | The local speaker MUST determine the immediate next-hop address from the NEXT_HOP attribute (§9.1.2) | MUST | 9.1.2 | **positive:** `unit/verify` [`TestLocRIBMirror`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L1515). **negative:** `unit/verify` [`TestRFC4271LocRIBNextHopComesFromNextHopAttribute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L94) | | `RFC4271-9.1.2-4` | If immediate next-hop or IGP cost to NEXT_HOP changes, Phase 2 Route Selection MUST be performed again (§9.1.2) | MUST | 9.1.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** nothing re-runs Phase 2 when the immediate next-hop or the IGP cost to the NEXT_HOP changes. The only entry points to checkBestPathChange are the UPDATE ingest path and the peer-state paths (internal/component/bgp/plugins/rib/rib_structured.go:271-286), and the IGP cost function is a passive lookup registered once with no invalidation callback (internal/component/bgp/plugins/rib/bestpath.go:30-43) | | `RFC4271-9.1.2.1-1` | When installing a BGP route in the Routing Table, implementations MUST recalculate and take into account next-hops (§9.1.2.1) | MUST | 9.1.2.1 | **positive:** `unit/verify` [`TestLocRIBMirror`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L1518). **negative:** `unit/verify` [`TestRFC4271LocRIBNextHopComesFromNextHopAttribute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L98) | | `RFC4271-9.1.2.1-2` | Unresolvable routes SHALL be removed from the Loc-RIB and the routing table (§9.1.2.1) | SHALL | 9.1.2.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** an unresolvable route is not removed from the Loc-RIB. The only Loc-RIB removal in the BGP plugin is the no-candidate-remains branch of checkBestPathChange (internal/component/bgp/plugins/rib/rib_bestchange.go:766-782), which is driven by the Adj-RIB-In losing its last path and never by next-hop resolvability; nothing in the plugin consults a resolver | | `RFC4271-9.1.2.2-1` | The tie-breaking criteria MUST be applied in the order specified (§9.1.2.2) | MUST | 9.1.2.2 | **positive:** `unit/verify` [`TestBestPathStepFComparesThePeerBGPIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_bgp_identifier_test.go#L88). **positive:** `unit/verify` [`TestBestPathStepFComparesThePeerBGPIdentifierOnTheJSONRail`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_bgp_identifier_json_test.go#L78). **positive:** `unit/verify` [`TestBestPath_FullTiebreak`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L530). **negative:** `unit/verify` [`TestBestPathEqualBGPIdentifiersFallThroughToPeerAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_bgp_identifier_test.go#L116). **negative:** `unit/verify` [`TestBestPathEqualBGPIdentifiersOnTheJSONRailFallThroughToPeerAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_bgp_identifier_json_test.go#L106). **negative:** `unit/verify` [`TestBestPath_MED_SameNeighborAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L315) | | `RFC4271-9.1.2.2-2` | If MULTI_EXIT_DISC is removed before IBGP readvertisement, the optional MED comparison MUST be performed only among EBGP-learned routes (§9.1.2.2) | MUST | 9.1.2.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the common egress guard is implemented, but normal BGP selected-route readvertisement has no runnable producer and no discriminating proof. `bgp-rib` records selected Loc-RIB state (internal/component/bgp/plugins/rib/rib_bestchange.go:738-790), route-server and route-reflector plugins forward cached UPDATEs instead (internal/component/bgp/plugins/rs/server.go:433-434 and internal/component/bgp/plugins/rr/rr.go:188-195), and BGP-to-BGP redistribution is rejected as same-protocol redistribution (internal/core/redistevents/registry.go:144-146) | | `RFC4271-9.1.2.2-3` | For IBGP-learned routes, MULTI_EXIT_DISC MUST be used in comparisons that reach the MED step (§9.1.2.2) | MUST | 9.1.2.2 | **positive:** `unit/verify` [`TestBestPath_MED_SameNeighborAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L319). **negative:** `unit/verify` [`TestBestPath_MED_SameNeighborAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L322) | | `RFC4271-9.1.2.2-4` | Routes that do not have the MULTI_EXIT_DISC attribute are considered to have the lowest possible MULTI_EXIT_DISC value (§9.1.2.2) | MUST | 9.1.2.2 | **positive:** `unit/verify` [`TestAbsentMedStillComparesAsZeroInPhaseTwo`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L225). **negative:** `unit/verify` [`TestAbsentMedTiesAnExplicitMedOfZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L257) | | `RFC4271-9.2-2` | A route SHALL NOT be installed in Adj-RIB-Out unless its destination and NEXT_HOP may be forwarded by the Routing Table (§9.2) | SHALL NOT | 9.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** nothing gates Adj-RIB-Out installation on the destination and NEXT_HOP being forwardable. QueueAnnounce records the route unconditionally (internal/component/bgp/rib/outgoing.go:65-101), and the forwarding rails decide only on filters, family negotiation and the route-reflection rules (internal/component/bgp/reactor/forward_rs.go:295-333) | | `RFC4271-9.2-3` | If a route in Loc-RIB is excluded from a particular Adj-RIB-Out, the previously advertised route MUST be withdrawn (§9.2) | MUST | 9.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** a route excluded from a peer's Adj-RIB-Out by an egress filter is skipped silently, leaving the peer's previous advertisement in place instead of withdrawing it. Both forwarding rails `continue` on suppression with no withdrawal built (internal/component/bgp/reactor/forward_rs.go:320-333 and internal/component/bgp/reactor/reactor_api_forward.go:496-506); the one announce-to-withdraw conversion is LLGR-specific and filter-requested, not exclusion-driven (internal/component/bgp/reactor/reactor_api_forward.go:588-601) | | `RFC4271-9.2-4` | The Decision Process MUST consider both overlapping routes based on acceptance policy (§9.2) | MUST | 9.2 | **positive:** `unit/verify` [`TestRFC4271OverlappingRoutesBothInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L148). **negative:** `unit/verify` [`TestRFC4271SamePrefixReplacesRatherThanAccumulates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L182) | | `RFC4271-9.2-5` | If both less and more specific overlapping routes are accepted, the Decision Process MUST install both or an aggregate in Loc-RIB (§9.2) | MUST | 9.2 | **positive:** `unit/verify` [`TestRFC4271OverlappingRoutesBothInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L151). **negative:** `unit/verify` [`TestRFC4271SamePrefixReplacesRatherThanAccumulates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L186) | | `RFC4271-9.2.1.1-2` | Two UPDATE messages advertising to common destinations MUST be separated by at least MinRouteAdvertisementIntervalTimer (§9.2.1.1) | MUST | 9.2.1.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze has no MinRouteAdvertisementIntervalTimer, so successive UPDATEs to a common set of destinations are not spaced. The timer set implements only ConnectRetry, Hold and Keepalive and records the omission in its own doc comment (internal/component/bgp/fsm/timer.go:34-42, "MinRouteAdvertisementIntervalTimer (Section 9.2.1.1) - not implemented here"); `grep -rniE 'minroute\|mrai' --include=*.go internal/` finds no producer | | `RFC4271-9.2.2.2-1` | Routes with different MULTI_EXIT_DISC attributes SHALL NOT be aggregated (§9.2.2.2) | SHALL NOT | 9.2.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never aggregates routes, so no producer can aggregate two routes with different MULTI_EXIT_DISC values. `grep -rniE 'aggregate-address\|AggregateRoute\|route aggregation' --include=*.go .` returns no hit outside rfc/ and plan/, and no code path synthesizes an aggregate route from more-specifics | | `RFC4271-9.2.2.2-2` | If any aggregated route has ORIGIN INCOMPLETE, the aggregate MUST have ORIGIN INCOMPLETE; else if any has EGP, the aggregate MUST have ORIGIN EGP (§9.2.2.2) | MUST | 9.2.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never aggregates routes, so no producer computes an aggregate ORIGIN. ORIGIN is only parsed (internal/core/bgp/attribute/origin.go:146-160), interned (internal/component/bgp/plugins/rib/storage/attrparse.go) and re-emitted verbatim (internal/component/bgp/plugins/rib/storage/familyrib.go:799-801); the same aggregation grep returns nothing | | `RFC4271-9.2.2.2-3` | When aggregating routes with different NEXT_HOP, the aggregated NEXT_HOP SHALL identify an interface on the aggregating speaker (§9.2.2.2) | SHALL | 9.2.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never aggregates routes, so no producer chooses an aggregated NEXT_HOP. The only next-hop selection is per-route egress policy (internal/component/bgp/reactor/peer_forward_facts.go:153-193); the same aggregation grep returns nothing | | `RFC4271-9.2.2.2-4` | If at least one aggregated route has ATOMIC_AGGREGATE, the aggregate SHALL have it as well (§9.2.2.2) | SHALL | 9.2.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never aggregates routes, so no producer decides whether an aggregate carries ATOMIC_AGGREGATE. The attribute is only decoded, stored and replayed (internal/core/bgp/attribute/simple.go:175-195, internal/component/bgp/plugins/rib/storage/familyrib.go:815-817); the same aggregation grep returns nothing | | `RFC4271-9.2.2.2-5` | AGGREGATOR attributes from aggregated routes MUST NOT be included in the aggregated route (§9.2.2.2) | MUST NOT | 9.2.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never aggregates routes, so no producer builds an aggregated route from which a contributing AGGREGATOR would have to be excluded. AGGREGATOR is only interned from the wire or emitted from operator configuration (internal/component/bgp/plugins/rib/storage/attrparse.go:96-102, internal/component/bgp/message/update_build_grouped.go:141-148); the same aggregation grep returns nothing | | `RFC4271-Security-1` | A BGP implementation MUST support TCP MD5 authentication (RFC 2385) (§Security, Appendix E) | MUST | Security | **positive:** `unit/verify` [`TestMD5PeersForListener`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_test.go#L2357). **negative:** `unit/verify` [`TestMD5PeersForListener`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_test.go#L2360) | | `RFC4271-9.2.1.1-3` | The last route selected while awaiting MinRouteAdvertisementIntervalTimer SHALL be advertised at expiry (§9.2.1.1) | SHALL | 9.2.1.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** with no MinRouteAdvertisementIntervalTimer there is no expiry at which a last-selected route could be advertised. The timer is absent by design note (internal/component/bgp/fsm/timer.go:39) and no producer buffers a pending best-route advertisement against such a timer; best-path changes are published as they are computed (internal/component/bgp/plugins/rib/rib_bestchange.go:832-880) | | `RFC4271-9.2-6` | A BGP speaker SHALL NOT redistribute routing information from an internal peer to other internal peers (unless route reflector) (§9.2) | SHALL NOT | 9.2 | **positive:** `unit/verify` [`TestRFC4271NoIBGPToIBGPRedistribution`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L508). **negative:** `unit/verify` [`TestRFC4271IBGPRedistributionAllowedForReflectorClient`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L567) | | `RFC4271-9.2-7` | Newly unfeasible routes for which there is no replacement SHALL be advertised via UPDATE (§9.2) | SHALL | 9.2 | **positive:** `unit/verify` [`TestRIBBestChangeWithdraw`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L689). **negative:** `unit/verify` [`TestRIBBestChangeNoPublishSameBest`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L653) | | `RFC4271-9.2-8` | Any routes in the Loc-RIB marked as unfeasible SHALL be removed (§9.2) | SHALL | 9.2 | **positive:** `unit/verify` [`TestLocRIBMirror`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L1521). **negative:** `unit/verify` [`TestRIBBestChangeNoPublishSameBest`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L655) | | `RFC4271-9.2-9` | Changes to reachable destinations within the speaker's own AS SHALL be advertised in an UPDATE (§9.2) | SHALL | 9.2 | **positive:** `unit/verify` [`TestRFC4271OwnASReachabilityChangeAdvertised`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L398). **negative:** `unit/verify` [`TestRFC4271OwnASUnreachabilityChangeAdvertised`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L426) | | `RFC4271-9.2-10` | If a single route does not fit in an UPDATE message, the speaker MUST NOT advertise it and MAY log an error (§9.2) | MUST | 9.2 | **positive:** `unit/verify` [`TestRFC4271OversizeSingleRouteNotAdvertised`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L402). **negative:** `unit/verify` [`TestRFC4271FittingRouteIsAdvertised`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L431) | | `RFC4271-Appendix-1` | Each BGP message SHOULD be transmitted with the TCP PUSH flag set (§Appendix E) | SHOULD | Appendix | **positive:** no positive test. **negative:** no negative test | | `RFC4271-Appendix-2` | The TCP connection used by BGP SHOULD be opened with DSCP bits 0-2 set to 110 (§Appendix E) | SHOULD | Appendix | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.3-1` | An AS SHOULD avoid using unstable routes (§9.3) | SHOULD | 9.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.3-2` | An AS SHOULD NOT make rapid, spontaneous changes to its choice of route (§9.3) | SHOULD NOT | 9.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.4-1` | Distribution of non-BGP acquired routes within an AS via BGP SHOULD be controlled via configuration (§9.4) | SHOULD | 9.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.1.2-5` | If the AS_PATH attribute of a BGP route contains an AS loop, the BGP route should be excluded from the Phase 2 decision function (§9.1.2). Detection scans the full AS path and checks that the local autonomous system number does not appear in it. RFC 4271 writes this keyword in lower case, so the level is a recommendation and not a capitalized RFC 2119 SHOULD. The same paragraph places a speaker configured to accept routes with its own autonomous system number in the AS path outside the scope of the document. That out-of-scope case is what the allow-own-as setting selects, so a non-zero allow-own-as is not a deviation from this line. | SHOULD | 9.1.2 | **positive:** `unit/verify` [`TestDetectASLoop_NotPresent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/filter/loop_test.go#L136). **negative:** `unit/verify` [`TestDetectASLoop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/filter/loop_test.go#L114). **negative:** `unit/verify` [`TestDetectASLoop_ASSet`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/filter/loop_test.go#L125). **negative:** `functional/verify` [`loop-as.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/loop-as.ci#L7) | | `RFC4271-9.1.2.1-3` | Unresolvable routes SHOULD be kept in the Adj-RIB-In for future re-evaluation (§9.1.2.1) | SHOULD | 9.1.2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.1.2.1-4` | If only an aggregate is available, only the longest matching route SHOULD be announced (§9.1.2.1) | SHOULD | 9.1.2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.1.2.1-5` | Connection establishment failure SHOULD be logged (§9.1.2.1) | SHOULD | 9.1.2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-8.2.1.4-1` | Connection that is closed in collision SHOULD be disposed (§8.2.1.4) | SHOULD | 8.2.1.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-4.3-6` | An UPDATE message SHOULD NOT include the same address prefix in both WITHDRAWN and NLRI (§4.3) | SHOULD | 4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-4.3-7` | An UPDATE containing the same prefix in WITHDRAWN and NLRI SHOULD be treated as if the prefix is not in WITHDRAWN (§4.3) | SHOULD | 4.3 | **positive:** `unit/verify` [`TestRIBInjectSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L121). **positive:** `unit/verify` [`TestRIBPoolPathSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L87). **positive:** `unit/verify` [`TestRIBSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L42). **negative:** no negative test | | `RFC4271-5.1.7-2` | AGGREGATOR IP address SHOULD be the same as the BGP Identifier of the speaker (§5.1.7) | SHOULD | 5.1.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.2-11` | A BGP speaker that chooses to aggregate SHOULD either include all ASes in an AS_SET or add ATOMIC_AGGREGATE (§9.2) | SHOULD | 9.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.2-12` | Routes SHOULD NOT be de-aggregated (§9.2) | SHOULD NOT | 9.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4271-9.2.2.2-6` | If aggregated AS_PATH begins with AS_SET, the originator SHOULD NOT advertise MULTI_EXIT_DISC (§9.2.2.2) | SHOULD NOT | 9.2.2.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4271-5.1.6-1`](#rfc4271-5.1.6-1) A BGP speaker that receives a route with the ATOMIC_AGGREGATE attribute MUST NOT make any NLRI of that route more specific when advertising this route to other BGP speakers (§5.1.6) | no test | no test carries this requirement id; annotated {not-applicable}: the obligation binds the RECEIVER/re-advertiser, and ze is one -- it stores a received ATOMIC_AGGREGATE and copies it through on readvertisement (internal/component/bgp/reactor/peer_rib_routes.go:141) -- but the prohibited act has no producer. `grep -rniE "more specific\|deaggregat\|de-aggregat\|disaggregat" --include=*.go internal/component/bgp/ \| grep -v _test` returns only substring hits inside `encodeAggregatorValue` and `attrCodeAggregator` (internal/component/bgp/reactor/filter_delta.go:294,396, internal/component/bgp/message/rfc7606.go:64,421); no code path splits a prefix. Both readvertisement encoders write the stored route's own prefix verbatim through nlri.WriteNLRI (internal/component/bgp/reactor/peer_rib_routes.go:103-104), so the advertised NLRI is byte-identical to what was received and can be neither more nor less specific. With no length-altering producer there is no behavior to exercise in either polarity | | [`RFC4271-6.1-1`](#rfc4271-6.1-1) All header errors MUST be indicated by sending NOTIFICATION with Error Code Message Header Error (§6.1) | {gap}, no test | one class of header error is detected but never reported. A bad marker or a Length below 19 makes ParseHeader return a bare sentinel (internal/component/bgp/message/header.go:96-108), and the read loop turns that into an FSM event and a returned error with no NOTIFICATION sent (internal/component/bgp/reactor/session_read.go:98-102). The per-type and over-maximum length errors on the following lines do send Message Header Error (session_read.go:105-117) | | [`RFC4271-6.1-2`](#rfc4271-6.1-2) Marker not all ones: Error Subcode MUST be Connection Not Synchronized (§6.1) | {gap}, no test | NotifyHeaderConnectionNotSync is declared (internal/component/bgp/message/notification.go:52) but no producer ever sends it. ParseHeader returns ErrInvalidMarker, a plain sentinel carrying no NOTIFICATION (internal/component/bgp/message/header.go:96-99), and the read loop's marker-error branch sends nothing before returning (internal/component/bgp/reactor/session_read.go:98-102) | | [`RFC4271-6.1-3`](#rfc4271-6.1-3) Invalid length: Error Subcode MUST be Bad Message Length; Data field MUST contain the erroneous Length field (§6.1) | {gap}, no test | RFC 4271 §6.1 lists five length conditions and ze reports only four of them. The per-type minima and the 4096/65535 ceiling do produce a conformant Notification -- ValidateLength and ValidateLengthWithMax return a *Notification carrying NotifyHeaderBadLength and the two big-endian octets of the offending Length (internal/component/bgp/message/header.go:155-171 and :207-213), which the read loop sends before closing (internal/component/bgp/reactor/session_read.go:105-117). The first listed condition, "Length field of the message header is less than 19", does not: ParseHeader returns the bare sentinel ErrInvalidLength with no Notification and no Data (internal/component/bgp/message/header.go:106-108), and the read loop logs an FSM event and returns without writing anything (internal/component/bgp/reactor/session_read.go:98-102). The same code fact is recorded as the NOTIFICATION-absence gap on RFC4271-6.1-1. Disclosed in docs/features/rfc-status.md RFC 4271 row | | [`RFC4271-6.1-4`](#rfc4271-6.1-4) Invalid type: Error Subcode MUST be Bad Message Type; Data field MUST contain the erroneous Type field (§6.1) | {gap}, no test | an unknown message type is reported with the wrong subcode and the wrong Data. handleUnknownType sends Message Header Error with subcode 0 and a human-readable text string rather than subcode 3 (Bad Message Type) with the erroneous Type octet (internal/component/bgp/reactor/session_handlers.go:20-36); NotifyHeaderBadType is declared at internal/component/bgp/message/notification.go:54 and has no producer | | [`RFC4271-6.2-3`](#rfc4271-6.2-3) All OPEN errors MUST be indicated by NOTIFICATION with Error Code OPEN Message Error (§6.2) | {gap}, no test | one class of OPEN error is detected and never reported. UnpackOpen returns the bare sentinel ErrShortRead when the body is under 10 octets or when the Optional Parameters Length (standard or RFC 9072 extended) overruns the body (internal/component/bgp/message/open.go:167-168, :193-194, :199-200, :209-210), and handleOpen turns that into an FSM event and a returned error, writing no NOTIFICATION and not even closing the connection (internal/component/bgp/reactor/session_handlers.go:43-47); session_read.go:264 only propagates it. Every other OPEN error path does send Error Code 2 -- unsupported version (session_handlers.go:54-60), unacceptable Hold Time (:70-77) and a malformed capability (rejectOpenCapabilityError, :185-199) -- so the obligation holds everywhere except the decode failure. Disclosed in docs/features/rfc-status.md RFC 4271 row | | [`RFC4271-8.2.1-3`](#rfc4271-8.2.1-3) For each incoming connection, a state machine MUST be instantiated (§8.2.1) | {gap}, no test | an incoming connection does not get its own state machine. acceptOrReject hands the accepted connection to the peer's existing session (internal/component/bgp/reactor/reactor_connection.go:117-163), and a connection queued for collision resolution is read raw by handlePendingCollision with no FSM behind it (internal/component/bgp/reactor/reactor_connection.go:196-249). An FSM is created per session, i.e. per connection attempt of a configured peer (internal/component/bgp/reactor/session.go:396), not per inbound connection | | [`RFC4271-3.1-2`](#rfc4271-3.1-2) Next hop for routes in Loc-RIB MUST be resolvable via the local BGP speaker's Routing Table (§3.1) | {gap}, no test | the BGP Loc-RIB install performs no reachability check on the route's next hop. mirrorToLocRIB inserts the winning path with whatever next-hop address the attribute carried (internal/component/bgp/plugins/rib/rib_bestchange.go:797-830), and the candidate gather step filters only on SRv6 ineligibility (internal/component/bgp/plugins/rib/rib_commands.go:1039-1057). Resolvability is enforced downstream at FIB-install time, which removes the route from the routing table but leaves it in the Loc-RIB | | [`RFC4271-5.1.7-1`](#rfc4271-5.1.7-1) A BGP speaker that performs aggregation and adds AGGREGATOR SHALL include its own AS number and IP address (§5.1.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze never performs aggregation, so it never adds an AGGREGATOR of its own. The same grep as RFC4271-5.1.6-1 finds no aggregation producer; AGGREGATOR is only interned from the wire (internal/component/bgp/plugins/rib/storage/attrparse.go:96-102), replayed on readvertise (internal/component/bgp/plugins/rib/storage/familyrib.go:817-819) or emitted from operator configuration (internal/component/bgp/message/update_build_grouped.go:141-148) | | [`RFC4271-9.1.2-1`](#rfc4271-9.1.2-1) If NEXT_HOP is not resolvable, the BGP route MUST be excluded from Phase 2 decision function (§9.1.2) | {gap}, no test | an unresolvable NEXT_HOP does not exclude the route from Phase 2. gatherCandidatesLocked skips only SRv6-ineligible entries (internal/component/bgp/plugins/rib/rib_commands.go:1039-1057), and extractCandidate uses the next hop solely to look up an IGP cost (internal/component/bgp/plugins/rib/rib_commands.go:1123-1131), so an unreachable next hop yields a cost of zero and the route competes normally | | [`RFC4271-9.1.2-4`](#rfc4271-9.1.2-4) If immediate next-hop or IGP cost to NEXT_HOP changes, Phase 2 Route Selection MUST be performed again (§9.1.2) | {gap}, no test | nothing re-runs Phase 2 when the immediate next-hop or the IGP cost to the NEXT_HOP changes. The only entry points to checkBestPathChange are the UPDATE ingest path and the peer-state paths (internal/component/bgp/plugins/rib/rib_structured.go:271-286), and the IGP cost function is a passive lookup registered once with no invalidation callback (internal/component/bgp/plugins/rib/bestpath.go:30-43) | | [`RFC4271-9.1.2.1-2`](#rfc4271-9.1.2.1-2) Unresolvable routes SHALL be removed from the Loc-RIB and the routing table (§9.1.2.1) | {gap}, no test | an unresolvable route is not removed from the Loc-RIB. The only Loc-RIB removal in the BGP plugin is the no-candidate-remains branch of checkBestPathChange (internal/component/bgp/plugins/rib/rib_bestchange.go:766-782), which is driven by the Adj-RIB-In losing its last path and never by next-hop resolvability; nothing in the plugin consults a resolver | | [`RFC4271-9.1.2.2-2`](#rfc4271-9.1.2.2-2) If MULTI_EXIT_DISC is removed before IBGP readvertisement, the optional MED comparison MUST be performed only among EBGP-learned routes (§9.1.2.2) | {gap}, no test | the common egress guard is implemented, but normal BGP selected-route readvertisement has no runnable producer and no discriminating proof. `bgp-rib` records selected Loc-RIB state (internal/component/bgp/plugins/rib/rib_bestchange.go:738-790), route-server and route-reflector plugins forward cached UPDATEs instead (internal/component/bgp/plugins/rs/server.go:433-434 and internal/component/bgp/plugins/rr/rr.go:188-195), and BGP-to-BGP redistribution is rejected as same-protocol redistribution (internal/core/redistevents/registry.go:144-146) | | [`RFC4271-9.2-2`](#rfc4271-9.2-2) A route SHALL NOT be installed in Adj-RIB-Out unless its destination and NEXT_HOP may be forwarded by the Routing Table (§9.2) | {gap}, no test | nothing gates Adj-RIB-Out installation on the destination and NEXT_HOP being forwardable. QueueAnnounce records the route unconditionally (internal/component/bgp/rib/outgoing.go:65-101), and the forwarding rails decide only on filters, family negotiation and the route-reflection rules (internal/component/bgp/reactor/forward_rs.go:295-333) | | [`RFC4271-9.2-3`](#rfc4271-9.2-3) If a route in Loc-RIB is excluded from a particular Adj-RIB-Out, the previously advertised route MUST be withdrawn (§9.2) | {gap}, no test | a route excluded from a peer's Adj-RIB-Out by an egress filter is skipped silently, leaving the peer's previous advertisement in place instead of withdrawing it. Both forwarding rails `continue` on suppression with no withdrawal built (internal/component/bgp/reactor/forward_rs.go:320-333 and internal/component/bgp/reactor/reactor_api_forward.go:496-506); the one announce-to-withdraw conversion is LLGR-specific and filter-requested, not exclusion-driven (internal/component/bgp/reactor/reactor_api_forward.go:588-601) | | [`RFC4271-9.2.1.1-2`](#rfc4271-9.2.1.1-2) Two UPDATE messages advertising to common destinations MUST be separated by at least MinRouteAdvertisementIntervalTimer (§9.2.1.1) | {gap}, no test | ze has no MinRouteAdvertisementIntervalTimer, so successive UPDATEs to a common set of destinations are not spaced. The timer set implements only ConnectRetry, Hold and Keepalive and records the omission in its own doc comment (internal/component/bgp/fsm/timer.go:34-42, "MinRouteAdvertisementIntervalTimer (Section 9.2.1.1) - not implemented here"); `grep -rniE 'minroute\|mrai' --include=*.go internal/` finds no producer | | [`RFC4271-9.2.2.2-1`](#rfc4271-9.2.2.2-1) Routes with different MULTI_EXIT_DISC attributes SHALL NOT be aggregated (§9.2.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze never aggregates routes, so no producer can aggregate two routes with different MULTI_EXIT_DISC values. `grep -rniE 'aggregate-address\|AggregateRoute\|route aggregation' --include=*.go .` returns no hit outside rfc/ and plan/, and no code path synthesizes an aggregate route from more-specifics | | [`RFC4271-9.2.2.2-2`](#rfc4271-9.2.2.2-2) If any aggregated route has ORIGIN INCOMPLETE, the aggregate MUST have ORIGIN INCOMPLETE; else if any has EGP, the aggregate MUST have ORIGIN EGP (§9.2.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze never aggregates routes, so no producer computes an aggregate ORIGIN. ORIGIN is only parsed (internal/core/bgp/attribute/origin.go:146-160), interned (internal/component/bgp/plugins/rib/storage/attrparse.go) and re-emitted verbatim (internal/component/bgp/plugins/rib/storage/familyrib.go:799-801); the same aggregation grep returns nothing | | [`RFC4271-9.2.2.2-3`](#rfc4271-9.2.2.2-3) When aggregating routes with different NEXT_HOP, the aggregated NEXT_HOP SHALL identify an interface on the aggregating speaker (§9.2.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze never aggregates routes, so no producer chooses an aggregated NEXT_HOP. The only next-hop selection is per-route egress policy (internal/component/bgp/reactor/peer_forward_facts.go:153-193); the same aggregation grep returns nothing | | [`RFC4271-9.2.2.2-4`](#rfc4271-9.2.2.2-4) If at least one aggregated route has ATOMIC_AGGREGATE, the aggregate SHALL have it as well (§9.2.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze never aggregates routes, so no producer decides whether an aggregate carries ATOMIC_AGGREGATE. The attribute is only decoded, stored and replayed (internal/core/bgp/attribute/simple.go:175-195, internal/component/bgp/plugins/rib/storage/familyrib.go:815-817); the same aggregation grep returns nothing | | [`RFC4271-9.2.2.2-5`](#rfc4271-9.2.2.2-5) AGGREGATOR attributes from aggregated routes MUST NOT be included in the aggregated route (§9.2.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze never aggregates routes, so no producer builds an aggregated route from which a contributing AGGREGATOR would have to be excluded. AGGREGATOR is only interned from the wire or emitted from operator configuration (internal/component/bgp/plugins/rib/storage/attrparse.go:96-102, internal/component/bgp/message/update_build_grouped.go:141-148); the same aggregation grep returns nothing | | [`RFC4271-9.2.1.1-3`](#rfc4271-9.2.1.1-3) The last route selected while awaiting MinRouteAdvertisementIntervalTimer SHALL be advertised at expiry (§9.2.1.1) | {gap}, no test | with no MinRouteAdvertisementIntervalTimer there is no expiry at which a last-selected route could be advertised. The timer is absent by design note (internal/component/bgp/fsm/timer.go:39) and no producer buffers a pending best-route advertisement against such a timer; best-path changes are published as they are computed (internal/component/bgp/plugins/rib/rib_bestchange.go:832-880) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4271-4.1-1`](#rfc4271-4.1-1) Marker field MUST be set to all ones (16 bytes of 0xFF) (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271MarkerNotAllOnesRejected`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L42) | unit/verify | unproven | | positive | [`TestRFC4271MarkerAllOnesOnSend`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L19) | unit/verify | unproven | ### [`RFC4271-4.1-2`](#rfc4271-4.1-2) Length field MUST have the smallest value required given the rest of the message (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271NonSmallestLengthRejected`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L95) | unit/verify | unproven | | positive | [`TestRFC4271SmallestLengthOnSend`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L62) | unit/verify | unproven | ### [`RFC4271-4.1-3`](#rfc4271-4.1-3) Message Length MUST be between 19 and 4096 octets (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271MessageLengthOutOfBounds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L150) | unit/verify | unproven | | positive | [`TestRFC4271MessageLengthWithinBounds`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L124) | unit/verify | unproven | ### [`RFC4271-4.3-1`](#rfc4271-4.3-1) For well-known attributes, the Transitive bit MUST be set to 1 (§4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271WellKnownAttributeErrorsAreCaught`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L500) | unit/verify | unproven | | positive | [`TestRFC4271WellKnownAttributesAreTransitive`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L31) | unit/verify | unproven | ### [`RFC4271-4.3-2`](#rfc4271-4.3-2) Partial bit MUST be set to 0 for well-known and optional non-transitive attributes (§4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271PartialBitClearedOnReadvertisedWellKnown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L47) | unit/verify | unproven | | negative | [`TestRFC4271PartialNotStampedOnExcludedClasses`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L170) | unit/verify | unproven | | positive | [`TestRFC4271PartialNotSetOnRecognizedOrNonTransitive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1116) | unit/verify | unproven | | positive | [`TestRFC4271PartialBitClearOnSend`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L55) | unit/verify | unproven | ### [`RFC4271-4.3-3`](#rfc4271-4.3-3) Lower-order four bits of attribute flags MUST be zero when sent (§4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4271AttributeFlagsLowNibbleZeroOnSend`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L80) | unit/verify | unproven | ### [`RFC4271-4.3-4`](#rfc4271-4.3-4) Lower-order four bits of attribute flags MUST be ignored when received (§4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271AttrFlagsHighBitsNotIgnored`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L259) | unit/verify | unproven | | positive | [`TestRFC4271AttrFlagsLowNibbleIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L232) | unit/verify | unproven | ### [`RFC4271-4.4-1`](#rfc4271-4.4-1) KEEPALIVE messages MUST NOT be sent more frequently than one per second (§4.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271KeepaliveIntervalNeverSubSecond`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_test.go#L50) | unit/verify | unproven | | positive | [`TestRFC4271KeepaliveNotFasterThanOnePerSecond`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_test.go#L18) | unit/verify | unproven | ### [`RFC4271-4.4-2`](#rfc4271-4.4-2) If the negotiated Hold Time is zero, periodic KEEPALIVE messages MUST NOT be sent (§4.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestKeepaliveWithZeroHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/timer_test.go#L411) | unit/verify | unproven | | positive | [`TestTimersKeepaliveTimer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/timer_test.go#L144) | unit/verify | unproven | ### [`RFC4271-6-1`](#rfc4271-6-1) If no Error Subcode is specified, a zero MUST be used (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271NotificationSpecifiedSubcodePreserved`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L376) | unit/verify | unproven | | positive | [`TestRFC4271NotificationUnspecifiedSubcodeIsZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L353) | unit/verify | unproven | ### [`RFC4271-4.2-1`](#rfc4271-4.2-1) Hold Time MUST be either zero or at least three seconds (§4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpenValidateHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/open_test.go#L341) | unit/verify | unproven | | positive | [`TestOpenValidateHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/open_test.go#L339) | unit/verify | unproven | | positive | [`open-hold-time-peer-lower-wins.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/open-hold-time-peer-lower-wins.ci#L3) | functional/verify | unproven | ### [`RFC4271-4.2-2`](#rfc4271-4.2-2) BGP speaker MUST calculate Hold Timer by using the smaller of its configured Hold Time and the received Hold Time (§4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegotiateWith_HoldTimeZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L74) | unit/verify | unproven | | positive | [`TestNegotiateWith_HoldTimeMinOfBoth`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L45) | unit/verify | unproven | ### [`RFC4271-6.2-1`](#rfc4271-6.2-1) An implementation MUST reject Hold Time values of one or two seconds (§6.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpenValidateHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/open_test.go#L345) | unit/verify | unproven | | positive | [`TestOpenValidateHoldTime`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/open_test.go#L343) | unit/verify | unproven | ### [`RFC4271-6.2-2`](#rfc4271-6.2-2) An implementation that accepts a Hold Time MUST use the negotiated value (§6.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271LocalHoldTimeNotUsedWhenPeerProposesSmaller`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L192) | unit/verify | unproven | | positive | [`TestRFC4271NegotiatedHoldTimeDrivesTimers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L171) | unit/verify | unproven | ### [`RFC4271-5-1`](#rfc4271-5-1) BGP implementations MUST recognize all well-known attributes (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271WellKnownAttributeErrorsAreCaught`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L492) | unit/verify | unproven | | positive | [`TestRFC4271WellKnownAttributesAreRecognized`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L463) | unit/verify | unproven | ### [`RFC4271-5-2`](#rfc4271-5-2) Well-known mandatory attributes MUST be included in every UPDATE containing NLRI (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271WellKnownAttributeErrorsAreCaught`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L497) | unit/verify | unproven | | positive | [`TestRFC4271WellKnownAttributesAreRecognized`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L466) | unit/verify | unproven | ### [`RFC4271-5-3`](#rfc4271-5-3) Unrecognized transitive optional attributes MUST be passed along with the Partial bit set to 1 (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271PartialNotSetOnRecognizedOrNonTransitive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1113) | unit/verify | unproven | | negative | [`TestRFC4271PartialNotStampedOnExcludedClasses`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L167) | unit/verify | unproven | | positive | [`TestRFC4271PartialSetOnUnrecognizedTransitiveOptional`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1084) | unit/verify | unproven | | positive | [`TestRFC4271PartialStampedOnUnrecognizedTransitive`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L132) | unit/verify | unproven | | positive | [`rfc4271-partial-unknown-transitive.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/rfc4271-partial-unknown-transitive.ci#L27) | functional/verify | unproven | ### [`RFC4271-5-4`](#rfc4271-5-4) Partial bit set to 1 by a previous AS MUST NOT be set back to 0 (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271PartialBitSurvivesLengthReframing`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L115) | unit/verify | unproven | | negative | [`TestRFC4271PartialFromPreviousASNotCleared`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4271_test.go#L197) | unit/verify | unproven | | positive | [`TestRFC4271PartialBitPreservedOnUnknownTransitive`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L82) | unit/verify | unproven | | positive | [`TestRFC4271PartialFromPreviousASNeverCleared`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1152) | unit/verify | unproven | ### [`RFC4271-5-5`](#rfc4271-5-5) Unrecognized non-transitive optional attributes MUST be quietly ignored (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271TheNonTransitiveDropSparesEveryOtherClass`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1251) | unit/verify | unproven | | positive | [`TestRFC4271UnrecognizedNonTransitiveIsNotPassedAlong`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1210) | unit/verify | unproven | ### [`RFC4271-5-6`](#rfc4271-5-6) Receiver of an UPDATE MUST be prepared to handle path attributes that are out of order (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271OutOfOrderDoesNotMaskMalformation`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L323) | unit/verify | unproven | | positive | [`TestRFC4271AttributesOutOfOrderAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L293) | unit/verify | unproven | ### [`RFC4271-4.3-5`](#rfc4271-4.3-5) A BGP speaker MUST be able to process UPDATE messages with the same prefix in both WITHDRAWN and NLRI (§4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRIBInjectSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L119) | unit/verify | unproven | | positive | [`TestRIBPoolPathSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L85) | unit/verify | unproven | | positive | [`TestRIBSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L39) | unit/verify | unproven | ### [`RFC4271-5.1.3-1`](#rfc4271-5.1.3-1) A route originated by a BGP speaker SHALL NOT be advertised to a peer using that peer's address as NEXT_HOP (§5.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEgressNextHopIsPeerOwnReadsTheRewrittenAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L286) | unit/verify | unproven | | negative | [`TestForwardRSWithholdsRouteWhoseNextHopIsTheClientsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L255) | unit/verify | unproven | | negative | [`TestForwardWithholdsRouteWhoseNextHopIsTheDestinationsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L186) | unit/verify | unproven | | negative | [`TestSendAnnounceWithholdsRouteWithPeerOwnNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L466) | unit/verify | unproven | | negative | [`TestSendUpdateWithholdsOriginatedRouteWithPeerOwnNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L429) | unit/verify | unproven | | negative | [`checkSelfNextHopWithheld`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L962) | interop/nightly | unproven | | negative | [`originated-nexthop-peer-own.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/originated-nexthop-peer-own.ci#L10) | functional/verify | unproven | | positive | [`TestEgressNextHopIsPeerOwnReadsTheRewrittenAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L282) | unit/verify | unproven | | positive | [`TestForwardRSWithholdsRouteWhoseNextHopIsTheClientsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L252) | unit/verify | unproven | | positive | [`TestForwardWithdrawsFromDestinationWhoseNextHopIsItsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L217) | unit/verify | unproven | | positive | [`TestForwardWithholdsRouteWhoseNextHopIsTheDestinationsOwnAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L181) | unit/verify | unproven | | positive | [`TestSendAnnounceWithholdsRouteWithPeerOwnNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L464) | unit/verify | unproven | | positive | [`TestSendUpdateWithholdsOriginatedRouteWithPeerOwnNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_next_hop_test.go#L425) | unit/verify | unproven | | positive | [`checkSelfNextHopWithheld`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L961) | interop/nightly | unproven | | positive | [`originated-nexthop-peer-own.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/originated-nexthop-peer-own.ci#L7) | functional/verify | unproven | ### [`RFC4271-5.1.3-2`](#rfc4271-5.1.3-2) A BGP speaker SHALL NOT install a route with itself as the next hop (§5.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271SelfNextHopDoesNotShadowASoundAlternative`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L89) | unit/verify | unproven | | negative | [`TestRFC4271SelfNextHopRouteIsNotInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L47) | unit/verify | unproven | | negative | [`TestRFC4271SelfNextHopSetComesFromPeerEvents`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L126) | unit/verify | unproven | | positive | [`TestRFC4271SelfNextHopRouteIsNotInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L43) | unit/verify | unproven | | positive | [`TestRFC4271SelfNextHopSetComesFromPeerEvents`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L122) | unit/verify | unproven | ### [`RFC4271-5.1.3-3`](#rfc4271-5.1.3-3) A BGP speaker MUST be able to support disabling advertisement of third-party NEXT_HOP attributes (§5.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ThirdPartyNextHopDisableFailsClosed`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L373) | unit/verify | unproven | | positive | [`TestRFC4271ThirdPartyNextHopCanBeDisabled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L341) | unit/verify | unproven | ### [`RFC4271-5.1.4-1`](#rfc4271-5.1.4-1) MULTI_EXIT_DISC received from a neighboring AS MUST NOT be propagated to other neighboring ASes (§5.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestForwardKeepsFilterSetMED`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L322) | unit/verify | unproven | | negative | [`TestForwardSuppressesReceivedMEDToAnotherAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L226) | unit/verify | unproven | | negative | [`TestForwardWritesLocallySetMED`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L289) | unit/verify | unproven | | negative | [`TestMEDPropagationAllowedTo`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L426) | unit/verify | unproven | | negative | [`checkMEDAcrossAS`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L200) | interop/nightly | unproven | | negative | [`med-locally-set-reaches-peer.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-locally-set-reaches-peer.ci#L4) | functional/verify | unproven | | negative | [`med-not-propagated-across-as.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-not-propagated-across-as.ci#L8) | functional/verify | unproven | | positive | [`TestForwardSuppressesReceivedMEDToAnotherAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L221) | unit/verify | unproven | | positive | [`checkMEDAcrossAS`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L196) | interop/nightly | unproven | | positive | [`med-not-propagated-across-as.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-not-propagated-across-as.ci#L4) | functional/verify | unproven | ### [`RFC4271-5.1.4-4`](#rfc4271-5.1.4-4) A BGP speaker MUST implement a mechanism (based on local configuration) that allows the MULTI_EXIT_DISC attribute to be removed from a route (§5.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseModifyDefsMEDRemove`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/filter_modify/modify_test.go#L647) | unit/verify | unproven | | negative | [`TestMEDRemovalMechanismIsConfigurable`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L475) | unit/verify | unproven | | negative | [`TestMEDRemoveDirectiveIsValueless`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L568) | unit/verify | unproven | | negative | [`checkMEDRemovalConfiguration`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L322) | interop/nightly | unproven | | positive | [`TestParseModifyDefsMEDRemove`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/filter_modify/modify_test.go#L642) | unit/verify | unproven | | positive | [`TestMEDRemovalMechanismIsConfigurable`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L468) | unit/verify | unproven | | positive | [`checkMEDRemovalConfiguration`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L318) | interop/nightly | unproven | | positive | [`med-removal-configured.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-configured.ci#L4) | functional/verify | unproven | ### [`RFC4271-5.1.4-2`](#rfc4271-5.1.4-2) MULTI_EXIT_DISC removal from routes MUST be done before the route is used in Phase 2 of the decision process (§5.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestHandleFilterUpdateMEDRemoveIsImportOnly`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/filter_modify/modify_test.go#L722) | unit/verify | unproven | | negative | [`TestMEDRemovalMechanismIsConfigurable`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L488) | unit/verify | unproven | | negative | [`med-removal-before-decision.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-before-decision.ci#L10) | functional/verify | unproven | | negative | [`med-removal-export-refused.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-export-refused.ci#L4) | functional/verify | unproven | | positive | [`TestHandleFilterUpdateMEDRemoveIsImportOnly`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/filter_modify/modify_test.go#L715) | unit/verify | unproven | | positive | [`TestMEDRemovalMechanismIsConfigurable`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_med_test.go#L480) | unit/verify | unproven | | positive | [`med-removal-before-decision.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-before-decision.ci#L4) | functional/verify | unproven | | positive | [`med-removal-configured.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/med-removal-configured.ci#L8) | functional/verify | unproven | ### [`RFC4271-5.1.5-1`](#rfc4271-5.1.5-1) LOCAL_PREF SHALL be included in all UPDATE messages sent to internal peers (§5.1.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestForwardLocalPrefStrippedToExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_local_pref_test.go#L45) | unit/verify | unproven | | negative | [`TestAnnounceStripsLocalPrefTowardExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_origin_test.go#L307) | unit/verify | unproven | | negative | [`TestRFC4271LocalPrefOmittedForExternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L107) | unit/verify | unproven | | positive | [`TestRFC4271LocalPrefIncludedForInternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L81) | unit/verify | unproven | ### [`RFC4271-5.1.5-2`](#rfc4271-5.1.5-2) A BGP speaker MUST NOT include LOCAL_PREF in UPDATE messages sent to external peers (§5.1.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLocalPrefAllowedToIsTheOnlyAnswer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_local_pref_test.go#L146) | unit/verify | unproven | | negative | [`TestRFC4271LocalPrefIncludedForInternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L83) | unit/verify | unproven | | negative | [`local-pref-strip-ebgp.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/local-pref-strip-ebgp.ci#L19) | functional/verify | unproven | | positive | [`TestForwardLocalPrefStripBeatsAFilterSet`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_local_pref_test.go#L113) | unit/verify | unproven | | positive | [`TestForwardLocalPrefStrippedToExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_local_pref_test.go#L41) | unit/verify | unproven | | positive | [`TestAnnounceStripsLocalPrefTowardExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_origin_test.go#L305) | unit/verify | unproven | | positive | [`TestRFC4271LocalPrefOmittedForExternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L104) | unit/verify | unproven | | positive | [`checkLocalPrefStrip`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L96) | interop/nightly | unproven | | positive | [`local-pref-strip-ebgp.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/local-pref-strip-ebgp.ci#L14) | functional/verify | unproven | ### [`RFC4271-5.1.5-3`](#rfc4271-5.1.5-3) If LOCAL_PREF is received over EBGP, it MUST be ignored (§5.1.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271LocalPrefIgnoredOnExternalSession`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L589) | unit/verify | unproven | | positive | [`TestRFC4271LocalPrefKeptOnInternalSession`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L566) | unit/verify | unproven | ### [`RFC4271-5.1.5-4`](#rfc4271-5.1.5-4) The higher degree of preference (LOCAL_PREF) MUST be preferred (§5.1.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBestPath_LocalPref`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L199) | unit/verify | unproven | | positive | [`TestBestPath_LocalPref`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L197) | unit/verify | unproven | ### [`RFC4271-5.1.6-1`](#rfc4271-5.1.6-1) A BGP speaker that receives a route with the ATOMIC_AGGREGATE attribute MUST NOT make any NLRI of that route more specific when advertising this route to other BGP speakers (§5.1.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-5.1.6-1, so no unit is bound to it. ### [`RFC4271-6.1-1`](#rfc4271-6.1-1) All header errors MUST be indicated by sending NOTIFICATION with Error Code Message Header Error (§6.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-6.1-1, so no unit is bound to it. ### [`RFC4271-6.1-2`](#rfc4271-6.1-2) Marker not all ones: Error Subcode MUST be Connection Not Synchronized (§6.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-6.1-2, so no unit is bound to it. ### [`RFC4271-6.1-3`](#rfc4271-6.1-3) Invalid length: Error Subcode MUST be Bad Message Length; Data field MUST contain the erroneous Length field (§6.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-6.1-3, so no unit is bound to it. ### [`RFC4271-6.1-4`](#rfc4271-6.1-4) Invalid type: Error Subcode MUST be Bad Message Type; Data field MUST contain the erroneous Type field (§6.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-6.1-4, so no unit is bound to it. ### [`RFC4271-6.2-3`](#rfc4271-6.2-3) All OPEN errors MUST be indicated by NOTIFICATION with Error Code OPEN Message Error (§6.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-6.2-3, so no unit is bound to it. ### [`RFC4271-6.3-1`](#rfc4271-6.3-1) All UPDATE errors MUST be indicated by NOTIFICATION with Error Code UPDATE Message Error (§6.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConformantUpdateSendsNoUpdateError`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L696) | unit/verify | unproven | | positive | [`TestRFC4271UpdateErrorReportedAsUpdateMessageError`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L633) | unit/verify | unproven | ### [`RFC4271-6.7-1`](#rfc4271-6.7-1) Cease NOTIFICATION MUST NOT be used when a fatal error does exist (§6.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271UpdateErrorReportedAsUpdateMessageError`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L636) | unit/verify | unproven | | positive | [`TestPrefixExceedTeardown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L110) | unit/verify | unproven | ### [`RFC4271-8.2.1-1`](#rfc4271-8.2.1-1) BGP MUST maintain a separate FSM for each configured peer (§8.2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271PerPeerFSMDoesNotShareTimers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L237) | unit/verify | unproven | | positive | [`TestRFC4271SeparateFSMPerPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L213) | unit/verify | unproven | ### [`RFC4271-8.2.1-2`](#rfc4271-8.2.1-2) A BGP implementation MUST connect to and listen on TCP port 179 (§8.2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ExplicitPortOverridesDefault`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L322) | unit/verify | unproven | | positive | [`TestRFC4271DefaultBGPPortIs179`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L306) | unit/verify | unproven | ### [`RFC4271-8.2.1-3`](#rfc4271-8.2.1-3) For each incoming connection, a state machine MUST be instantiated (§8.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-8.2.1-3, so no unit is bound to it. ### [`RFC4271-8.2.2-1`](#rfc4271-8.2.2-1) Event 10 (HoldTimer_Expires) lists "sends a NOTIFICATION message with the error code Hold Timer Expired" before "drops the TCP connection", in OpenSent, OpenConfirm and Established alike; a peer whose connection is dropped without it is told nothing. §8 states the FSM description is conceptual but binds "the same externally visible behavior", and the NOTIFICATION is the externally visible part, so the level is MUST (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271HoldTimerNotYetExpiredSendsNoNotification`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L836) | unit/verify | unproven | | positive | [`TestRFC4271HoldTimerExpirySendsNotification`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L807) | unit/verify | unproven | | positive | [`deadpeer-holddown.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/deadpeer-holddown.ci#L3) | functional/verify | unproven | ### [`RFC4271-8.2.2-2`](#rfc4271-8.2.2-2) Event 10 (HoldTimer_Expires) lists "sets the ConnectRetryTimer to zero", in OpenSent, OpenConfirm and Established alike. A ConnectRetryTimer left running past the teardown fires against a connection that no longer exists, and the retry it schedules is externally visible as an unexpected connection attempt, so the level is MUST on the same §8 "same externally visible behavior" ground as -1 (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271NoHoldExpiryLeavesTheSessionIntact`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1000) | unit/verify | unproven | | positive | [`TestRFC4271HoldExpiryRunsTheEvent10ActionList`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L929) | unit/verify | unproven | ### [`RFC4271-8.2.2-3`](#rfc4271-8.2.2-3) Event 10 (HoldTimer_Expires) lists "releases all BGP resources", in OpenSent, OpenConfirm and Established alike. The externally visible part is the KeepaliveTimer: a session torn down for silence that keeps its keepalive chain running writes KEEPALIVEs after the teardown, so the level is MUST (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271NoHoldExpiryLeavesTheSessionIntact`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1002) | unit/verify | unproven | | positive | [`TestRFC4271HoldExpiryRunsTheEvent10ActionList`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L932) | unit/verify | unproven | ### [`RFC4271-8.2.2-4`](#rfc4271-8.2.2-4) Event 10 (HoldTimer_Expires) lists "drops the TCP connection", in OpenSent, OpenConfirm and Established alike. The peer must see the connection go away and not merely stop receiving; a half-open socket is externally visible, so the level is MUST (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271NoHoldExpiryLeavesTheSessionIntact`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1005) | unit/verify | unproven | | positive | [`TestRFC4271HoldExpiryRunsTheEvent10ActionList`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L935) | unit/verify | unproven | ### [`RFC4271-8.2.2-5`](#rfc4271-8.2.2-5) Event 10 (HoldTimer_Expires) lists "changes its state to Idle", in OpenSent, OpenConfirm and Established alike. The Established->Idle transition is what deletes the routes learned over the connection, which is externally visible to every other peer, so the level is MUST (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271NoHoldExpiryLeavesTheSessionIntact`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L1007) | unit/verify | unproven | | positive | [`TestRFC4271HoldExpiryRunsTheEvent10ActionList`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L938) | unit/verify | unproven | ### [`RFC4271-8.2.2-7`](#rfc4271-8.2.2-7) ManualStart (Event 1) in Idle lists "sets ConnectRetryCounter to zero", once for the Connect branch (Events 1 and 3) and again, in the same words, for the passive Active branch (Events 4 and 5). §8 makes ConnectRetryCounter a mandatory session attribute and defines it as "the number of times a BGP peer has tried to establish a peer session", so an operator start that left a stale retry history behind would misreport that number; the level is MUST (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterSurvivesDampedStart`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L75) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterZeroedOnManualStart`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L42) | unit/verify | unproven | ### [`RFC4271-8.2.2-8`](#rfc4271-8.2.2-8) ManualStop (Event 2) lists "sets the ConnectRetryCounter to zero", in Connect, Active, OpenSent, OpenConfirm and Established alike. Same §8 mandatory-attribute ground as -7: the operator stopping the peer ends the run of attempts the counter was counting (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterNotZeroedByIdleManualStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L130) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterZeroedOnManualStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L108) | unit/verify | unproven | ### [`RFC4271-8.2.2-9`](#rfc4271-8.2.2-9) HoldTimer_Expires (Event 10) lists "increments the ConnectRetryCounter", in OpenSent, OpenConfirm and Established alike (OpenSent omits the words "by 1" that the other two carry; the counter is an integer count, so the step is one). A session lost to silence is an attempt that failed, and §8 makes the count mandatory (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterQuietOnHealthyEstablishedTraffic`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L174) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterIncrementsOnHoldTimerExpiry`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L151) | unit/verify | unproven | ### [`RFC4271-8.2.2-10`](#rfc4271-8.2.2-10) BGPHeaderErr (Event 21) and BGPOpenMsgErr (Event 22) list "increments the ConnectRetryCounter by 1" in Connect, Active, OpenSent and OpenConfirm, and in Established through "In response to any other event (Events 9, 12-13, 20-22)", which names both (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterNotIncrementedByIdleErrors`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L236) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterIncrementsOnHeaderAndOpenErrors`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L210) | unit/verify | unproven | ### [`RFC4271-8.2.2-11`](#rfc4271-8.2.2-11) NotifMsg (Event 25) leads to "increments the ConnectRetryCounter by 1" in every state that can see it: explicitly in OpenConfirm ("a TcpConnectionFails event (Event 18) [...] or a NOTIFICATION message (Event 25)") and Established ("a NOTIFICATION message (Event 24 or Event 25)"), and through the "any other event" list that names Event 25 in Connect, Active and OpenSent (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterStepsByExactlyOnePerNotification`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L283) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterIncrementsOnNotification`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L258) | unit/verify | unproven | ### [`RFC4271-8.2.2-12`](#rfc4271-8.2.2-12) NotifMsgVerErr (Event 24) increments the ConnectRetryCounter in Connect, Active and Established, and MUST NOT in OpenSent or OpenConfirm. In Connect and Active the clause sits in the DelayOpenTimer-is-not-running branch, which is the only branch a speaker without a DelayOpenTimer can take, and Established groups Event 24 with Events 25 and 18; in OpenSent and OpenConfirm the Event 24 action list is four items long and has no ConnectRetryCounter line at all (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterQuietOnVersionErrorInOpenStates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L332) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterOnVersionErrorPerState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L308) | unit/verify | unproven | ### [`RFC4271-8.2.2-13`](#rfc4271-8.2.2-13) TcpConnectionFails (Event 18) increments the ConnectRetryCounter in Active, OpenConfirm and Established, and MUST NOT in Connect or OpenSent. Connect's Event 18 has two branches and neither carries the clause, and OpenSent's Event 18 leaves for Active rather than tearing the peering down (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterQuietOnTCPFailureInConnectAndOpenSent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L378) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterOnTCPFailurePerState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L355) | unit/verify | unproven | ### [`RFC4271-8.2.2-14`](#rfc4271-8.2.2-14) UpdateMsgErr (Event 28) in Established lists "increments the ConnectRetryCounter by 1". An UPDATE error ends the session, which makes it a failed attempt by the §8 definition of the attribute (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterQuietOnGoodUpdate`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L421) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterIncrementsOnUpdateError`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L401) | unit/verify | unproven | ### [`RFC4271-8.2.2-15`](#rfc4271-8.2.2-15) The "any other event" action list lists "increments the ConnectRetryCounter by 1" ("by one" in Active) in all five non-Idle states: Connect and Active (Events 8, 10-11, 13, 19, 23, 25-28), OpenSent (Events 9, 11-13, 20, 25-28), OpenConfirm (Events 9, 12-13, 20, 27-28) and Established (Events 9, 12-13, 20-22). Idle is excluded on purpose: its own "any other event" clause says the event "does not cause change in the state of the local system" and lists no actions (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterIdleDefaultArmCountsNothing`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L478) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterIncrementsOnAnyOtherEvent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L443) | unit/verify | unproven | ### [`RFC4271-8.2.2-16`](#rfc4271-8.2.2-16) AutomaticStop (Event 8) lists "increments the ConnectRetryCounter by 1" in OpenSent, OpenConfirm and Established, and Connect and Active name Event 8 in their "any other events" list, which carries the same line. Its action list differs from ManualStop's by that one line, and the difference is the point: §8 defines the attribute as "the number of times a BGP peer has tried to establish a peer session", so a stop the local system chose records a failed attempt where an operator stop ends the count (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterAutomaticStopIsNotAManualStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L575) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterIncrementsOnAutomaticStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L549) | unit/verify | unproven | ### [`RFC4271-8.2.2-17`](#rfc4271-8.2.2-17) OpenCollisionDump (Event 23) lists "increments the ConnectRetryCounter by 1" in OpenSent, OpenConfirm and Established, and Connect and Active name Event 23 in their "any other events" list, which carries the same line. A connection closed by §6.8 collision resolution is an attempt that did not become a session (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ConnectRetryCounterCollisionDumpIsQuietInIdle`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L630) | unit/verify | unproven | | positive | [`TestRFC4271ConnectRetryCounterIncrementsOnOpenCollisionDump`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/fsm/rfc4271_connect_retry_test.go#L606) | unit/verify | unproven | ### [`RFC4271-8.2.2-18`](#rfc4271-8.2.2-18) ManualStop (Event 2) lists the Cease NOTIFICATION FIRST in its action list, in every state that has a connection to send it on: OpenSent writes "sends the NOTIFICATION with a Cease", OpenConfirm and Established write "sends the NOTIFICATION message with a Cease", and all three list it before "drops the TCP connection". The level is MUST on the same §8 ground as -1: the FSM description is conceptual, but an implementation "MUST support the described functionality and exhibit the same externally visible behavior", and the NOTIFICATION is the externally visible part -- it is the ONLY signal that says the operator stopped this speaker rather than the network dropping it. §6.7's MAY governs a different act, a speaker CHOOSING to Cease "at any given time" of its own accord; once the operator has issued Event 2 the action list is what the FSM owes. Connect and Active list no Cease clause and are excluded: neither state holds a BGP connection (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271NoCeaseWithoutAManualStop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/shutdown_notify_test.go#L207) | unit/verify | unproven | | positive | [`TestShutdownNotifySendsCeaseFromEveryConnectedState`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/shutdown_notify_test.go#L174) | unit/verify | unproven | | positive | [`signal-stop-cease.ci`](https://github.com/ze-software/ze/blob/main/test/reload/signal-stop-cease.ci#L3) | functional/verify | unproven | ### [`RFC4271-10-1`](#rfc4271-10-1) An implementation MUST allow the HoldTimer to be configurable on a per-peer basis (§10) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271PerPeerHoldTimeSurvivesNegotiation`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L285) | unit/verify | unproven | | positive | [`TestRFC4271HoldTimeConfigurablePerPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L261) | unit/verify | unproven | ### [`RFC4271-5-7`](#rfc4271-5-7) Path attributes SHOULD be ordered in ascending order of attribute type (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSplitMP_PreservesAscendingAttributeOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/update_split_attr_order_test.go#L72) | unit/verify | unproven | | positive | [`TestAnnounceBatchRail_AS4PathOrderedAgainstLargeCommunity`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_batch_attr_order_test.go#L328) | unit/verify | unproven | | positive | [`TestAnnounceBatchRail_AscendingTypeCodeOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_batch_attr_order_test.go#L268) | unit/verify | unproven | | positive | [`TestAnnounceQueuedRail_AscendingTypeCodeOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_api_batch_attr_order_test.go#L289) | unit/verify | unproven | ### [`RFC4271-6.3-2`](#rfc4271-6.3-2) Semantically incorrect NEXT_HOP SHOULD be logged and the route SHOULD be ignored (§6.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4271SelfNextHopRouteIsNotInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_self_nexthop_test.go#L52) | unit/verify | unproven | ### [`RFC4271-3.1-2`](#rfc4271-3.1-2) Next hop for routes in Loc-RIB MUST be resolvable via the local BGP speaker's Routing Table (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-3.1-2, so no unit is bound to it. ### [`RFC4271-5.1.2-2`](#rfc4271-5.1.2-2) When advertising a route to an internal peer, the speaker SHALL NOT modify the AS_PATH attribute (§5.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271ASPathPrependedTowardExternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L148) | unit/verify | unproven | | positive | [`TestEstablishedAnnounce_ExplicitASPath_IBGPVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_batch_test.go#L569) | unit/verify | unproven | | positive | [`TestRFC4271ASPathUnmodifiedTowardInternalPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L131) | unit/verify | unproven | ### [`RFC4271-5.1.2-3`](#rfc4271-5.1.2-3) When advertising a route to an external peer, the speaker prepends its own AS number to the leading AS_SEQUENCE of AS_PATH (creating one when the path is empty or led by an AS_SET) (§5.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEstablishedAnnounce_ExplicitASPath_IBGPVerbatim`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_batch_test.go#L567) | unit/verify | unproven | | negative | [`TestASPathSlotPrependOnlyWhenAdvertising`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/advertise_test.go#L99) | unit/verify | unproven | | negative | [`checkRelayWithdrawalShape`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L432) | interop/nightly | unproven | | positive | [`TestEstablishedAnnounce_ExplicitASPath_PrependsLocalAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_batch_test.go#L543) | unit/verify | unproven | | positive | [`TestASPathSlotPrependOnlyWhenAdvertising`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/wireu/advertise_test.go#L97) | unit/verify | unproven | | positive | [`checkRelayWithdrawalShape`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L431) | interop/nightly | unproven | ### [`RFC4271-5.1.4-3`](#rfc4271-5.1.4-3) If altering MULTI_EXIT_DISC received over EBGP, alteration MUST be done prior to decision process phases 1 and 2 (§5.1.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4271MEDAlterationHappensAtIngress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L452) | unit/verify | unproven | ### [`RFC4271-5.1.5-5`](#rfc4271-5.1.5-5) A BGP speaker SHALL calculate the degree of preference for each external route based on locally-configured policy (§5.1.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271DegreeOfPreferenceNotAHardcodedConstant`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L168) | unit/verify | unproven | | positive | [`TestRFC4271ExternalRouteDegreeOfPreferenceFromLocalPolicy`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L141) | unit/verify | unproven | ### [`RFC4271-5.1.7-1`](#rfc4271-5.1.7-1) A BGP speaker that performs aggregation and adds AGGREGATOR SHALL include its own AS number and IP address (§5.1.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-5.1.7-1, so no unit is bound to it. ### [`RFC4271-6.7-4`](#rfc4271-6.7-4) When terminating due to prefix limit, speaker MUST send NOTIFICATION with Error Code Cease (§6.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPrefixExceedDrop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L144) | unit/verify | unproven | | positive | [`TestPrefixExceedTeardown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L107) | unit/verify | unproven | ### [`RFC4271-6.8-1`](#rfc4271-6.8-1) In the event of connection collision, one of the connections MUST be closed (§6.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCollisionOpenSentNoCollision`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/collision_test.go#L177) | unit/verify | unproven | | positive | [`TestCollisionOpenConfirmLocalWins`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/collision_test.go#L127) | unit/verify | unproven | ### [`RFC4271-6.8-2`](#rfc4271-6.8-2) Upon receipt of an OPEN message, the local system MUST examine all connections in OpenConfirm state for collision (§6.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCollisionNonCollisionStates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/collision_test.go#L536) | unit/verify | unproven | | positive | [`TestCollisionOpenConfirmLocalWins`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/collision_test.go#L130) | unit/verify | unproven | ### [`RFC4271-9-1`](#rfc4271-9-1) Withdrawn routes SHALL be removed from the Adj-RIB-In and the Decision Process SHALL be run (§9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271WithdrawRemovesFromAdjRIBIn`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L220) | unit/verify | unproven | | positive | [`TestRFC4271WithdrawRemovesFromAdjRIBIn`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L215) | unit/verify | unproven | ### [`RFC4271-9-2`](#rfc4271-9-2) A new route with identical NLRI to an existing route SHALL replace the older route in Adj-RIB-In (§9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271WithdrawRemovesFromAdjRIBIn`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L217) | unit/verify | unproven | | positive | [`TestRFC4271SamePrefixReplacesRatherThanAccumulates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L189) | unit/verify | unproven | ### [`RFC4271-9-3`](#rfc4271-9-3) Once the Adj-RIB-In is updated, the speaker SHALL run its Decision Process (§9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRIBBestChangeNoPublishSameBest`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L650) | unit/verify | unproven | | positive | [`TestRIBBestChangeWithdraw`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L686) | unit/verify | unproven | ### [`RFC4271-9.1.1-1`](#rfc4271-9.1.1-1) The degree of preference function SHALL NOT use the existence or attributes of other routes as inputs (§9.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271DegreeOfPreferenceFollowsOwnAttributes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L68) | unit/verify | unproven | | positive | [`TestRFC4271DegreeOfPreferenceIgnoresOtherRoutes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L33) | unit/verify | unproven | ### [`RFC4271-9.1.1-2`](#rfc4271-9.1.1-2) For external routes, the computed degree of preference MUST be used as the LOCAL_PREF value in IBGP readvertisement (§9.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271LocalPrefOmittedForExternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L110) | unit/verify | unproven | | positive | [`TestRFC4271LocalPrefIncludedForInternalPeers`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L86) | unit/verify | unproven | ### [`RFC4271-9.1.2-1`](#rfc4271-9.1.2-1) If NEXT_HOP is not resolvable, the BGP route MUST be excluded from Phase 2 decision function (§9.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.1.2-1, so no unit is bound to it. ### [`RFC4271-9.1.2-2`](#rfc4271-9.1.2-2) The local speaker SHALL install the best route in the Loc-RIB (§9.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRIBBestChangeWithdraw`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L691) | unit/verify | unproven | | positive | [`TestLocRIBMirror`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L1513) | unit/verify | unproven | ### [`RFC4271-9.1.2-3`](#rfc4271-9.1.2-3) The local speaker MUST determine the immediate next-hop address from the NEXT_HOP attribute (§9.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271LocRIBNextHopComesFromNextHopAttribute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L94) | unit/verify | unproven | | positive | [`TestLocRIBMirror`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L1515) | unit/verify | unproven | ### [`RFC4271-9.1.2-4`](#rfc4271-9.1.2-4) If immediate next-hop or IGP cost to NEXT_HOP changes, Phase 2 Route Selection MUST be performed again (§9.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.1.2-4, so no unit is bound to it. ### [`RFC4271-9.1.2.1-1`](#rfc4271-9.1.2.1-1) When installing a BGP route in the Routing Table, implementations MUST recalculate and take into account next-hops (§9.1.2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271LocRIBNextHopComesFromNextHopAttribute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L98) | unit/verify | unproven | | positive | [`TestLocRIBMirror`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L1518) | unit/verify | unproven | ### [`RFC4271-9.1.2.1-2`](#rfc4271-9.1.2.1-2) Unresolvable routes SHALL be removed from the Loc-RIB and the routing table (§9.1.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.1.2.1-2, so no unit is bound to it. ### [`RFC4271-9.1.2.2-1`](#rfc4271-9.1.2.2-1) The tie-breaking criteria MUST be applied in the order specified (§9.1.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBestPath_MED_SameNeighborAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L315) | unit/verify | unproven | | negative | [`TestBestPathEqualBGPIdentifiersOnTheJSONRailFallThroughToPeerAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_bgp_identifier_json_test.go#L106) | unit/verify | unproven | | negative | [`TestBestPathEqualBGPIdentifiersFallThroughToPeerAddress`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_bgp_identifier_test.go#L116) | unit/verify | unproven | | positive | [`TestBestPath_FullTiebreak`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L530) | unit/verify | unproven | | positive | [`TestBestPathStepFComparesThePeerBGPIdentifierOnTheJSONRail`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_bgp_identifier_json_test.go#L78) | unit/verify | unproven | | positive | [`TestBestPathStepFComparesThePeerBGPIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_bgp_identifier_test.go#L88) | unit/verify | unproven | ### [`RFC4271-9.1.2.2-2`](#rfc4271-9.1.2.2-2) If MULTI_EXIT_DISC is removed before IBGP readvertisement, the optional MED comparison MUST be performed only among EBGP-learned routes (§9.1.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.1.2.2-2, so no unit is bound to it. ### [`RFC4271-9.1.2.2-3`](#rfc4271-9.1.2.2-3) For IBGP-learned routes, MULTI_EXIT_DISC MUST be used in comparisons that reach the MED step (§9.1.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBestPath_MED_SameNeighborAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L322) | unit/verify | unproven | | positive | [`TestBestPath_MED_SameNeighborAS`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L319) | unit/verify | unproven | ### [`RFC4271-9.1.2.2-4`](#rfc4271-9.1.2.2-4) Routes that do not have the MULTI_EXIT_DISC attribute are considered to have the lowest possible MULTI_EXIT_DISC value (§9.1.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAbsentMedTiesAnExplicitMedOfZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L257) | unit/verify | revert, verified | | positive | [`TestAbsentMedStillComparesAsZeroInPhaseTwo`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_test.go#L225) | unit/verify | revert, verified | ### [`RFC4271-9.2-2`](#rfc4271-9.2-2) A route SHALL NOT be installed in Adj-RIB-Out unless its destination and NEXT_HOP may be forwarded by the Routing Table (§9.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2-2, so no unit is bound to it. ### [`RFC4271-9.2-3`](#rfc4271-9.2-3) If a route in Loc-RIB is excluded from a particular Adj-RIB-Out, the previously advertised route MUST be withdrawn (§9.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2-3, so no unit is bound to it. ### [`RFC4271-9.2-4`](#rfc4271-9.2-4) The Decision Process MUST consider both overlapping routes based on acceptance policy (§9.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271SamePrefixReplacesRatherThanAccumulates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L182) | unit/verify | unproven | | positive | [`TestRFC4271OverlappingRoutesBothInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L148) | unit/verify | unproven | ### [`RFC4271-9.2-5`](#rfc4271-9.2-5) If both less and more specific overlapping routes are accepted, the Decision Process MUST install both or an aggregate in Loc-RIB (§9.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271SamePrefixReplacesRatherThanAccumulates`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L186) | unit/verify | unproven | | positive | [`TestRFC4271OverlappingRoutesBothInstalled`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/rfc4271_test.go#L151) | unit/verify | unproven | ### [`RFC4271-9.2.1.1-2`](#rfc4271-9.2.1.1-2) Two UPDATE messages advertising to common destinations MUST be separated by at least MinRouteAdvertisementIntervalTimer (§9.2.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2.1.1-2, so no unit is bound to it. ### [`RFC4271-9.2.2.2-1`](#rfc4271-9.2.2.2-1) Routes with different MULTI_EXIT_DISC attributes SHALL NOT be aggregated (§9.2.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2.2.2-1, so no unit is bound to it. ### [`RFC4271-9.2.2.2-2`](#rfc4271-9.2.2.2-2) If any aggregated route has ORIGIN INCOMPLETE, the aggregate MUST have ORIGIN INCOMPLETE; else if any has EGP, the aggregate MUST have ORIGIN EGP (§9.2.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2.2.2-2, so no unit is bound to it. ### [`RFC4271-9.2.2.2-3`](#rfc4271-9.2.2.2-3) When aggregating routes with different NEXT_HOP, the aggregated NEXT_HOP SHALL identify an interface on the aggregating speaker (§9.2.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2.2.2-3, so no unit is bound to it. ### [`RFC4271-9.2.2.2-4`](#rfc4271-9.2.2.2-4) If at least one aggregated route has ATOMIC_AGGREGATE, the aggregate SHALL have it as well (§9.2.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2.2.2-4, so no unit is bound to it. ### [`RFC4271-9.2.2.2-5`](#rfc4271-9.2.2.2-5) AGGREGATOR attributes from aggregated routes MUST NOT be included in the aggregated route (§9.2.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2.2.2-5, so no unit is bound to it. ### [`RFC4271-Security-1`](#rfc4271-security-1) A BGP implementation MUST support TCP MD5 authentication (RFC 2385) (§Security, Appendix E) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestMD5PeersForListener`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_test.go#L2360) | unit/verify | unproven | | positive | [`TestMD5PeersForListener`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/reactor_test.go#L2357) | unit/verify | unproven | ### [`RFC4271-9.2.1.1-3`](#rfc4271-9.2.1.1-3) The last route selected while awaiting MinRouteAdvertisementIntervalTimer SHALL be advertised at expiry (§9.2.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4271-9.2.1.1-3, so no unit is bound to it. ### [`RFC4271-9.2-6`](#rfc4271-9.2-6) A BGP speaker SHALL NOT redistribute routing information from an internal peer to other internal peers (unless route reflector) (§9.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271IBGPRedistributionAllowedForReflectorClient`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L567) | unit/verify | unproven | | positive | [`TestRFC4271NoIBGPToIBGPRedistribution`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L508) | unit/verify | unproven | ### [`RFC4271-9.2-7`](#rfc4271-9.2-7) Newly unfeasible routes for which there is no replacement SHALL be advertised via UPDATE (§9.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRIBBestChangeNoPublishSameBest`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L653) | unit/verify | unproven | | positive | [`TestRIBBestChangeWithdraw`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L689) | unit/verify | unproven | ### [`RFC4271-9.2-8`](#rfc4271-9.2-8) Any routes in the Loc-RIB marked as unfeasible SHALL be removed (§9.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRIBBestChangeNoPublishSameBest`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L655) | unit/verify | unproven | | positive | [`TestLocRIBMirror`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_bestchange_test.go#L1521) | unit/verify | unproven | ### [`RFC4271-9.2-9`](#rfc4271-9.2-9) Changes to reachable destinations within the speaker's own AS SHALL be advertised in an UPDATE (§9.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271OwnASUnreachabilityChangeAdvertised`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L426) | unit/verify | unproven | | positive | [`TestRFC4271OwnASReachabilityChangeAdvertised`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4271_test.go#L398) | unit/verify | unproven | ### [`RFC4271-9.2-10`](#rfc4271-9.2-10) If a single route does not fit in an UPDATE message, the speaker MUST NOT advertise it and MAY log an error (§9.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4271FittingRouteIsAdvertised`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L431) | unit/verify | unproven | | positive | [`TestRFC4271OversizeSingleRouteNotAdvertised`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4271_test.go#L402) | unit/verify | unproven | ### [`RFC4271-9.1.2-5`](#rfc4271-9.1.2-5) If the AS_PATH attribute of a BGP route contains an AS loop, the BGP route should be excluded from the Phase 2 decision function (§9.1.2). Detection scans the full AS path and checks that the local autonomous system number does not appear in it. RFC 4271 writes this keyword in lower case, so the level is a recommendation and not a capitalized RFC 2119 SHOULD. The same paragraph places a speaker configured to accept routes with its own autonomous system number in the AS path outside the scope of the document. That out-of-scope case is what the allow-own-as setting selects, so a non-zero allow-own-as is not a deviation from this line. Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDetectASLoop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/filter/loop_test.go#L114) | unit/verify | unproven | | negative | [`TestDetectASLoop_ASSet`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/filter/loop_test.go#L125) | unit/verify | unproven | | negative | [`loop-as.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/loop-as.ci#L7) | functional/verify | unproven | | positive | [`TestDetectASLoop_NotPresent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/filter/loop_test.go#L136) | unit/verify | unproven | ### [`RFC4271-4.3-7`](#rfc4271-4.3-7) An UPDATE containing the same prefix in WITHDRAWN and NLRI SHOULD be treated as if the prefix is not in WITHDRAWN (§4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRIBInjectSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L121) | unit/verify | unproven | | positive | [`TestRIBPoolPathSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L87) | unit/verify | unproven | | positive | [`TestRIBSamePrefixInWithdrawnAndNLRIInstallsTheRoute`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rfc4271_rib_mixed_update_test.go#L42) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 4271, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4271, so its obligations are stated where they were written. --- ### Page: RFC 4301 - Security Architecture for the Internet Protocol https://ze-software.net/quality/rfc-compliance/rfc4301/ # RFC 4301 - Security Architecture for the Internet Protocol Partial. Every requirement this repository extracted from RFC 4301, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 30.0% | 6 of 20 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 35.0% | 7 of 20 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 20 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 30.6% | 11 of 36 tagged units, 0 escaped and 1 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 20 | of 23 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 6 | of 20 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 30.0% | 6 of 20 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 20 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 20 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 5.0% | 1 of 20 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 20 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 23 | | Gated MUST-level | 20 | | Not applicable, so out of scope | 6 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 36 | | Tagged units | 36 | | Recorded audit verdicts | 0 | | Discrimination records | 12 | | Summary | `rfc/short/rfc4301.md` | | Requirement shard | `rfc/requirements/rfc4301.md` | | RFC text | `rfc/full/rfc4301.txt` | ## Enrolment Enrolled: Security Architecture for IP (RFC 4301): native control-plane SPD/SAD projected to kernel XFRM; 1 MET (no null-enc/no-integrity ESP) + 7 single-polarity positive (tunnel/transport modes, SPD present, negotiated selectors, manual+automated keying) + 1 gap (port/ICMP selectors) + 6 not-applicable (per-packet datapath kernel-delegated) ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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. **What the ledger says remains** Lowered from `Supported` on 2026-08-30, after an extraction walk of [`rfc/full/rfc4301.txt`](https://github.com/ze-software/ze/blob/main/rfc/full/rfc4301.txt) found architecture obligations Ze does not meet. The implementation work is [`plan/immediate/spec-rfc4301-architecture-gaps.md`](https://github.com/ze-software/ze/blob/main/plan/immediate/spec-rfc4301-architecture-gaps.md), 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`](https://github.com/ze-software/ze/blob/main/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; [`plan/spec-ipsec-lifetime-volume.md`](https://github.com/ze-software/ze/blob/main/plan/spec-ipsec-lifetime-volume.md) designs the byte-count lifetime. Section 5.1.2.1, outer header: the DSCP value of the outer tunnel header is not mapped for the domain the packet enters. Section 4.4.1.1, selectors: a port selector holds any port or one exact port rather than the range the section defines, and `SPParams` carries no ICMP type or code field. Only that last item is gated in [`rfc/short/rfc4301.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4301.md), as [`RFC4301-4.4.1.1-1`](#rfc4301-4.4.1.1-1), and its reason text is stale about ports. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 6 | one part of the gated population | | Annotated instead of tested | 14 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **20** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (6):** [`RFC4301-4.2-1`](#rfc4301-4.2-1), [`RFC4301-4.4.3.1-1`](#rfc4301-4.4.3.1-1), [`RFC4301-4.4.3.1-2`](#rfc4301-4.4.3.1-2), [`RFC4301-4.4.1-4`](#rfc4301-4.4.1-4), [`RFC4301-7.4-1`](#rfc4301-7.4-1), [`RFC4301-7.4-2`](#rfc4301-7.4-2) **Annotated instead of tested (14):** [`RFC4301-4.1-1`](#rfc4301-4.1-1), [`RFC4301-4.1-2`](#rfc4301-4.1-2), [`RFC4301-4.1-3`](#rfc4301-4.1-3), [`RFC4301-4.1-4`](#rfc4301-4.1-4), [`RFC4301-4.1-5`](#rfc4301-4.1-5), [`RFC4301-4.4.1-1`](#rfc4301-4.4.1-1), [`RFC4301-4.4.1-2`](#rfc4301-4.4.1-2), [`RFC4301-4.4.1.1-1`](#rfc4301-4.4.1.1-1), [`RFC4301-4.4.2-1`](#rfc4301-4.4.2-1), [`RFC4301-4.5-1`](#rfc4301-4.5-1), [`RFC4301-5.2-1`](#rfc4301-5.2-1), [`RFC4301-5.2-2`](#rfc4301-5.2-2), [`RFC4301-4.1-6`](#rfc4301-4.1-6), [`RFC4301-7-1`](#rfc4301-7-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4301-4.1-1` | Host implementations MUST support both transport and tunnel mode (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L177). **positive:** `unit/verify` [`TestIPsecSAIsWildcardWithOSPFSelector`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L149). **negative:** no negative test. **{single-polarity}:** ze projects both modes -- IKE child SAs install tunnel-mode ESP and OSPFv3 RFC 4552 installs transport-mode ESP/AH; a capability-presence MUST has no meaningful negative (internal/component/ike/engine/child.go:224, :253, internal/plugins/ospf/ipsec_install.go:413, :441) | | `RFC4301-4.1-2` | Security gateways MUST support tunnel mode (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L192). **negative:** no negative test. **{single-polarity}:** ze (a security gateway) installs tunnel-mode ESP for every IKE-negotiated peer SA and policy (internal/component/ike/engine/child.go:224, :253, :281, :295) | | `RFC4301-4.1-3` | SAs between a security gateway and any peer MUST use tunnel mode (two narrow exceptions for gateway-as-host) (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L179). **negative:** no negative test. **{single-polarity}:** the IKE child-SA path hardcodes tunnel mode for every peer SA, so a peer SA can never be transport (internal/component/ike/engine/child.go:40, :224, :253) | | `RFC4301-4.1-4` | IKE-created SA pairs MUST use the same mode (both tunnel or both transport) (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L181). **negative:** no negative test. **{single-polarity}:** the inbound and outbound child SAs of a pair are both built with modeTunnel, so the pair is always same-mode (internal/component/ike/engine/child.go:224, :253) | | `RFC4301-4.1-5` | Implementation MUST permit multiple SAs between same endpoints with same selectors (QoS differentiation) (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** multiple SAs with identical selectors coexist by SPI in the kernel XFRM SAD and ze's SPI-keyed installs do not forbid this; the DSCP classification that selects among them for QoS is a datapath function ze does not model (internal/component/ike/dataplane/dataplane.go:81-108) | | `RFC4301-4.2-1` | MUST NOT instantiate an ESP SA with both NULL encryption and no integrity algorithm (§4.2) | MUST NOT | 4.2 | **positive:** `unit/verify` [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L124). **negative:** `unit/verify` [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L102) | | `RFC4301-4.4.1-1` | Implementation MUST have at least one SPD (§4.4.1) | MUST | 4.4.1 | **positive:** `unit/verify` [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L158). **negative:** no negative test. **{single-polarity}:** ze maintains an SPD policy model (SPParams) and installs at least the PROTECT entries into the kernel SPD via both the IKE and OSPFv3 paths (internal/component/ike/dataplane/dataplane.go:145, engine/child.go:276, :290, internal/plugins/ospf/ipsec_install.go:432-452) | | `RFC4301-4.4.1-2` | SPD MUST be consulted for ALL traffic crossing IPsec boundary, including IKE management traffic (§4.4.1, §5) | MUST | 4.4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** per-packet SPD consultation for every crossing packet is a kernel XFRM datapath function; ze populates the kernel SPD but does not process packets | | `RFC4301-4.4.1.1-1` | All implementations MUST support the defined selectors: remote/local IP, next-layer protocol, ports, ICMP type/code (§4.4.1.1) | MUST | 4.4.1.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's policy model carries IP-prefix and next-layer-protocol selectors only; the IKE traffic-selector negotiation discards ports/protocol into an address-only net.IPNet, and SPParams has no port or ICMP-type/code field (internal/component/ike/dataplane/dataplane.go:110-130 exists; ports/ICMP missing at internal/component/ike/engine/sa.go:128-129, engine/initiator.go:325) | | `RFC4301-4.4.2-1` | Inbound SAD entries MUST be populated with negotiated selector values for packet verification (§4.4.2) | MUST | 4.4.2 | **positive:** `unit/verify` [`TestChildSAInboundPolicyUsesNegotiatedTS`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L420). **positive:** `unit/verify` [`TestNarrowedSelectorsReachTheInstalledPolicy`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/ts_narrow_test.go#L423). **negative:** no negative test. **{single-polarity}:** ze captures the RFC 7296-narrowed negotiated traffic selectors and projects them into the inbound require-policy; the per-packet check against them is kernel-enforced (internal/component/ike/engine/child.go:155-164, :276-288) | | `RFC4301-4.5-1` | Implementations MUST support both manual and automated (IKEv2) key management (§4.5) | MUST | 4.5 | **positive:** `unit/verify` [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L152). **positive:** `unit/verify` [`TestIPsecInstallOnInterfaceUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L62). **negative:** no negative test. **{single-polarity}:** ze implements automated keying via its native IKEv2 engine and manual keying via the OSPFv3 RFC 4552 config, both installing SAs through the same dataplane seam (internal/component/ike/engine/child.go:199-307, internal/plugins/ospf/ipsec_install.go:295-342) | | `RFC4301-5.2-1` | Inbound packets not matching SPD-I MUST be discarded (§5.2) | MUST | 5.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** dropping inbound packets that fail the SPD-I match is a kernel XFRM datapath function; ze installs the inbound require-policies but does not process packets (internal/component/ike/engine/child.go:276) | | `RFC4301-5.2-2` | After decapsulation, inner packet selectors MUST be verified against SAD traffic selectors (§5.2) | MUST | 5.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** post-decapsulation inner-selector verification against the SAD is a kernel XFRM datapath function; the kernel verifies the inner packet against the inbound policy ze installs | | `RFC4301-4.1-6` | Multicast-capable implementations MUST support multicast SAD lookup (three-step: SPI+dst+src, SPI+dst, SPI alone) (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the three-step inbound SAD lookup (SPI+dst+src / SPI+dst / SPI) is a per-packet kernel XFRM function; ze performs no inbound SAD lookups | | `RFC4301-7-1` | AH/ESP MUST NOT be applied in transport mode to IPv4 fragments; use tunnel mode (§7) | MUST NOT | 7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze applies transport mode only to IPv6 OSPFv3 traffic (IPv4-family IPsec is rejected at config), so transport-mode IPsec on an IPv4 fragment structurally cannot arise, and per-packet fragment handling is kernel-delegated (internal/plugins/ospf/config_ipsec.go:21-22) | | `RFC4301-4.4.1-3` | SPD SHOULD have a default final entry that discards unmatched traffic (§4.4.1) | SHOULD | 4.4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4301-7-2` | Encapsulator SHOULD perform Path MTU Discovery and adjust tunnel MTU (§7) | SHOULD | 7 | **positive:** no positive test. **negative:** no negative test | | `RFC4301-4.1-7` | Security gateways MAY support transport mode only when acting as a host (e.g., SNMP management traffic) (§4.1) | MAY | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4301-4.4.3.1-1` | Sub-tree matching MUST be supported for distinguished names, domain names and RFC 822 e-mail addresses in PAD entries (§4.4.3.1) | MUST | 4.4.3.1 | **positive:** `unit/verify` [`TestPadSubtreeAdmitsAPeerBeneathIt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L35). **positive:** `unit/verify` [`TestPadSubtreeStillBindsTheCertificate`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L185). **positive:** `unit/verify` [`TestVidPeerAuthorizationSetsCommitOnlyAsRemoteID`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/validate_identity_test.go#L166). **negative:** `unit/verify` [`TestPadKeyIDStaysExact`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L111). **negative:** `unit/verify` [`TestPadSubtreeRefusesAPeerOutsideIt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L69) | | `RFC4301-4.4.3.1-2` | For IPv4 and IPv6 addresses in PAD entries, the same address range syntax used for SPD entries MUST be supported (§4.4.3.1) | MUST | 4.4.3.1 | **positive:** `unit/verify` [`TestPadAddressRangeAdmitsAnAddressInsideIt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L129). **positive:** `unit/verify` [`TestVidPeerAuthorizationSetsCommitOnlyAsRemoteID`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/validate_identity_test.go#L167). **negative:** `unit/verify` [`TestPadAddressRangeRefusesWhatIsOutsideIt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L155) | | `RFC4301-4.4.1-4` | A user or administrator MUST be able to order the SPD entries, and the management interface MUST support (total) ordering of them as seen via that interface (§4.4.1) | MUST | 4.4.1 | **positive:** `unit/verify` [`TestSPDPolicyMirrorsTheInboundSelector`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_discard_test.go#L94). **positive:** `unit/verify` [`TestSpdOperatorOrdersOverlappingPeers`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_order_test.go#L20). **negative:** `unit/verify` [`TestSPDPolicyOrderInsideBypassBandRefused`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/rfc4301_spd_discard_test.go#L123). **negative:** `unit/verify` [`TestSpdOrderCannotCaptureTheIKEControlPlane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_order_test.go#L107). **negative:** `unit/verify` [`TestSpdUnstatedOrderTakesTheDefaultRank`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_order_test.go#L67) | | `RFC4301-7.4-1` | All implementations MUST support DISCARDing of fragments using the normal SPD packet classification mechanisms (§7.4) | MUST | 7.4 | **positive:** `unit/verify` [`TestDiscardPolicyReachesTheBackend`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_discard_test.go#L45). **positive:** `unit/verify` [`TestDiscardPolicyReachesTheKernelAsBlock`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_discard_linux_test.go#L41). **positive:** `unit/verify` [`TestPolicyReadbackReportsDiscard`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_discard_linux_test.go#L98). **positive:** `unit/verify` [`TestSPDPolicyCarriesTheDiscardDisposition`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/rfc4301_spd_discard_test.go#L52). **positive:** `unit/verify` [`TestVPPSPDActionCarriesEveryDisposition`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_discard_vpp_test.go#L23). **negative:** `unit/verify` [`TestBypassPolicyIsNotBlocked`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_discard_linux_test.go#L64). **negative:** `unit/verify` [`TestBypassPolicyReachesTheBackendAsBypass`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_discard_test.go#L75). **negative:** `unit/verify` [`TestSPDPolicyBypassIsNotADiscard`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/rfc4301_spd_discard_test.go#L83) | | `RFC4301-7.4-2` | All implementations MUST support stateful fragment checking to accommodate BYPASS traffic for which a non-trivial port range is specified (§7.4; Appendix D.4 restates it as "implementations MUST support fragment reassembly for BYPASS/DISCARD traffic when port fields are specified") | MUST | 7.4 | **positive:** `unit/verify` [`TestPortScopedBypassNeverReachesTheForwardPath`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_fragment_test.go#L74). **positive:** `unit/verify` [`TestXFRMPortScopedBypassIsNeverStoredOnTheForwardPath`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_fragment_integration_linux_test.go#L69). **negative:** `unit/verify` [`TestIKEBypassIsPortScopedSoSection74Binds`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_fragment_test.go#L133) | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4301-4.1-5`](#rfc4301-4.1-5) Implementation MUST permit multiple SAs between same endpoints with same selectors (QoS differentiation) (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: multiple SAs with identical selectors coexist by SPI in the kernel XFRM SAD and ze's SPI-keyed installs do not forbid this; the DSCP classification that selects among them for QoS is a datapath function ze does not model (internal/component/ike/dataplane/dataplane.go:81-108) | | [`RFC4301-4.4.1-2`](#rfc4301-4.4.1-2) SPD MUST be consulted for ALL traffic crossing IPsec boundary, including IKE management traffic (§4.4.1, §5) | no test | no test carries this requirement id; annotated {not-applicable}: per-packet SPD consultation for every crossing packet is a kernel XFRM datapath function; ze populates the kernel SPD but does not process packets | | [`RFC4301-4.4.1.1-1`](#rfc4301-4.4.1.1-1) All implementations MUST support the defined selectors: remote/local IP, next-layer protocol, ports, ICMP type/code (§4.4.1.1) | {gap}, no test | ze's policy model carries IP-prefix and next-layer-protocol selectors only; the IKE traffic-selector negotiation discards ports/protocol into an address-only net.IPNet, and SPParams has no port or ICMP-type/code field (internal/component/ike/dataplane/dataplane.go:110-130 exists; ports/ICMP missing at internal/component/ike/engine/sa.go:128-129, engine/initiator.go:325) | | [`RFC4301-5.2-1`](#rfc4301-5.2-1) Inbound packets not matching SPD-I MUST be discarded (§5.2) | no test | no test carries this requirement id; annotated {not-applicable}: dropping inbound packets that fail the SPD-I match is a kernel XFRM datapath function; ze installs the inbound require-policies but does not process packets (internal/component/ike/engine/child.go:276) | | [`RFC4301-5.2-2`](#rfc4301-5.2-2) After decapsulation, inner packet selectors MUST be verified against SAD traffic selectors (§5.2) | no test | no test carries this requirement id; annotated {not-applicable}: post-decapsulation inner-selector verification against the SAD is a kernel XFRM datapath function; the kernel verifies the inner packet against the inbound policy ze installs | | [`RFC4301-4.1-6`](#rfc4301-4.1-6) Multicast-capable implementations MUST support multicast SAD lookup (three-step: SPI+dst+src, SPI+dst, SPI alone) (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: the three-step inbound SAD lookup (SPI+dst+src / SPI+dst / SPI) is a per-packet kernel XFRM function; ze performs no inbound SAD lookups | | [`RFC4301-7-1`](#rfc4301-7-1) AH/ESP MUST NOT be applied in transport mode to IPv4 fragments; use tunnel mode (§7) | no test | no test carries this requirement id; annotated {not-applicable}: ze applies transport mode only to IPv6 OSPFv3 traffic (IPv4-family IPsec is rejected at config), so transport-mode IPsec on an IPv4 fragment structurally cannot arise, and per-packet fragment handling is kernel-delegated (internal/plugins/ospf/config_ipsec.go:21-22) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4301-4.1-1`](#rfc4301-4.1-1) Host implementations MUST support both transport and tunnel mode (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L177) | unit/verify | unproven | | positive | [`TestIPsecSAIsWildcardWithOSPFSelector`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L149) | unit/verify | unproven | ### [`RFC4301-4.1-2`](#rfc4301-4.1-2) Security gateways MUST support tunnel mode (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L192) | unit/verify | unproven | ### [`RFC4301-4.1-3`](#rfc4301-4.1-3) SAs between a security gateway and any peer MUST use tunnel mode (two narrow exceptions for gateway-as-host) (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L179) | unit/verify | unproven | ### [`RFC4301-4.1-4`](#rfc4301-4.1-4) IKE-created SA pairs MUST use the same mode (both tunnel or both transport) (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L181) | unit/verify | unproven | ### [`RFC4301-4.1-5`](#rfc4301-4.1-5) Implementation MUST permit multiple SAs between same endpoints with same selectors (QoS differentiation) (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4301-4.1-5, so no unit is bound to it. ### [`RFC4301-4.2-1`](#rfc4301-4.2-1) MUST NOT instantiate an ESP SA with both NULL encryption and no integrity algorithm (§4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L102) | unit/verify | unproven | | positive | [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L124) | unit/verify | unproven | ### [`RFC4301-4.4.1-1`](#rfc4301-4.4.1-1) Implementation MUST have at least one SPD (§4.4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L158) | unit/verify | unproven | ### [`RFC4301-4.4.1-2`](#rfc4301-4.4.1-2) SPD MUST be consulted for ALL traffic crossing IPsec boundary, including IKE management traffic (§4.4.1, §5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4301-4.4.1-2, so no unit is bound to it. ### [`RFC4301-4.4.1.1-1`](#rfc4301-4.4.1.1-1) All implementations MUST support the defined selectors: remote/local IP, next-layer protocol, ports, ICMP type/code (§4.4.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4301-4.4.1.1-1, so no unit is bound to it. ### [`RFC4301-4.4.2-1`](#rfc4301-4.4.2-1) Inbound SAD entries MUST be populated with negotiated selector values for packet verification (§4.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAInboundPolicyUsesNegotiatedTS`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L420) | unit/verify | unproven | | positive | [`TestNarrowedSelectorsReachTheInstalledPolicy`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/ts_narrow_test.go#L423) | unit/verify | unproven | ### [`RFC4301-4.5-1`](#rfc4301-4.5-1) Implementations MUST support both manual and automated (IKEv2) key management (§4.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAInstallsInDataplane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L152) | unit/verify | unproven | | positive | [`TestIPsecInstallOnInterfaceUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L62) | unit/verify | unproven | ### [`RFC4301-5.2-1`](#rfc4301-5.2-1) Inbound packets not matching SPD-I MUST be discarded (§5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4301-5.2-1, so no unit is bound to it. ### [`RFC4301-5.2-2`](#rfc4301-5.2-2) After decapsulation, inner packet selectors MUST be verified against SAD traffic selectors (§5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4301-5.2-2, so no unit is bound to it. ### [`RFC4301-4.1-6`](#rfc4301-4.1-6) Multicast-capable implementations MUST support multicast SAD lookup (three-step: SPI+dst+src, SPI+dst, SPI alone) (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4301-4.1-6, so no unit is bound to it. ### [`RFC4301-7-1`](#rfc4301-7-1) AH/ESP MUST NOT be applied in transport mode to IPv4 fragments; use tunnel mode (§7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4301-7-1, so no unit is bound to it. ### [`RFC4301-4.4.3.1-1`](#rfc4301-4.4.3.1-1) Sub-tree matching MUST be supported for distinguished names, domain names and RFC 822 e-mail addresses in PAD entries (§4.4.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPadKeyIDStaysExact`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L111) | unit/verify | unproven | | negative | [`TestPadSubtreeRefusesAPeerOutsideIt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L69) | unit/verify | unproven | | positive | [`TestPadSubtreeAdmitsAPeerBeneathIt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L35) | unit/verify | unproven | | positive | [`TestPadSubtreeStillBindsTheCertificate`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L185) | unit/verify | unproven | | positive | [`TestVidPeerAuthorizationSetsCommitOnlyAsRemoteID`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/validate_identity_test.go#L166) | unit/verify | unproven | ### [`RFC4301-4.4.3.1-2`](#rfc4301-4.4.3.1-2) For IPv4 and IPv6 addresses in PAD entries, the same address range syntax used for SPD entries MUST be supported (§4.4.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPadAddressRangeRefusesWhatIsOutsideIt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L155) | unit/verify | unproven | | positive | [`TestPadAddressRangeAdmitsAnAddressInsideIt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_pad_subtree_test.go#L129) | unit/verify | unproven | | positive | [`TestVidPeerAuthorizationSetsCommitOnlyAsRemoteID`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/validate_identity_test.go#L167) | unit/verify | unproven | ### [`RFC4301-4.4.1-4`](#rfc4301-4.4.1-4) A user or administrator MUST be able to order the SPD entries, and the management interface MUST support (total) ordering of them as seen via that interface (§4.4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSpdOrderCannotCaptureTheIKEControlPlane`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_order_test.go#L107) | unit/verify | unproven | | negative | [`TestSpdUnstatedOrderTakesTheDefaultRank`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_order_test.go#L67) | unit/verify | unproven | | negative | [`TestSPDPolicyOrderInsideBypassBandRefused`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/rfc4301_spd_discard_test.go#L123) | unit/verify | revert, verified | | positive | [`TestSPDPolicyMirrorsTheInboundSelector`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_discard_test.go#L94) | unit/verify | revert, verified | | positive | [`TestSpdOperatorOrdersOverlappingPeers`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_order_test.go#L20) | unit/verify | unproven | ### [`RFC4301-7.4-1`](#rfc4301-7.4-1) All implementations MUST support DISCARDing of fragments using the normal SPD packet classification mechanisms (§7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBypassPolicyIsNotBlocked`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_discard_linux_test.go#L64) | unit/verify | revert, verified | | negative | [`TestBypassPolicyReachesTheBackendAsBypass`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_discard_test.go#L75) | unit/verify | revert, verified | | negative | [`TestSPDPolicyBypassIsNotADiscard`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/rfc4301_spd_discard_test.go#L83) | unit/verify | revert, verified | | positive | [`TestDiscardPolicyReachesTheKernelAsBlock`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_discard_linux_test.go#L41) | unit/verify | revert, verified | | positive | [`TestPolicyReadbackReportsDiscard`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_discard_linux_test.go#L98) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | | positive | [`TestVPPSPDActionCarriesEveryDisposition`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_discard_vpp_test.go#L23) | unit/verify | revert, verified | | positive | [`TestDiscardPolicyReachesTheBackend`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_spd_discard_test.go#L45) | unit/verify | revert, verified | | positive | [`TestSPDPolicyCarriesTheDiscardDisposition`](https://github.com/ze-software/ze/blob/main/internal/component/ike/ipsec/rfc4301_spd_discard_test.go#L52) | unit/verify | revert, verified | ### [`RFC4301-7.4-2`](#rfc4301-7.4-2) All implementations MUST support stateful fragment checking to accommodate BYPASS traffic for which a non-trivial port range is specified (§7.4; Appendix D.4 restates it as "implementations MUST support fragment reassembly for BYPASS/DISCARD traffic when port fields are specified") Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIKEBypassIsPortScopedSoSection74Binds`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_fragment_test.go#L133) | unit/verify | revert, verified | | positive | [`TestXFRMPortScopedBypassIsNeverStoredOnTheForwardPath`](https://github.com/ze-software/ze/blob/main/internal/component/ike/dataplane/rfc4301_fragment_integration_linux_test.go#L69) | unit/verify | unproven | | positive | [`TestPortScopedBypassNeverReachesTheForwardPath`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc4301_fragment_test.go#L74) | unit/verify | revert, verified | ## Extraction sign-off No extraction sign-off exists for RFC 4301, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4301, so its obligations are stated where they were written. --- ### Page: RFC 4302 - IP Authentication Header https://ze-software.net/quality/rfc-compliance/rfc4302/ # RFC 4302 - IP Authentication Header Partial. Every requirement this repository extracted from RFC 4302, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 23.5% | 8 of 34 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 34 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 34 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 100.0% | 16 of 16 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 34 | of 44 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 34 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 34 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 58.8% | 20 of 34 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 2.9% | 1 of 34 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 14.7% | 5 of 34 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 34 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 44 | | Gated MUST-level | 34 | | Not applicable, so out of scope | 0 | | Declared gaps | 2 | | Gated with no test | 3 | | Nightly-only evidence | 0 | | Test tags | 16 | | Tagged units | 16 | | Recorded audit verdicts | 0 | | Discrimination records | 16 | | Summary | `rfc/short/rfc4302.md` | | Requirement shard | `rfc/requirements/rfc4302.md` | | RFC text | `rfc/full/rfc4302.txt` | ## Enrolment Enrolled: IP Authentication Header (RFC 4302): control plane only. Ze speaks AH through RFC 4552 manually-keyed IPsec for OSPFv3: validateIPsecInterface (plugins/ospf/config_ipsec.go) accepts protocol ah with an SPI at or above 256, an HMAC-SHA algorithm and a hex key of that algorithm length, and refuses an encryption key beside AH; buildIPsecSA and buildIPsecPolicies (plugins/ospf/ipsec_install.go) install one transport-mode SA with Proto ProtoAH and a {::/0, ::/0, proto 89} state selector plus out/in/fwd policies scoped to the interface; planStateAlgos (ike/dataplane/dataplane.go) gives an AH state an integrity transform and no encryption transform, and xfrmStateFromParams (ike/dataplane/xfrm_linux.go) writes it. Every per-packet AH obligation -- header construction, sequence numbers, mutable-field zeroing, ICV, replay window, discard on failure -- is performed by Linux XFRM, which the whole-stack conformance ruling of 2026-08-31 counts as an implementation of the obligation rather than an exemption. Ze never negotiates an AH Child SA over IKEv2, so every AH SA it installs is manually keyed and carries no replay window unless the interface's replay-window leaf asks for one, which defaults to 0 as Section 5 asks. 34 gated rows, none tested and none annotated at enrolment; the interop scenario ospf-ipsec-ah-frr already reads the installed kernel state and is the carrier the coverage work will tag. ## What the public ledger says **Status:** Partial **What the ledger says is covered:** AH algorithm planning and RFC 4552 OSPFv3 use. **What the ledger says remains:** Scoped to configured manual IPsec support. No counter reset before sequence-number rollover, and no AH auditing surface. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 8 | one part of the gated population | | Annotated instead of tested | 23 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 3 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **34** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (8):** [`RFC4302-2-1`](#rfc4302-2-1), [`RFC4302-2-2`](#rfc4302-2-2), [`RFC4302-2.4-1`](#rfc4302-2.4-1), [`RFC4302-2.4-5`](#rfc4302-2.4-5), [`RFC4302-2.4-6`](#rfc4302-2.4-6), [`RFC4302-3.4.2-1`](#rfc4302-3.4.2-1), [`RFC4302-3.4.3-1`](#rfc4302-3.4.3-1), [`RFC4302-3.4.3-5`](#rfc4302-3.4.3-5) **Annotated instead of tested (23):** [`RFC4302-2.3-1`](#rfc4302-2.3-1), [`RFC4302-2.4-2`](#rfc4302-2.4-2), [`RFC4302-2.4-3`](#rfc4302-2.4-3), [`RFC4302-2.4-4`](#rfc4302-2.4-4), [`RFC4302-2.5-1`](#rfc4302-2.5-1), [`RFC4302-2.5-2`](#rfc4302-2.5-2), [`RFC4302-2.5-3`](#rfc4302-2.5-3), [`RFC4302-2.5-5`](#rfc4302-2.5-5), [`RFC4302-2.5.1-1`](#rfc4302-2.5.1-1), [`RFC4302-2.6-1`](#rfc4302-2.6-1), [`RFC4302-3.3.2-1`](#rfc4302-3.3.2-1), [`RFC4302-3.3.3.1-1`](#rfc4302-3.3.3.1-1), [`RFC4302-3.3.3.2.2-1`](#rfc4302-3.3.3.2.2-1), [`RFC4302-3.3.3.2.2-2`](#rfc4302-3.3.3.2.2-2), [`RFC4302-3.3.3.2.2-3`](#rfc4302-3.3.3.2.2-3), [`RFC4302-3.3.3.2.2-4`](#rfc4302-3.3.3.2.2-4), [`RFC4302-3.4.1-1`](#rfc4302-3.4.1-1), [`RFC4302-3.4.3-2`](#rfc4302-3.4.3-2), [`RFC4302-3.4.3-3`](#rfc4302-3.4.3-3), [`RFC4302-3.4.3-4`](#rfc4302-3.4.3-4), [`RFC4302-4-1`](#rfc4302-4-1), [`RFC4302-A2-1`](#rfc4302-a2-1), [`RFC4302-A2-2`](#rfc4302-a2-2) **No test and no annotation (3):** [`RFC4302-3.3.4-1`](#rfc4302-3.3.4-1), [`RFC4302-5-1`](#rfc4302-5-1), [`RFC4302-5-2`](#rfc4302-5-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4302-2-1` | The protocol header immediately preceding the AH header SHALL contain the value 51 in its Protocol or Next Header field (§2) | SHALL | 2 | **positive:** `unit/verify` [`TestIPsecSAProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L298). **negative:** `unit/verify` [`TestIPsecSAProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L303) | | `RFC4302-2-2` | AH carries no version number, so backward-compatibility concerns MUST be addressed by a signaling mechanism between the two peers, an SA management protocol or an out-of-band configuration mechanism (§2) | MUST | 2 | **positive:** `unit/verify` [`TestAHParametersComeFromConfiguration`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L167). **negative:** `unit/verify` [`TestAHParametersComeFromConfiguration`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L175) | | `RFC4302-2.3-1` | The RESERVED field MUST be set to zero by the sender (§2.3) | MUST | 2.3 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode AH SA, and the kernel's AH output builds every AH header from it, so no value Ze writes decides this field | | `RFC4302-2.4-1` | The SPI field is mandatory, and the mechanism for mapping inbound traffic to unicast SAs MUST be supported by all AH implementations (§2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestAHSAIdentifiedBySPIAndProtocol`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L37). **negative:** `unit/verify` [`TestAHSAIdentifiedBySPIAndProtocol`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L45) | | `RFC4302-2.4-2` | An implementation that supports multicast MUST support multicast SAs using the SAD search order this section gives (§2.4) | MUST | 2.4 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes each SAD entry with its SPI and its address selector, and Linux XFRM maps an inbound AH datagram to an entry in the longest-match order this section gives; ze performs no SAD search of its own | | `RFC4302-2.4-3` | A multicast-capable implementation MUST correctly de-multiplex inbound traffic even in the context of SPI collisions (§2.4) | MUST | 2.4 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes a state Linux XFRM keys by destination, SPI and protocol, and Linux XFRM de-multiplexes on that key, so a group SA and a unicast SA that share an SPI stay distinct entries | | `RFC4302-2.4-4` | An accelerated SAD search MUST be functionally equivalent, in externally visible behavior, to searching the SAD in the order given (§2.4) | MUST | 2.4 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams installs the SAD entries, and Linux XFRM owns both the search and whatever hashing accelerates it, so the equivalence this sentence demands is the kernel's to keep | | `RFC4302-2.4-5` | Whether source and destination address matching is required to map inbound traffic to an SA MUST be set as a side effect of manual SA configuration or by an SA management protocol (§2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestAHSAAddressMatchIndication`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L65). **negative:** `unit/verify` [`TestAHSAAddressMatchIndication`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L73) | | `RFC4302-2.4-6` | The SPI value zero is reserved for local implementation-specific use and MUST NOT be sent on the wire (§2.4) | MUST NOT | 2.4 | **positive:** `unit/verify` [`TestAHSPIReservedRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L95). **negative:** `unit/verify` [`TestAHSPIReservedRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L90) | | `RFC4302-2.5-1` | For a unicast SA or a single-sender multicast SA the sender MUST increment the Sequence Number for every transmitted packet (§2.5) | MUST | 2.5 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the XFRM state that carries the sequence counter, and the kernel increments it for each packet it sends on the SA; Ze sees no AH packet | | `RFC4302-2.5-2` | The Sequence Number field is mandatory and MUST always be present and transmitted, even when the receiver does not enable anti-replay (§2.5) | MUST | 2.5 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the XFRM state, and the kernel emits the Sequence Number on every AH packet whether or not the state carries a replay window | | `RFC4302-2.5-3` | All AH implementations MUST be capable of performing the sequence number generation and the sequence number verification this document describes (§2.5) | MUST | 2.5 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the state for both directions of the SA, and the kernel generates the sequence numbers it sends and verifies the ones it receives | | `RFC4302-2.5-5` | The sender's and receiver's counters MUST be reset, by establishing a new SA and a new key, before the 2^32nd packet is transmitted on an SA (§2.5) | MUST | 2.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** nothing resets the counters before the 2^32nd packet. The OSPFv3 SA is manually keyed and ze never rekeys it: setConfig installs one SPI and key per interface (internal/plugins/ospf/ipsec_install.go) and no code establishes a replacement SA, so an operator who enables anti-replay with the replay-window leaf gets an SA that stops sending at the rollover rather than one that is re-established. Disclosed in docs/features/rfc-status.md RFC 4302 row | | `RFC4302-2.5.1-1` | Use of an Extended Sequence Number MUST be negotiated by an SA management protocol (§2.5.1) | MUST | 2.5.1 | **positive:** no positive test. **negative:** no negative test. **{feature-declined}:** "a new option for sequence numbers SHOULD be offered, as an extension to the current, 32-bit sequence number field"; ze offers no ESN, so nothing ever uses one. internal/plugins/ospf/ipsec_install.go::buildIPsecSA is the only code that creates an AH SA, and it builds one manually keyed transport-mode state from static interface configuration with no ESN in it; espProposalToWire, internal/component/ike/engine/initiator.go, proposes ESP alone and keys Transform Type 5 to espESNNotExtended, so ze runs no SA management protocol that could negotiate an AH ESN | | `RFC4302-2.6-1` | All implementations MUST support explicit ICV padding and MUST insert only enough padding to satisfy the IPv4 or IPv6 alignment requirement (§2.6) | MUST | 2.6 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams sets the integrity transform and its truncation length, and the kernel's AH output sizes the ICV field and pads it to the alignment the address family needs | | `RFC4302-3.3.2-1` | The sender MUST NOT send a packet on an SA if doing so would cause the Sequence Number to cycle (§3.3.2) | MUST NOT | 3.3.2 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the per-SA replay window the interface replay-window leaf asks for, and Linux XFRM refuses to send a packet on that state once the sequence number would cycle; ze emits no AH packet of its own | | `RFC4302-3.3.3.1-1` | For the ICV computation a field that may be modified in transit is set to zero, a mutable but predictable field is set to the value it will have at the receiver, and the ICV field itself is set to zero (§3.3.3.1) | MUST | 3.3.3.1 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode AH SA, and the kernel's AH code zeroes the mutable fields and the ICV field before it computes the ICV | | `RFC4302-3.3.3.2.2-1` | When the packet length does not match the integrity algorithm's blocksize, implicit padding MUST be appended to the end of the packet before the ICV is computed (§3.3.3.2.2) | MUST | 3.3.3.2.2 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams names the integrity algorithm, and the kernel's crypto layer appends whatever implicit padding that algorithm's blocksize needs | | `RFC4302-3.3.3.2.2-2` | The implicit padding octets MUST have a value of zero (§3.3.3.2.2) | MUST | 3.3.3.2.2 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams names the integrity algorithm, and the kernel's crypto layer chooses the value of the implicit padding octets | | `RFC4302-3.3.3.2.2-3` | The document defining the integrity algorithm MUST be consulted to decide whether implicit padding is required (§3.3.3.2.2) | MUST | 3.3.3.2.2 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams names the integrity algorithm, and the kernel's implementation of that algorithm decides whether implicit padding is required | | `RFC4302-3.3.3.2.2-4` | When that document gives no answer, implicit padding is assumed to be required and its octets MUST have a value of zero (§3.3.3.2.2) | MUST | 3.3.3.2.2 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams names the integrity algorithm, and the kernel's implementation of that algorithm supplies the default when the algorithm gives no answer | | `RFC4302-3.3.4-1` | An AH implementation MUST support generation of ICMP PMTU messages, or the equivalent internal signaling for a native host implementation (§3.3.4) | MUST | 3.3.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-3.4.1-1` | A packet offered to AH that appears to be an IP fragment MUST be discarded, and that is an auditable event (§3.4.1) | MUST | 3.4.1 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecPolicies installs the inbound require-policy that puts OSPF traffic through the kernel's AH input, which is where a packet offered to AH that is an IP fragment is dropped | | `RFC4302-3.4.2-1` | When no valid Security Association exists for a packet the receiver MUST discard it, and that is an auditable event (§3.4.2) | MUST | 3.4.2 | **positive:** `unit/verify` [`TestAHInboundRequirePolicyInstalled`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L118). **negative:** `unit/verify` [`TestAHInboundRequirePolicyInstalled`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L126) | | `RFC4302-3.4.3-1` | All AH implementations MUST support the anti-replay service, whose use the receiver enables or disables per SA (§3.4.3) | MUST | 3.4.3 | **positive:** `unit/verify` [`TestIPsecSAReplayWindow`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L362). **negative:** `unit/verify` [`TestIPsecSAReplayWindow`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L367) | | `RFC4302-3.4.3-2` | When anti-replay is enabled for an SA the receive packet counter MUST be initialized to zero as the SA is established (§3.4.3) | MUST | 3.4.3 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the state and presets no replay counter, and Linux XFRM initializes the receive packet counter to zero as it creates the state | | `RFC4302-3.4.3-3` | For each received packet the receiver MUST verify that the Sequence Number does not duplicate one already received on this SA (§3.4.3) | MUST | 3.4.3 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the replay window the receiver asked for, and Linux XFRM checks every received sequence number against that window and rejects a duplicate | | `RFC4302-3.4.3-4` | When ICV validation fails the receiver MUST discard the datagram as invalid (§3.4.3) | MUST | 3.4.3 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams installs the integrity transform the kernel validates the ICV with, and the kernel discards the datagram when that validation fails; Ze holds no rejecting branch of its own | | `RFC4302-3.4.3-5` | A minimum replay window size of 32 packets MUST be supported (§3.4.3) | MUST | 3.4.3 | **positive:** `unit/verify` [`TestIPsecReplayWindowRange`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L256). **negative:** `unit/verify` [`TestIPsecReplayWindowRange`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L260) | | `RFC4302-4-1` | When AH is incorporated into a system that supports auditing, the AH implementation MUST also support auditing and MUST allow an administrator to enable or disable auditing for AH (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze supports auditing for AH nowhere. readXfrmDropsPlatform (internal/plugins/ospf/ipsec_drops_linux.go) polls the node-global /proc/net/xfrm_stat counters into one OSPF metric, which carries no SPI, no per-SA identity, no AH-versus-ESP split and no administrator switch to enable or disable AH auditing. Disclosed in docs/features/rfc-status.md RFC 4302 row | | `RFC4302-5-1` | An implementation claiming conformance MUST fully implement the AH syntax and processing for unicast traffic, and MUST comply with all requirements of the Security Architecture document (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-5-2` | An implementation claiming to support multicast traffic MUST comply with the additional requirements specified for such traffic (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-A2-1` | When the IP implementation leaves a Fragmentation Extension Header in place after reassembly, AH MUST remove or skip it and repair the preceding Next Header before ICV processing (§A2) | MUST | A2 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode AH SA, and the kernel's IPv6 AH input skips a Fragmentation Extension Header the IP layer left in place and repairs the preceding Next Header | | `RFC4302-A2-2` | When the IP implementation hands AH a send-side packet carrying a Fragmentation Extension Header with Offset zero and More Fragments zero, AH MUST remove or skip it and repair the preceding Next Header before ICV processing (§A2) | MUST | A2 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode AH SA, and the kernel's IPv6 AH output skips a Fragmentation Extension Header the IP layer handed it and repairs the preceding Next Header | | `RFC4302-2.3-2` | The RESERVED field SHOULD be ignored by the recipient (§2.3) | SHOULD | 2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-2.5.1-2` | A 64-bit sequence number option SHOULD be offered as an extension to the 32-bit Sequence Number field (§2.5.1) | SHOULD | 2.5.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-3.4.3-6` | A replay window size of 64 is preferred and SHOULD be employed as the default (§3.4.3) | SHOULD | 3.4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-3.4.3-7` | The replay check SHOULD be the first AH check applied to a packet once it is matched to an SA (§3.4.3) | SHOULD | 3.4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-3.4.3-8` | When an SA establishment protocol is in use, a receiver that will not provide anti-replay protection SHOULD say so during SA establishment (§3.4.3) | SHOULD | 3.4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-5-3` | A compliant implementation SHOULD NOT provide the anti-replay service with an SA that is manually keyed (§5) | SHOULD NOT | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-2.4-7` | An implementation MAY choose any method to accelerate the SAD search (§2.4) | MAY | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-3.3.4-2` | An AH implementation MAY choose not to support fragmentation and MAY mark transmitted packets with the DF bit (§3.3.4) | MAY | 3.3.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-3.4.3-9` | A replay window larger than the minimum MAY be chosen by the receiver (§3.4.3) | MAY | 3.4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4302-5-4` | Algorithms beyond those mandated for AH MAY be supported (§5) | MAY | 5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4302-2.3-1`](#rfc4302-2.3-1) The RESERVED field MUST be set to zero by the sender (§2.3) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode AH SA, and the kernel's AH output builds every AH header from it, so no value Ze writes decides this field | | [`RFC4302-2.4-2`](#rfc4302-2.4-2) An implementation that supports multicast MUST support multicast SAs using the SAD search order this section gives (§2.4) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes each SAD entry with its SPI and its address selector, and Linux XFRM maps an inbound AH datagram to an entry in the longest-match order this section gives; ze performs no SAD search of its own | | [`RFC4302-2.4-3`](#rfc4302-2.4-3) A multicast-capable implementation MUST correctly de-multiplex inbound traffic even in the context of SPI collisions (§2.4) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes a state Linux XFRM keys by destination, SPI and protocol, and Linux XFRM de-multiplexes on that key, so a group SA and a unicast SA that share an SPI stay distinct entries | | [`RFC4302-2.4-4`](#rfc4302-2.4-4) An accelerated SAD search MUST be functionally equivalent, in externally visible behavior, to searching the SAD in the order given (§2.4) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams installs the SAD entries, and Linux XFRM owns both the search and whatever hashing accelerates it, so the equivalence this sentence demands is the kernel's to keep | | [`RFC4302-2.5-1`](#rfc4302-2.5-1) For a unicast SA or a single-sender multicast SA the sender MUST increment the Sequence Number for every transmitted packet (§2.5) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the XFRM state that carries the sequence counter, and the kernel increments it for each packet it sends on the SA; Ze sees no AH packet | | [`RFC4302-2.5-2`](#rfc4302-2.5-2) The Sequence Number field is mandatory and MUST always be present and transmitted, even when the receiver does not enable anti-replay (§2.5) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the XFRM state, and the kernel emits the Sequence Number on every AH packet whether or not the state carries a replay window | | [`RFC4302-2.5-3`](#rfc4302-2.5-3) All AH implementations MUST be capable of performing the sequence number generation and the sequence number verification this document describes (§2.5) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the state for both directions of the SA, and the kernel generates the sequence numbers it sends and verifies the ones it receives | | [`RFC4302-2.5-5`](#rfc4302-2.5-5) The sender's and receiver's counters MUST be reset, by establishing a new SA and a new key, before the 2^32nd packet is transmitted on an SA (§2.5) | {gap}, no test | nothing resets the counters before the 2^32nd packet. The OSPFv3 SA is manually keyed and ze never rekeys it: setConfig installs one SPI and key per interface (internal/plugins/ospf/ipsec_install.go) and no code establishes a replacement SA, so an operator who enables anti-replay with the replay-window leaf gets an SA that stops sending at the rollover rather than one that is re-established. Disclosed in docs/features/rfc-status.md RFC 4302 row | | [`RFC4302-2.5.1-1`](#rfc4302-2.5.1-1) Use of an Extended Sequence Number MUST be negotiated by an SA management protocol (§2.5.1) | no test | no test carries this requirement id; annotated {feature-declined}: "a new option for sequence numbers SHOULD be offered, as an extension to the current, 32-bit sequence number field"; ze offers no ESN, so nothing ever uses one. internal/plugins/ospf/ipsec_install.go::buildIPsecSA is the only code that creates an AH SA, and it builds one manually keyed transport-mode state from static interface configuration with no ESN in it; espProposalToWire, internal/component/ike/engine/initiator.go, proposes ESP alone and keys Transform Type 5 to espESNNotExtended, so ze runs no SA management protocol that could negotiate an AH ESN | | [`RFC4302-2.6-1`](#rfc4302-2.6-1) All implementations MUST support explicit ICV padding and MUST insert only enough padding to satisfy the IPv4 or IPv6 alignment requirement (§2.6) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams sets the integrity transform and its truncation length, and the kernel's AH output sizes the ICV field and pads it to the alignment the address family needs | | [`RFC4302-3.3.2-1`](#rfc4302-3.3.2-1) The sender MUST NOT send a packet on an SA if doing so would cause the Sequence Number to cycle (§3.3.2) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the per-SA replay window the interface replay-window leaf asks for, and Linux XFRM refuses to send a packet on that state once the sequence number would cycle; ze emits no AH packet of its own | | [`RFC4302-3.3.3.1-1`](#rfc4302-3.3.3.1-1) For the ICV computation a field that may be modified in transit is set to zero, a mutable but predictable field is set to the value it will have at the receiver, and the ICV field itself is set to zero (§3.3.3.1) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode AH SA, and the kernel's AH code zeroes the mutable fields and the ICV field before it computes the ICV | | [`RFC4302-3.3.3.2.2-1`](#rfc4302-3.3.3.2.2-1) When the packet length does not match the integrity algorithm's blocksize, implicit padding MUST be appended to the end of the packet before the ICV is computed (§3.3.3.2.2) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams names the integrity algorithm, and the kernel's crypto layer appends whatever implicit padding that algorithm's blocksize needs | | [`RFC4302-3.3.3.2.2-2`](#rfc4302-3.3.3.2.2-2) The implicit padding octets MUST have a value of zero (§3.3.3.2.2) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams names the integrity algorithm, and the kernel's crypto layer chooses the value of the implicit padding octets | | [`RFC4302-3.3.3.2.2-3`](#rfc4302-3.3.3.2.2-3) The document defining the integrity algorithm MUST be consulted to decide whether implicit padding is required (§3.3.3.2.2) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams names the integrity algorithm, and the kernel's implementation of that algorithm decides whether implicit padding is required | | [`RFC4302-3.3.3.2.2-4`](#rfc4302-3.3.3.2.2-4) When that document gives no answer, implicit padding is assumed to be required and its octets MUST have a value of zero (§3.3.3.2.2) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams names the integrity algorithm, and the kernel's implementation of that algorithm supplies the default when the algorithm gives no answer | | [`RFC4302-3.3.4-1`](#rfc4302-3.3.4-1) An AH implementation MUST support generation of ICMP PMTU messages, or the equivalent internal signaling for a native host implementation (§3.3.4) | no test | no test carries this requirement id | | [`RFC4302-3.4.1-1`](#rfc4302-3.4.1-1) A packet offered to AH that appears to be an IP fragment MUST be discarded, and that is an auditable event (§3.4.1) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecPolicies installs the inbound require-policy that puts OSPF traffic through the kernel's AH input, which is where a packet offered to AH that is an IP fragment is dropped | | [`RFC4302-3.4.3-2`](#rfc4302-3.4.3-2) When anti-replay is enabled for an SA the receive packet counter MUST be initialized to zero as the SA is established (§3.4.3) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the state and presets no replay counter, and Linux XFRM initializes the receive packet counter to zero as it creates the state | | [`RFC4302-3.4.3-3`](#rfc4302-3.4.3-3) For each received packet the receiver MUST verify that the Sequence Number does not duplicate one already received on this SA (§3.4.3) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes the replay window the receiver asked for, and Linux XFRM checks every received sequence number against that window and rejects a duplicate | | [`RFC4302-3.4.3-4`](#rfc4302-3.4.3-4) When ICV validation fails the receiver MUST discard the datagram as invalid (§3.4.3) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams installs the integrity transform the kernel validates the ICV with, and the kernel discards the datagram when that validation fails; Ze holds no rejecting branch of its own | | [`RFC4302-4-1`](#rfc4302-4-1) When AH is incorporated into a system that supports auditing, the AH implementation MUST also support auditing and MUST allow an administrator to enable or disable auditing for AH (§4) | {gap}, no test | ze supports auditing for AH nowhere. readXfrmDropsPlatform (internal/plugins/ospf/ipsec_drops_linux.go) polls the node-global /proc/net/xfrm_stat counters into one OSPF metric, which carries no SPI, no per-SA identity, no AH-versus-ESP split and no administrator switch to enable or disable AH auditing. Disclosed in docs/features/rfc-status.md RFC 4302 row | | [`RFC4302-5-1`](#rfc4302-5-1) An implementation claiming conformance MUST fully implement the AH syntax and processing for unicast traffic, and MUST comply with all requirements of the Security Architecture document (§5) | no test | no test carries this requirement id | | [`RFC4302-5-2`](#rfc4302-5-2) An implementation claiming to support multicast traffic MUST comply with the additional requirements specified for such traffic (§5) | no test | no test carries this requirement id | | [`RFC4302-A2-1`](#rfc4302-a2-1) When the IP implementation leaves a Fragmentation Extension Header in place after reassembly, AH MUST remove or skip it and repair the preceding Next Header before ICV processing (§A2) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode AH SA, and the kernel's IPv6 AH input skips a Fragmentation Extension Header the IP layer left in place and repairs the preceding Next Header | | [`RFC4302-A2-2`](#rfc4302-a2-2) When the IP implementation hands AH a send-side packet carrying a Fragmentation Extension Header with Offset zero and More Fragments zero, AH MUST remove or skip it and repair the preceding Next Header before ICV processing (§A2) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode AH SA, and the kernel's IPv6 AH output skips a Fragmentation Extension Header the IP layer handed it and repairs the preceding Next Header | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4302-2-1`](#rfc4302-2-1) The protocol header immediately preceding the AH header SHALL contain the value 51 in its Protocol or Next Header field (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecSAProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L303) | unit/verify | mutant, verified | | positive | [`TestIPsecSAProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L298) | unit/verify | mutant, verified | ### [`RFC4302-2-2`](#rfc4302-2-2) AH carries no version number, so backward-compatibility concerns MUST be addressed by a signaling mechanism between the two peers, an SA management protocol or an out-of-band configuration mechanism (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAHParametersComeFromConfiguration`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L175) | unit/verify | mutant, verified | | positive | [`TestAHParametersComeFromConfiguration`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L167) | unit/verify | mutant, verified | ### [`RFC4302-2.3-1`](#rfc4302-2.3-1) The RESERVED field MUST be set to zero by the sender (§2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.3-1, so no unit is bound to it. ### [`RFC4302-2.4-1`](#rfc4302-2.4-1) The SPI field is mandatory, and the mechanism for mapping inbound traffic to unicast SAs MUST be supported by all AH implementations (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAHSAIdentifiedBySPIAndProtocol`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L45) | unit/verify | revert, verified | | positive | [`TestAHSAIdentifiedBySPIAndProtocol`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L37) | unit/verify | mutant, verified | ### [`RFC4302-2.4-2`](#rfc4302-2.4-2) An implementation that supports multicast MUST support multicast SAs using the SAD search order this section gives (§2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.4-2, so no unit is bound to it. ### [`RFC4302-2.4-3`](#rfc4302-2.4-3) A multicast-capable implementation MUST correctly de-multiplex inbound traffic even in the context of SPI collisions (§2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.4-3, so no unit is bound to it. ### [`RFC4302-2.4-4`](#rfc4302-2.4-4) An accelerated SAD search MUST be functionally equivalent, in externally visible behavior, to searching the SAD in the order given (§2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.4-4, so no unit is bound to it. ### [`RFC4302-2.4-5`](#rfc4302-2.4-5) Whether source and destination address matching is required to map inbound traffic to an SA MUST be set as a side effect of manual SA configuration or by an SA management protocol (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAHSAAddressMatchIndication`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L73) | unit/verify | revert, verified | | positive | [`TestAHSAAddressMatchIndication`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L65) | unit/verify | revert, verified | ### [`RFC4302-2.4-6`](#rfc4302-2.4-6) The SPI value zero is reserved for local implementation-specific use and MUST NOT be sent on the wire (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAHSPIReservedRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L90) | unit/verify | mutant, verified | | positive | [`TestAHSPIReservedRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L95) | unit/verify | mutant, verified | ### [`RFC4302-2.5-1`](#rfc4302-2.5-1) For a unicast SA or a single-sender multicast SA the sender MUST increment the Sequence Number for every transmitted packet (§2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.5-1, so no unit is bound to it. ### [`RFC4302-2.5-2`](#rfc4302-2.5-2) The Sequence Number field is mandatory and MUST always be present and transmitted, even when the receiver does not enable anti-replay (§2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.5-2, so no unit is bound to it. ### [`RFC4302-2.5-3`](#rfc4302-2.5-3) All AH implementations MUST be capable of performing the sequence number generation and the sequence number verification this document describes (§2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.5-3, so no unit is bound to it. ### [`RFC4302-2.5-5`](#rfc4302-2.5-5) The sender's and receiver's counters MUST be reset, by establishing a new SA and a new key, before the 2^32nd packet is transmitted on an SA (§2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.5-5, so no unit is bound to it. ### [`RFC4302-2.5.1-1`](#rfc4302-2.5.1-1) Use of an Extended Sequence Number MUST be negotiated by an SA management protocol (§2.5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.5.1-1, so no unit is bound to it. ### [`RFC4302-2.6-1`](#rfc4302-2.6-1) All implementations MUST support explicit ICV padding and MUST insert only enough padding to satisfy the IPv4 or IPv6 alignment requirement (§2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-2.6-1, so no unit is bound to it. ### [`RFC4302-3.3.2-1`](#rfc4302-3.3.2-1) The sender MUST NOT send a packet on an SA if doing so would cause the Sequence Number to cycle (§3.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.3.2-1, so no unit is bound to it. ### [`RFC4302-3.3.3.1-1`](#rfc4302-3.3.3.1-1) For the ICV computation a field that may be modified in transit is set to zero, a mutable but predictable field is set to the value it will have at the receiver, and the ICV field itself is set to zero (§3.3.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.3.3.1-1, so no unit is bound to it. ### [`RFC4302-3.3.3.2.2-1`](#rfc4302-3.3.3.2.2-1) When the packet length does not match the integrity algorithm's blocksize, implicit padding MUST be appended to the end of the packet before the ICV is computed (§3.3.3.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.3.3.2.2-1, so no unit is bound to it. ### [`RFC4302-3.3.3.2.2-2`](#rfc4302-3.3.3.2.2-2) The implicit padding octets MUST have a value of zero (§3.3.3.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.3.3.2.2-2, so no unit is bound to it. ### [`RFC4302-3.3.3.2.2-3`](#rfc4302-3.3.3.2.2-3) The document defining the integrity algorithm MUST be consulted to decide whether implicit padding is required (§3.3.3.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.3.3.2.2-3, so no unit is bound to it. ### [`RFC4302-3.3.3.2.2-4`](#rfc4302-3.3.3.2.2-4) When that document gives no answer, implicit padding is assumed to be required and its octets MUST have a value of zero (§3.3.3.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.3.3.2.2-4, so no unit is bound to it. ### [`RFC4302-3.3.4-1`](#rfc4302-3.3.4-1) An AH implementation MUST support generation of ICMP PMTU messages, or the equivalent internal signaling for a native host implementation (§3.3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.3.4-1, so no unit is bound to it. ### [`RFC4302-3.4.1-1`](#rfc4302-3.4.1-1) A packet offered to AH that appears to be an IP fragment MUST be discarded, and that is an auditable event (§3.4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.4.1-1, so no unit is bound to it. ### [`RFC4302-3.4.2-1`](#rfc4302-3.4.2-1) When no valid Security Association exists for a packet the receiver MUST discard it, and that is an auditable event (§3.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAHInboundRequirePolicyInstalled`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L126) | unit/verify | mutant, verified | | positive | [`TestAHInboundRequirePolicyInstalled`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_rfc4302_test.go#L118) | unit/verify | revert, verified | ### [`RFC4302-3.4.3-1`](#rfc4302-3.4.3-1) All AH implementations MUST support the anti-replay service, whose use the receiver enables or disables per SA (§3.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecSAReplayWindow`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L367) | unit/verify | revert, verified | | positive | [`TestIPsecSAReplayWindow`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L362) | unit/verify | revert, verified | ### [`RFC4302-3.4.3-2`](#rfc4302-3.4.3-2) When anti-replay is enabled for an SA the receive packet counter MUST be initialized to zero as the SA is established (§3.4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.4.3-2, so no unit is bound to it. ### [`RFC4302-3.4.3-3`](#rfc4302-3.4.3-3) For each received packet the receiver MUST verify that the Sequence Number does not duplicate one already received on this SA (§3.4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.4.3-3, so no unit is bound to it. ### [`RFC4302-3.4.3-4`](#rfc4302-3.4.3-4) When ICV validation fails the receiver MUST discard the datagram as invalid (§3.4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-3.4.3-4, so no unit is bound to it. ### [`RFC4302-3.4.3-5`](#rfc4302-3.4.3-5) A minimum replay window size of 32 packets MUST be supported (§3.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecReplayWindowRange`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L260) | unit/verify | mutant, verified | | positive | [`TestIPsecReplayWindowRange`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L256) | unit/verify | mutant, verified | ### [`RFC4302-4-1`](#rfc4302-4-1) When AH is incorporated into a system that supports auditing, the AH implementation MUST also support auditing and MUST allow an administrator to enable or disable auditing for AH (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-4-1, so no unit is bound to it. ### [`RFC4302-5-1`](#rfc4302-5-1) An implementation claiming conformance MUST fully implement the AH syntax and processing for unicast traffic, and MUST comply with all requirements of the Security Architecture document (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-5-1, so no unit is bound to it. ### [`RFC4302-5-2`](#rfc4302-5-2) An implementation claiming to support multicast traffic MUST comply with the additional requirements specified for such traffic (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-5-2, so no unit is bound to it. ### [`RFC4302-A2-1`](#rfc4302-a2-1) When the IP implementation leaves a Fragmentation Extension Header in place after reassembly, AH MUST remove or skip it and repair the preceding Next Header before ICV processing (§A2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-A2-1, so no unit is bound to it. ### [`RFC4302-A2-2`](#rfc4302-a2-2) When the IP implementation hands AH a send-side packet carrying a Fragmentation Extension Header with Offset zero and More Fragments zero, AH MUST remove or skip it and repair the preceding Next Header before ICV processing (§A2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4302-A2-2, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | claude-opus-5 (ipsecwalk), spec-rfcgate-6-supported-extraction-signoff | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc4302.txt | | Source fingerprint | 9966a2ddb94e62b7 | | Record | rfc/extraction/rfc4302.json | | Mapped sentences | 33 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Status of This Memo, Copyright Notice, Abstract and Table of Contents. | | `1` | not stated | 0 | walked | not stated | | `2` | not stated | 2 | walked | not stated | | `2.1` | not stated | 0 | walked | not stated | | `2.2` | not stated | 0 | walked | not stated | | `2.3` | not stated | 1 | walked | not stated | | `2.4` | not stated | 6 | walked | not stated | | `2.5` | not stated | 5 | walked | not stated | | `2.5.1` | not stated | 1 | walked | not stated | | `2.6` | not stated | 2 | walked | not stated | | `3` | not stated | 0 | walked | not stated | | `3.1` | not stated | 0 | walked | not stated | | `3.1.1` | not stated | 0 | walked | not stated | | `3.1.2` | not stated | 0 | walked | not stated | | `3.2` | not stated | 0 | walked | not stated | | `3.3` | not stated | 0 | walked | not stated | | `3.3.1` | not stated | 0 | walked | not stated | | `3.3.2` | not stated | 1 | walked | not stated | | `3.3.3` | not stated | 0 | walked | not stated | | `3.3.3.1` | not stated | 0 | walked | not stated | | `3.3.3.1.1` | not stated | 0 | walked | not stated | | `3.3.3.1.1.1` | not stated | 0 | walked | not stated | | `3.3.3.1.1.2` | not stated | 0 | walked | not stated | | `3.3.3.1.2` | not stated | 0 | walked | not stated | | `3.3.3.1.2.1` | not stated | 0 | walked | not stated | | `3.3.3.1.2.2` | not stated | 0 | walked | not stated | | `3.3.3.1.2.3` | not stated | 0 | walked | not stated | | `3.3.3.2` | not stated | 0 | walked | not stated | | `3.3.3.2.1` | not stated | 0 | walked | not stated | | `3.3.3.2.2` | not stated | 4 | walked | not stated | | `3.3.4` | not stated | 1 | walked | not stated | | `3.4` | not stated | 0 | walked | not stated | | `3.4.1` | not stated | 1 | walked | not stated | | `3.4.2` | not stated | 1 | walked | not stated | | `3.4.3` | not stated | 5 | walked | not stated | | `3.4.4` | not stated | 1 | walked | not stated | | `4` | not stated | 1 | walked | not stated | | `5` | not stated | 2 | walked | not stated | | `6` | not stated | 0 | skipped (appendix-non-normative) | Security Considerations: it discusses the assurance AH provides and states no obligation the extractor or this reviewer could find. | | `7` | not stated | 0 | skipped (appendix-non-normative) | Differences from RFC 2402: a change log against the document this one obsoletes. | | `8` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `9` | References | 0 | skipped (references) | References. | | `9.1` | Normative References | 0 | skipped (references) | Normative References. | | `9.2` | not stated | 2 | walked | not stated | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `2.5:4` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates the obligation site 2.5:2 already carries: the Sequence Number field is mandatory and the sender always transmits it. The clause after the comma, that the receiver need not act upon it, is a permission rather than an obligation and site 3.4.3:1 carries the receiver's own rule. | Thus, the sender MUST always transmit this field, but the receiver need not act upon it. | | `2.6:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The role is the AUTHOR of an integrity-algorithm specification, and the sentence obliges that document to state the ICV length and the validation rules. Ze publishes no integrity-algorithm specification and no producer in this repository could: it CONSUMES them, and ipsecAuthKeyLen (internal/plugins/ospf/config.go) plus xfrmAuthTruncLen (internal/component/ike/dataplane/xfrm_linux.go) hold the key length and the ICV truncation RFC 4868 already specified for HMAC-SHA-256-128, SHA-384-192 and SHA-512-256. The obligation the sentence places on ze as a reader of such a document is site 3.3.3.2.2:3, which is mapped. | The integrity algorithm specification MUST specify the length of the ICV and the comparison rules and processing steps for validation. | | `3.4.4:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates the discard obligation site 3.4.3:4 carries. Both sentences say that a failed ICV comparison discards the datagram as invalid; this one is the Integrity Check Value Verification section repeating the Sequence Number Verification section. | If the test fails, then the receiver MUST discard the received IP datagram as invalid. | ## Superseded No document obsoletes RFC 4302, so its obligations are stated where they were written. --- ### Page: RFC 4303 - IP Encapsulating Security Payload (ESP) https://ze-software.net/quality/rfc-compliance/rfc4303/ # RFC 4303 - IP Encapsulating Security Payload (ESP) Supported. Every requirement this repository extracted from RFC 4303, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 27.3% | 6 of 22 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 9.1% | 2 of 22 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 22 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 22 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 17 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 22 | of 25 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 14 | of 22 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 63.6% | 14 of 22 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 22 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 22 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 22 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 25 | | Gated MUST-level | 22 | | Not applicable, so out of scope | 14 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 17 | | Tagged units | 17 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4303.md` | | Requirement shard | `rfc/requirements/rfc4303.md` | | RFC text | `rfc/full/rfc4303.txt` | ## Enrolment Enrolled: IP Encapsulating Security Payload (RFC 4303): 1 MET (SPI 0/reserved never on the wire) + 2 single-polarity positive (32-packet anti-replay window projected, anti-replay never without integrity) + 14 not-applicable (per-packet ESP datapath -- sequence, padding, TFC, SAD lookup, ICV, anti-replay check -- kernel XFRM) ## What the public ledger says **Status:** Supported **What the ledger says is covered:** ESP SA parameter model, protocol 50, tunnel and transport modes, XFRM installation, OSPFv3 manual ESP. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 6 | one part of the gated population | | Annotated instead of tested | 16 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **22** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (6):** [`RFC4303-1-1`](#rfc4303-1-1), [`RFC4303-2-1`](#rfc4303-2-1), [`RFC4303-2.1-1`](#rfc4303-2.1-1), [`RFC4303-2.1-2`](#rfc4303-2.1-2), [`RFC4303-3.2-1`](#rfc4303-3.2-1), [`RFC4303-2.2.1-2`](#rfc4303-2.2.1-2) **Annotated instead of tested (16):** [`RFC4303-2.2-1`](#rfc4303-2.2-1), [`RFC4303-2.2-2`](#rfc4303-2.2-2), [`RFC4303-2.2-3`](#rfc4303-2.2-3), [`RFC4303-2.4-1`](#rfc4303-2.4-1), [`RFC4303-2.6-1`](#rfc4303-2.6-1), [`RFC4303-2.6-2`](#rfc4303-2.6-2), [`RFC4303-3.3.3-1`](#rfc4303-3.3.3-1), [`RFC4303-3.3.4-1`](#rfc4303-3.3.4-1), [`RFC4303-3.3.4-2`](#rfc4303-3.3.4-2), [`RFC4303-3.4.3-1`](#rfc4303-3.4.3-1), [`RFC4303-3.4.3-2`](#rfc4303-3.4.3-2), [`RFC4303-3.4.3-3`](#rfc4303-3.4.3-3), [`RFC4303-3.4.2-1`](#rfc4303-3.4.2-1), [`RFC4303-3.4.2-2`](#rfc4303-3.4.2-2), [`RFC4303-3.4.4.1-1`](#rfc4303-3.4.4.1-1), [`RFC4303-3.4.4.2-1`](#rfc4303-3.4.4.2-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4303-1-1` | Integrity-only ESP MUST be offered as a service selection option and MUST be configurable via management interfaces (§1) | MUST | 1 - Introduction | **positive:** `unit/verify` [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L127). **negative:** `unit/verify` [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L112) | | `RFC4303-2-1` | The outer protocol header that precedes the ESP header SHALL carry the value 50 in its Protocol or Next Header field (§2) | SHALL | 2 - Packet format | **positive:** `unit/verify` [`TestIPsecSAProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L290). **negative:** `unit/verify` [`TestIPsecSAProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L295) | | `RFC4303-2.1-1` | SPI value 0 MUST NOT appear on the wire (reserved for local use) (§2.1) | MUST NOT | 2.1 - Security Parameters Index | **positive:** `unit/verify` [`TestGenerateESPSPI`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L258). **positive:** `unit/verify` [`TestIPsecSPIBoundary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L192). **negative:** `unit/verify` [`TestGenerateESPSPI`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L272). **negative:** `unit/verify` [`TestIPsecSPIBoundary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L186) | | `RFC4303-2.1-2` | Whether source and destination address matching is required to map inbound traffic to an SA MUST be set by manual SA configuration or by SA management protocol negotiation (§2.1) | MUST | 2.1 - Security Parameters Index | **positive:** `unit/verify` [`TestIPsecSAAddressMatchIndication`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L320). **negative:** `unit/verify` [`TestIPsecSAAddressMatchIndication`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L326) | | `RFC4303-2.2-1` | Sequence Number MUST be incremented for every transmitted packet (§2.2) | MUST | 2.2 - Sequence Number | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the per-SA sequence counter is kernel XFRM/ESP per-packet state; ze installs the SA but never touches sequence numbers (internal/component/ike/dataplane/dataplane.go:80-108 has no sequence field) | | `RFC4303-2.2-2` | Sender MUST NOT send a packet that would cause the sequence counter to cycle (overflow) (§2.2) | MUST NOT | 2.2 - Sequence Number | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** 32-bit counter overflow protection is enforced by the kernel XFRM datapath (it expires the state rather than wrapping); ze's control plane holds no per-packet counter | | `RFC4303-2.2-3` | Counter and receiver window MUST be reset before the 2^32nd packet on a non-ESN SA (§2.2) | MUST | 2.2 - Sequence Number | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the sequence counter and receive window are kernel per-packet SA state; the kernel enforces the 2^32 boundary and a fresh counter/window arises from installing a new SA | | `RFC4303-2.4-1` | Padding MUST bring plaintext to the block size of the cipher and ensure 4-byte alignment of the resulting ciphertext (§2.4) | MUST | 2.4 - Padding for encryption | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ESP padding is applied by the kernel encryption datapath during packet construction; ze projects only the algorithm choice and never builds ESP payloads | | `RFC4303-2.6-1` | Transmitter MUST be capable of generating dummy packets (Next Header = 59) for traffic-flow confidentiality (§2.6) | MUST | 2.6 - Next Header | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** dummy/TFC packet generation is an ESP transmit-datapath function; ze emits no ESP packets, so it plays no role in producing Next-Header-59 dummies | | `RFC4303-2.6-2` | Receiver MUST silently discard dummy packets (Next Header = 59) (§2.6) | MUST | 2.6 - Next Header | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** inbound Next-Header-59 dummy discard occurs in the kernel ESP receive path after decryption; ze processes no inbound ESP payloads | | `RFC4303-3.2-1` | The encryption and integrity algorithms MUST NOT both be NULL; at least one ESP service is always selected (§3.2) | MUST NOT | 3.2 - Algorithms | **positive:** `unit/verify` [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L131). **negative:** `unit/verify` [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L108) | | `RFC4303-3.3.3-1` | Sender initializes sequence counter to 0 at SA establishment; first transmitted packet carries Sequence Number 1 (§3.3.3) | MUST | 3.3.3 - Sequence Number generation | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the kernel initializes the sequence counter when the SA state is added; ze installs a fresh SA with no seq field and never sets or reads the counter (internal/component/ike/dataplane/xfrm_linux.go:21-86) | | `RFC4303-3.3.4-1` | Transport-mode ESP is applied only to whole IP datagrams, never to fragments (§3.3.4) | MUST | 3.3.4 - Fragmentation | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ordering ESP relative to fragmentation is an outbound kernel datapath decision; ze selects transport vs tunnel mode as an SA parameter but does not apply ESP to packets (internal/plugins/ospf/ipsec_install.go:413) | | `RFC4303-3.3.4-2` | Implementation MUST support Path MTU Discovery (§3.3.4) | MUST | 3.3.4 - Fragmentation | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Path MTU Discovery is provided by the kernel networking/XFRM stack; ze's control plane installs SAs and does not run the datapath MTU machinery | | `RFC4303-3.4.3-1` | Anti-replay sliding window: minimum window size of 32 packets MUST be supported (§3.4.3) | MUST | 3.4.3 - Sequence Number verification, the anti-replay section | **positive:** `unit/verify` [`TestChildSAReplayWindowDefault`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L323). **negative:** no negative test. **{single-polarity}:** ze projects a 64-packet anti-replay window on IKE child SAs (ReplayWin=64) to the kernel state, which exceeds the minimum at the SA-parameter level, while the sliding-window check itself runs in the kernel (internal/component/ike/engine/child.go:62, :377, :444, internal/component/ike/dataplane/xfrm_linux.go:132-133) | | `RFC4303-3.4.3-2` | Anti-replay MUST NOT be enabled unless the ESP integrity service is also enabled (§3.4.3) | MUST NOT | 3.4.3 - Sequence Number verification, the anti-replay section | **positive:** `unit/verify` [`TestChildSAReplayRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L349). **negative:** no negative test. **{single-polarity}:** ze's ESP SA model always projects integrity (planStateAlgos returns Crypt+Auth or AEAD for ESP) and the replay window is set only on those integrity-bearing child SAs, so anti-replay is structurally never enabled without integrity (internal/component/ike/dataplane/dataplane.go:53-62, internal/component/ike/engine/child.go:218-261) | | `RFC4303-3.4.3-3` | Window advance occurs only after integrity verification succeeds (never on unverified packets) (§3.4.3) | MUST | 3.4.3 - Sequence Number verification, the anti-replay section | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** window advancement is gated on per-packet ICV verification inside the kernel ESP receive path; ze has no control over when the window advances | | `RFC4303-3.4.2-1` | For unicast, SA lookup uses SPI (or SPI plus protocol); invalid SA causes discard (auditable event) (§3.4.2) | MUST | 3.4.2 - Inbound SA lookup | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** inbound SAD lookup by SPI and discard/audit on miss is performed per packet by the kernel; ze installs SAs keyed by SPI but never performs the lookup (internal/plugins/ospf/ipsec_install.go:484-498 samples kernel drop counters only) | | `RFC4303-3.4.2-2` | For multicast, destination address is also used in SA lookup (§3.4.2) | MUST | 3.4.2 - Inbound SA lookup | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** multicast SA resolution is a kernel per-packet lookup; ze only installs the wildcard state and proto-89 selector that lets the kernel resolve OSPFv3 multicast flows (internal/plugins/ospf/ipsec_install.go:415-419) | | `RFC4303-3.4.4.1-1` | Separate-algorithm processing: verify ICV first, then decrypt, then check padding (§3.4.4.1) | MUST | 3.4.4.1 - Separate confidentiality and integrity algorithms, inbound | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the verify-then-decrypt-then-check-padding ordering is executed by the kernel ESP inbound datapath; ze projects the crypt+auth algorithm pair but does no packet processing (internal/component/ike/dataplane/xfrm_linux.go:59-71) | | `RFC4303-3.4.4.2-1` | Combined-mode processing: decrypt and verify integrity in a single algorithm call (§3.4.4.2) | MUST | 3.4.4.2 - Combined confidentiality and integrity algorithms, inbound | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the single AEAD decrypt-and-verify call is performed by the kernel; ze projects the AEAD transform name and ICV length as SA parameters only (internal/component/ike/dataplane/xfrm_linux.go:52-58) | | `RFC4303-2.2.1-1` | Extended Sequence Numbers (64-bit) SHOULD be implemented (§2.2.1) | SHOULD | 2.2.1 - Extended (64-bit) Sequence Number | **positive:** no positive test. **negative:** no negative test | | `RFC4303-2.2.1-2` | ESN use MUST be negotiated by the SA management protocol (e.g., IKEv2) (§2.2.1) | MUST | 2.2.1 - Extended (64-bit) Sequence Number | **positive:** `unit/verify` [`TestEsnInitiatorRefusesAnESNValueItNeverOffered`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc7296_esn_test.go#L152). **negative:** `unit/verify` [`TestEsnInitiatorRefusesAnESNValueItNeverOffered`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc7296_esn_test.go#L156) | | `RFC4303-3.4.3-4` | A window size of 64 is preferred and SHOULD be employed as the default (§3.4.3) | SHOULD | 3.4.3 - Sequence Number verification, the anti-replay section | **positive:** `unit/verify` [`TestChildSAReplayWindowDefault`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L326). **negative:** no negative test. **{single-polarity}:** a default value has no conforming negative. The requirement is met or it is not, and there is no input a receiver must refuse, so the sibling rows -3.4.3-1 and -3.4.3-2 carry the same annotation. Proven by the production Child SA path installing ReplayWin=64 on both directions (internal/component/ike/engine/child.go:62, :377, :444) | | `RFC4303-3.3.4-3` | Tunnel-mode ESP may encapsulate a fragment (§3.3.4) | MAY | 3.3.4 - Fragmentation | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4303-2.2-1`](#rfc4303-2.2-1) Sequence Number MUST be incremented for every transmitted packet (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: the per-SA sequence counter is kernel XFRM/ESP per-packet state; ze installs the SA but never touches sequence numbers (internal/component/ike/dataplane/dataplane.go:80-108 has no sequence field) | | [`RFC4303-2.2-2`](#rfc4303-2.2-2) Sender MUST NOT send a packet that would cause the sequence counter to cycle (overflow) (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: 32-bit counter overflow protection is enforced by the kernel XFRM datapath (it expires the state rather than wrapping); ze's control plane holds no per-packet counter | | [`RFC4303-2.2-3`](#rfc4303-2.2-3) Counter and receiver window MUST be reset before the 2^32nd packet on a non-ESN SA (§2.2) | no test | no test carries this requirement id; annotated {not-applicable}: the sequence counter and receive window are kernel per-packet SA state; the kernel enforces the 2^32 boundary and a fresh counter/window arises from installing a new SA | | [`RFC4303-2.4-1`](#rfc4303-2.4-1) Padding MUST bring plaintext to the block size of the cipher and ensure 4-byte alignment of the resulting ciphertext (§2.4) | no test | no test carries this requirement id; annotated {not-applicable}: ESP padding is applied by the kernel encryption datapath during packet construction; ze projects only the algorithm choice and never builds ESP payloads | | [`RFC4303-2.6-1`](#rfc4303-2.6-1) Transmitter MUST be capable of generating dummy packets (Next Header = 59) for traffic-flow confidentiality (§2.6) | no test | no test carries this requirement id; annotated {not-applicable}: dummy/TFC packet generation is an ESP transmit-datapath function; ze emits no ESP packets, so it plays no role in producing Next-Header-59 dummies | | [`RFC4303-2.6-2`](#rfc4303-2.6-2) Receiver MUST silently discard dummy packets (Next Header = 59) (§2.6) | no test | no test carries this requirement id; annotated {not-applicable}: inbound Next-Header-59 dummy discard occurs in the kernel ESP receive path after decryption; ze processes no inbound ESP payloads | | [`RFC4303-3.3.3-1`](#rfc4303-3.3.3-1) Sender initializes sequence counter to 0 at SA establishment; first transmitted packet carries Sequence Number 1 (§3.3.3) | no test | no test carries this requirement id; annotated {not-applicable}: the kernel initializes the sequence counter when the SA state is added; ze installs a fresh SA with no seq field and never sets or reads the counter (internal/component/ike/dataplane/xfrm_linux.go:21-86) | | [`RFC4303-3.3.4-1`](#rfc4303-3.3.4-1) Transport-mode ESP is applied only to whole IP datagrams, never to fragments (§3.3.4) | no test | no test carries this requirement id; annotated {not-applicable}: ordering ESP relative to fragmentation is an outbound kernel datapath decision; ze selects transport vs tunnel mode as an SA parameter but does not apply ESP to packets (internal/plugins/ospf/ipsec_install.go:413) | | [`RFC4303-3.3.4-2`](#rfc4303-3.3.4-2) Implementation MUST support Path MTU Discovery (§3.3.4) | no test | no test carries this requirement id; annotated {not-applicable}: Path MTU Discovery is provided by the kernel networking/XFRM stack; ze's control plane installs SAs and does not run the datapath MTU machinery | | [`RFC4303-3.4.3-3`](#rfc4303-3.4.3-3) Window advance occurs only after integrity verification succeeds (never on unverified packets) (§3.4.3) | no test | no test carries this requirement id; annotated {not-applicable}: window advancement is gated on per-packet ICV verification inside the kernel ESP receive path; ze has no control over when the window advances | | [`RFC4303-3.4.2-1`](#rfc4303-3.4.2-1) For unicast, SA lookup uses SPI (or SPI plus protocol); invalid SA causes discard (auditable event) (§3.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: inbound SAD lookup by SPI and discard/audit on miss is performed per packet by the kernel; ze installs SAs keyed by SPI but never performs the lookup (internal/plugins/ospf/ipsec_install.go:484-498 samples kernel drop counters only) | | [`RFC4303-3.4.2-2`](#rfc4303-3.4.2-2) For multicast, destination address is also used in SA lookup (§3.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: multicast SA resolution is a kernel per-packet lookup; ze only installs the wildcard state and proto-89 selector that lets the kernel resolve OSPFv3 multicast flows (internal/plugins/ospf/ipsec_install.go:415-419) | | [`RFC4303-3.4.4.1-1`](#rfc4303-3.4.4.1-1) Separate-algorithm processing: verify ICV first, then decrypt, then check padding (§3.4.4.1) | no test | no test carries this requirement id; annotated {not-applicable}: the verify-then-decrypt-then-check-padding ordering is executed by the kernel ESP inbound datapath; ze projects the crypt+auth algorithm pair but does no packet processing (internal/component/ike/dataplane/xfrm_linux.go:59-71) | | [`RFC4303-3.4.4.2-1`](#rfc4303-3.4.4.2-1) Combined-mode processing: decrypt and verify integrity in a single algorithm call (§3.4.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: the single AEAD decrypt-and-verify call is performed by the kernel; ze projects the AEAD transform name and ICV length as SA parameters only (internal/component/ike/dataplane/xfrm_linux.go:52-58) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4303-1-1`](#rfc4303-1-1) Integrity-only ESP MUST be offered as a service selection option and MUST be configurable via management interfaces (§1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L112) | unit/verify | unproven | | positive | [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L127) | unit/verify | unproven | ### [`RFC4303-2-1`](#rfc4303-2-1) The outer protocol header that precedes the ESP header SHALL carry the value 50 in its Protocol or Next Header field (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecSAProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L295) | unit/verify | unproven | | positive | [`TestIPsecSAProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L290) | unit/verify | unproven | ### [`RFC4303-2.1-1`](#rfc4303-2.1-1) SPI value 0 MUST NOT appear on the wire (reserved for local use) (§2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGenerateESPSPI`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L272) | unit/verify | unproven | | negative | [`TestIPsecSPIBoundary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L186) | unit/verify | unproven | | positive | [`TestGenerateESPSPI`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L258) | unit/verify | unproven | | positive | [`TestIPsecSPIBoundary`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L192) | unit/verify | unproven | ### [`RFC4303-2.1-2`](#rfc4303-2.1-2) Whether source and destination address matching is required to map inbound traffic to an SA MUST be set by manual SA configuration or by SA management protocol negotiation (§2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecSAAddressMatchIndication`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L326) | unit/verify | unproven | | positive | [`TestIPsecSAAddressMatchIndication`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L320) | unit/verify | unproven | ### [`RFC4303-2.2-1`](#rfc4303-2.2-1) Sequence Number MUST be incremented for every transmitted packet (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-2.2-1, so no unit is bound to it. ### [`RFC4303-2.2-2`](#rfc4303-2.2-2) Sender MUST NOT send a packet that would cause the sequence counter to cycle (overflow) (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-2.2-2, so no unit is bound to it. ### [`RFC4303-2.2-3`](#rfc4303-2.2-3) Counter and receiver window MUST be reset before the 2^32nd packet on a non-ESN SA (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-2.2-3, so no unit is bound to it. ### [`RFC4303-2.4-1`](#rfc4303-2.4-1) Padding MUST bring plaintext to the block size of the cipher and ensure 4-byte alignment of the resulting ciphertext (§2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-2.4-1, so no unit is bound to it. ### [`RFC4303-2.6-1`](#rfc4303-2.6-1) Transmitter MUST be capable of generating dummy packets (Next Header = 59) for traffic-flow confidentiality (§2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-2.6-1, so no unit is bound to it. ### [`RFC4303-2.6-2`](#rfc4303-2.6-2) Receiver MUST silently discard dummy packets (Next Header = 59) (§2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-2.6-2, so no unit is bound to it. ### [`RFC4303-3.2-1`](#rfc4303-3.2-1) The encryption and integrity algorithms MUST NOT both be NULL; at least one ESP service is always selected (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L108) | unit/verify | unproven | | positive | [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L131) | unit/verify | unproven | ### [`RFC4303-3.3.3-1`](#rfc4303-3.3.3-1) Sender initializes sequence counter to 0 at SA establishment; first transmitted packet carries Sequence Number 1 (§3.3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-3.3.3-1, so no unit is bound to it. ### [`RFC4303-3.3.4-1`](#rfc4303-3.3.4-1) Transport-mode ESP is applied only to whole IP datagrams, never to fragments (§3.3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-3.3.4-1, so no unit is bound to it. ### [`RFC4303-3.3.4-2`](#rfc4303-3.3.4-2) Implementation MUST support Path MTU Discovery (§3.3.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-3.3.4-2, so no unit is bound to it. ### [`RFC4303-3.4.3-1`](#rfc4303-3.4.3-1) Anti-replay sliding window: minimum window size of 32 packets MUST be supported (§3.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAReplayWindowDefault`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L323) | unit/verify | unproven | ### [`RFC4303-3.4.3-2`](#rfc4303-3.4.3-2) Anti-replay MUST NOT be enabled unless the ESP integrity service is also enabled (§3.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAReplayRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L349) | unit/verify | unproven | ### [`RFC4303-3.4.3-3`](#rfc4303-3.4.3-3) Window advance occurs only after integrity verification succeeds (never on unverified packets) (§3.4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-3.4.3-3, so no unit is bound to it. ### [`RFC4303-3.4.2-1`](#rfc4303-3.4.2-1) For unicast, SA lookup uses SPI (or SPI plus protocol); invalid SA causes discard (auditable event) (§3.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-3.4.2-1, so no unit is bound to it. ### [`RFC4303-3.4.2-2`](#rfc4303-3.4.2-2) For multicast, destination address is also used in SA lookup (§3.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-3.4.2-2, so no unit is bound to it. ### [`RFC4303-3.4.4.1-1`](#rfc4303-3.4.4.1-1) Separate-algorithm processing: verify ICV first, then decrypt, then check padding (§3.4.4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-3.4.4.1-1, so no unit is bound to it. ### [`RFC4303-3.4.4.2-1`](#rfc4303-3.4.4.2-1) Combined-mode processing: decrypt and verify integrity in a single algorithm call (§3.4.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4303-3.4.4.2-1, so no unit is bound to it. ### [`RFC4303-2.2.1-2`](#rfc4303-2.2.1-2) ESN use MUST be negotiated by the SA management protocol (e.g., IKEv2) (§2.2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEsnInitiatorRefusesAnESNValueItNeverOffered`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc7296_esn_test.go#L156) | unit/verify | unproven | | positive | [`TestEsnInitiatorRefusesAnESNValueItNeverOffered`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc7296_esn_test.go#L152) | unit/verify | unproven | ### [`RFC4303-3.4.3-4`](#rfc4303-3.4.3-4) A window size of 64 is preferred and SHOULD be employed as the default (§3.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestChildSAReplayWindowDefault`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/child_test.go#L326) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase 2, rfc4303 | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc4303.txt | | Source fingerprint | ec4c9d4414570513 | | Record | rfc/extraction/rfc4303.json | | Mapped sentences | 19 | | Declined as scope | 36 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, copyright notice, Abstract and table of contents. The Abstract summarises the protocol and states no obligation. | | `1` | Introduction | 2 | walked | Introduction. It states the service model and the three service combinations, and its two sites carry the integrity-only obligation this walk declares as RFC4303-1-1. The RFC 2119 key-words paragraph sits here too and binds nobody, which is why the derivation excludes it from the site inventory. | | `2` | Packet format | 2 | walked | Packet format. Site 2:1 is the protocol number 50 obligation and site 2:2 is the backward-compatibility sentence. The field figure and the coverage prose are descriptive. | | `2.1` | Security Parameters Index | 6 | walked | Security Parameters Index. Six sites: the unicast and multicast lookup obligations, the SAD search equivalence, the address-matching indication, and the SPI zero prohibition. | | `2.2` | Sequence Number | 5 | walked | Sequence Number. Five sites covering the sender increment, the mandatory presence of the field, the capability to perform the section 3.3.3 and 3.4.3 processing, and the counter reset before the 2^32nd packet. | | `2.2.1` | Extended (64-bit) Sequence Number | 1 | walked | Extended (64-bit) Sequence Number. Its one site is the negotiation MUST. The 'ESNs SHOULD be implemented' sentence in the same paragraph is advisory, so the capitalised keyword scan does not see it. | | `2.3` | Payload Data | 4 | walked | Payload Data. Four sites, three of which bind the author of an algorithm-definition RFC and one the packet builder's header alignment. | | `2.4` | Padding for encryption | 4 | walked | Padding for encryption. Four sites: the implementation's obligation to support padding, the default padding contents, and two obligations on the algorithm-definition RFC. | | `2.5` | Pad Length | 0 | walked | Pad Length. A field description with a value range and a mandatory-field statement. No obligation is directed at a speaker or a receiver. | | `2.6` | Next Header | 3 | walked | Next Header. Three sites: the value 59 convention, the transmitter and receiver dummy packet obligations fused in one sentence, and the field-presence rule for dummy packets. RFC4303-2.6-2 is the receiver half of site 2.6:2 and has no site of its own. | | `2.7` | Traffic Flow Confidentiality padding | 2 | walked | Traffic Flow Confidentiality padding. Both sites bind a transmitter that employs TFC padding, which Ze does not implement. | | `2.8` | Integrity Check Value | 1 | walked | Integrity Check Value. Its one site binds the integrity algorithm specification. | | `3` | Processing | 0 | walked | Processing. A heading with no text of its own. | | `3.1` | ESP header location | 0 | walked | ESP header location. Descriptive prose introducing the two modes. | | `3.1.1` | Transport mode processing | 0 | walked | Transport mode processing. Diagrams and placement prose, with no capitalised keyword and no obligation stated in indicative prose either. | | `3.1.2` | Tunnel mode processing | 0 | walked | Tunnel mode processing. Diagrams and placement prose. The outer and inner header rules it states are RFC 4301's, which the Security Architecture document owns. | | `3.2` | Algorithms | 1 | walked | Algorithms. Its one site is the prohibition on both algorithms being NULL, declared by this walk as RFC4303-3.2-1. | | `3.2.1` | Encryption algorithms | 0 | walked | Encryption algorithms. Descriptive, with lowercase 'must' statements binding the algorithm RFC to specify a padding modulus. | | `3.2.2` | Integrity algorithms | 0 | walked | Integrity algorithms. Descriptive, with the same lowercase obligation on the algorithm RFC as section 3.2.1. | | `3.2.3` | Combined mode algorithms | 0 | walked | Combined mode algorithms. Descriptive: it says a combined mode algorithm provides both services and that no payload substructure is defined. | | `3.3` | Outbound packet processing | 0 | walked | Outbound packet processing. A heading with an ordering list and no obligation. | | `3.3.1` | Outbound SA lookup | 0 | walked | Outbound SA lookup. It points at the Security Architecture document for how an SA is found for an outbound packet. | | `3.3.2` | Packet encryption and ICV calculation | 0 | walked | Packet encryption and ICV calculation. A heading introducing 3.3.2.1 and 3.3.2.2. | | `3.3.2.1` | Separate confidentiality and integrity algorithms, outbound | 3 | walked | Separate confidentiality and integrity algorithms, outbound. Three sites, all binding the ICV computation or the integrity algorithm's defining document. | | `3.3.2.2` | Combined confidentiality and integrity algorithms, outbound | 1 | walked | Combined confidentiality and integrity algorithms, outbound. Its one site binds the combined mode algorithm's defining RFC. | | `3.3.3` | Sequence Number generation | 2 | walked | Sequence Number generation. Two sites: the counter cycling prohibition and the manually keyed counter maintenance. RFC4303-3.3.3-1 renders the section's opening sentences, 'The sender's counter is initialized to 0 when an SA is established' and 'the first packet sent using a given SA will contain a sequence number of 1', which state the rule in indicative prose and carry no keyword for the site scan to see. | | `3.3.4` | Fragmentation | 1 | walked | Fragmentation. Its one site is the PMTU obligation. RFC4303-3.3.4-1 renders 'Thus, transport mode ESP is applied only to whole IP datagrams (not to IP fragments)', which is indicative prose, and RFC4303-3.3.4-3 renders the tunnel mode permission in the same paragraph. | | `3.4` | Inbound packet processing | 0 | walked | Inbound packet processing. A heading. | | `3.4.1` | Reassembly | 1 | walked | Reassembly. Its one site is the fragment discard obligation. | | `3.4.2` | Inbound SA lookup | 1 | walked | Inbound SA lookup. Its one site is the discard when no valid SA exists; the lookup rule itself is stated in section 2.1 and mapped from there. | | `3.4.3` | Sequence Number verification, the anti-replay section | 6 | walked | Sequence Number verification, the anti-replay section. Six sites: support for the service, the integrity precondition, the receive counter initialisation, the duplicate check, the discard on integrity failure, and the window size floor. RFC4303-3.4.3-3 renders 'The receive window is updated only if the integrity verification succeeds', which is indicative prose with no keyword. Read at the producer before classifying: every ESP SA Ze installs carries integrity (parseESPProposal refuses a non-AEAD proposal with no hash; planStateAlgos gives ESP Crypt+Auth or AEAD), the IKE Child SA path sets a 64-packet window on both directions, and the manually keyed OSPFv3 SA sets no window at all. The window check, the duplicate check and the ICV verification run in the kernel. | | `3.4.4` | ICV verification | 0 | walked | ICV verification. A heading introducing 3.4.4.1 and 3.4.4.2. | | `3.4.4.1` | Separate confidentiality and integrity algorithms, inbound | 2 | walked | Separate confidentiality and integrity algorithms, inbound. Two sites: the discard on ICV mismatch and the ordering rule that integrity completes before the decrypted packet goes on. | | `3.4.4.2` | Combined confidentiality and integrity algorithms, inbound | 1 | walked | Combined confidentiality and integrity algorithms, inbound. Its one site is the discard on integrity failure. RFC4303-3.4.4.2-1 renders step 1 of the numbered list, 'Decrypts and integrity checks the ESP Payload Data ... using the key, algorithm, algorithm mode', which is indicative prose. | | `4` | Auditing | 1 | walked | Auditing. Its one site binds the ESP implementation that processes packets and the audit subsystem of the system that hosts it. | | `5` | Conformance requirements | 4 | walked | Conformance requirements. Four sites: the conformance umbrella and the cross-reference to RFC 4301, the multicast restatement, the NULL integrity negotiation for a confidentiality-only implementation, and the both-NULL prohibition restated. | | `6` | Security Considerations | 0 | walked | Security Considerations. Two sentences saying security permeates the specification and pointing at the Security Architecture document. No countermeasure is directed at a speaker. | | `7` | Differences from RFC 2406 | 1 | walked | Differences from RFC 2406. A change log; its one site reports a level change rather than stating an obligation. | | `8` | Backward compatibility considerations | 0 | walked | Backward compatibility considerations. It explains that ESP has no version number and what an ESP v2 receiver may do with Next Header 59. Every statement is indicative and the normative half is site 2:2 in section 2. | | `9` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `10` | References heading | 0 | skipped (references) | References heading. | | `10.1` | not stated | 0 | skipped (references) | Normative References: RFC 2119, RFC 4301, RFC 4302, RFC 2434 and the ESP algorithm requirements document. | | `10.2` | not stated | 0 | walked | The derived span carries the Informative References and Appendix A, Extended (64-bit) Sequence Numbers. The appendix describes the receiver's ESN window management and its optional resynchronisation heuristic. It states no MUST-level obligation: no capitalised MUST, SHALL or REQUIRED appears anywhere after the section 10 heading. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The obligation binds the deployment that has ESP version-compatibility concerns, and the remedy it names is a deployment choice: a signaling mechanism between the peers or an out-of-band configuration mechanism. Ze implements no ESP version field and no version signaling of its own. It provides both named mechanisms, IKEv2 Child SA negotiation (internal/component/ike/engine/child.go) and manual OSPFv3 configuration (internal/plugins/ospf/ipsec_install.go), so an operator on Ze always has one. | ESP does not contain a version number, therefore if there are concerns about backward compatibility, they MUST be addressed by using a signaling mechanism between the two IPsec peers to ensure compatible versions of ESP (e.g., Internet Key Exchange (IKEv2) [Kau05]) or an out-of-band configuration mechanism. | | `2.1:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the inbound SAD demultiplexer, which is the ESP receive datapath. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. SPI collision handling happens inside the kernel lookup. | A multicast-capable IPsec implementation MUST correctly de-multiplex inbound traffic even in the context of SPI collisions. | | `2.1:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the implementation of the SAD search itself, including any acceleration of it. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | In practice, an implementation MAY choose any method to accelerate this search, although its externally visible behavior MUST be functionally equivalent to having searched the SAD in the above order. | | `2.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP transmit datapath that builds the header. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | The field is mandatory and MUST always be present even if the receiver does not elect to enable the anti-replay service for a specific SA. | | `2.2:3` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The sentence adds no obligation of its own. It says an ESP implementation must be capable of the processing described in sections 3.3.3 and 3.4.3, whose obligations this walk already accounts for: RFC4303-3.3.3-1, RFC4303-2.2-2 and the RFC4303-3.4.3 rows. | Processing of the Sequence Number field is at the discretion of the receiver, but all ESP implementations MUST be capable of performing the processing described in Sections 3.3.3 and 3.4.3. | | `2.2:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP transmit datapath that builds the header, as site 2.2:2 does. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | Thus, the sender MUST always transmit this field, but the receiver need not act upon it (see the discussion of Sequence Number Verification in the "Inbound Packet Processing" section (3.4.3) below). | | `2.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the author of the RFC that specifies how an encryption algorithm is used with ESP: that document must state the length, structure and location of explicit per-packet synchronization data. Ze writes no algorithm specification. The role is a document author, so no producer could act as it. Ze CONSUMES such a specification: `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`) projects the negotiated algorithm and key onto the SA it installs, and the kernel applies whatever that document defined. | (See Figure 2.) Any encryption algorithm that requires such explicit, per-packet synchronization data MUST indicate the length, any structure for such data, and the location of this data as part of an RFC specifying how the algorithm is used with ESP. | | `2.3:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the author of an algorithm-definition RFC, as site 2.3:1 does: implicit synchronization data must be derived by an algorithm the defining RFC states. The role is a document author, so no producer could act as it. Ze CONSUMES such a specification: `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`) projects the negotiated algorithm and key onto the SA it installs, and the kernel applies whatever that document defined. | See Figure 2.) If such synchronization data is implicit, the algorithm for deriving the data MUST be part of the algorithm definition RFC. | | `2.3:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP packet builder, which must align the next layer protocol header relative to the ESP header. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | Note that the beginning of the next layer protocol header MUST be aligned relative to the beginning of the ESP header as follows. | | `2.3:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the author of an algorithm specification, which must say how ciphertext alignment is achieved when the receiver reads the IV separately. The role is a document author, so no producer could act as it. Ze CONSUMES such a specification: `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`) projects the negotiated algorithm and key onto the SA it installs, and the kernel applies whatever that document defined. | In these cases, the algorithm specification MUST address how alignment of the (real) ciphertext is to be achieved. | | `2.4:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP transmit datapath, which fills the padding bytes with the default monotonically increasing series when the algorithm states no contents. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | If Padding bytes are needed but the encryption algorithm does not specify the padding contents, then the following default processing MUST be used. | | `2.4:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the author of the RFC that defines how an encryption or combined mode algorithm is employed with ESP, which must specify any constraint on padding byte values. The role is a document author, so no producer could act as it. Ze CONSUMES such a specification: `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`) projects the negotiated algorithm and key onto the SA it installs, and the kernel applies whatever that document defined. | If an encryption or combined mode algorithm imposes constraints on the values of the bytes used for padding, they MUST be specified by the RFC defining how the algorithm is employed with ESP. | | `2.4:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same algorithm-definition RFC as site 2.4:3, which must say whether padding values are checked. The role is a document author, so no producer could act as it. Ze CONSUMES such a specification: `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`) projects the negotiated algorithm and key onto the SA it installs, and the kernel applies whatever that document defined. | If the algorithm requires checking of the values of the bytes used for padding, this too MUST be specified in that RFC. | | `2.6:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The value convention and the capability are one obligation stated twice in adjacent sentences. RFC4303-2.6-1 names the value 59 and the dummy packet, and site 2.6:2 maps it. | To facilitate the rapid generation and discarding of the padding traffic in support of traffic flow confidentiality (see Section 2.4), the protocol value 59 (which means "no next header") MUST be used to designate a "dummy" packet. | | `2.6:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP transmit datapath that constructs a dummy packet and fills its header and trailer fields. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | All other ESP header and trailer fields (SPI, Sequence Number, Padding, Pad Length, Next Header, and ICV) MUST be present in dummy packets, but the plaintext portion of the payload, other than this Next Header field, need not be well-formed, e.g., the rest of the Payload Data may consist of only random bytes. | | `2.7:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds an ESP transmitter that adds TFC padding. Ze implements none: dataplane.SAParams carries no TFC padding field and xfrmStateFromParams sets none, so no SA Ze installs can add TFC padding or modify a datagram length field. The producer that would act as it if ze did is `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`), which builds every SA ze installs and sets no TFC padding field on any of them. | (ESP trailer fields are located by counting back from the end of the ESP packet.) Accordingly, if TFC padding is added, the field containing the specification of the length of the IP datagram MUST NOT be modified to reflect this padding. | | `2.7:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the SA management protocol only once a transmitter employs TFC padding, and Ze implements no such transmitter (site 2.7:1). dataplane.SAParams carries no TFC padding field, so no Ze-installed SA can employ the service that would trigger the negotiation. The producer that would act as it if ze did is `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`), which builds every SA ze installs and sets no TFC padding field on any of them. | However, because receivers may not have been prepared to deal with this padding, the SA management protocol MUST negotiate this service prior to a transmitter employing it, to ensure backward compatibility. | | `2.8:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the author of an integrity algorithm specification, which must state the ICV length and the comparison rules. Ze writes no algorithm specification; it projects the negotiated algorithm name and key onto the SA. The role is a document author, so no producer could act as it. Ze CONSUMES such a specification: `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`) projects the negotiated algorithm and key onto the SA it installs, and the kernel applies whatever that document defined. | The integrity algorithm specification MUST specify the length of the ICV and the comparison rules and processing steps for validation. | | `3.3.2.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP transmit datapath that computes the ICV and appends implicit padding to reach the integrity algorithm's block size. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | If the length of ESP packet (as described above) does not match the block size requirements for the algorithm, implicit padding MUST be appended to the end of the ESP packet. | | `3.3.2.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP implementation that computes the ICV, which must read the integrity algorithm's defining document to learn whether implicit padding applies. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | The document that defines an integrity algorithm MUST be consulted to determine if implicit padding is required as described above. | | `3.3.2.1:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP transmit datapath, which must use zero-valued implicit padding octets when the algorithm states no contents. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | If the document does not specify an answer to this question, then the default is to assume that implicit padding is required (as needed to match the packet length to the algorithm's block size.) If padding bytes are needed but the algorithm does not specify the padding contents, then the padding octets MUST have a value of zero. | | `3.3.2.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the author of the RFC that defines the use of a combined mode algorithm with ESP, which must state where the integrity fields sit and how the SPI and Sequence Number enter the computation. The role is a document author, so no producer could act as it. Ze CONSUMES such a specification: `xfrmStateFromParams` (`internal/component/ike/dataplane/xfrm_linux.go`) projects the negotiated algorithm and key onto the SA it installs, and the kernel applies whatever that document defined. | The location of any integrity fields, and the means by which the Sequence Number and SPI are included in the integrity computation, MUST be defined in an RFC that defines the use of the combined mode algorithm with ESP. | | `3.3.3:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP transmit datapath that maintains the per-SA sequence counter across reboots, and only once a user employs anti-replay with a manually keyed SA. The counter itself is kernel state. An operator can now switch anti-replay on for a manually keyed OSPFv3 SA (the interface replay-window leaf, carried into SAParams.ReplayWin by buildIPsecSA, internal/plugins/ospf/ipsec_install.go), and it is off unless they do; either way the sender counter is maintained by Linux XFRM and Ze never reads or writes it. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | If a user chooses to employ anti-replay in conjunction with SAs that are manually keyed, the sequence number counter at the sender MUST be correctly maintained across local reboots, etc., until the key is replaced. | | `3.4.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP receive datapath, which discards a packet that arrives as an IP fragment. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | If a packet offered to ESP for processing appears to be an IP fragment, i.e., the OFFSET field is non-zero or the MORE FRAGMENTS flag is set, the receiver MUST discard the packet; this is an auditable event. | | `3.4.3:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Supporting the anti-replay service and supporting its sliding window are the same obligation at two grains, and RFC4303-3.4.3-1 is proven by the evidence this sentence would need: the production Child SA path installs a 64-packet window on both directions (internal/component/ike/engine/child.go). A second row proven by the same assertion would be one fact declared twice. | All ESP implementations MUST support the anti-replay service, though its use may be enabled or disabled by the receiver on a per-SA basis. | | `3.4.3:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the kernel XFRM replay state. Ze sets only the window size on an installed state (state.ReplayWindow from p.ReplayWin, xfrm_linux.go lines 132 to 133) and carries no replay counter into a new state, so a state Ze adds starts with a zero receive counter and Ze has no way to set a non-zero one. | If the receiver has enabled the anti-replay service for this SA, the receive packet counter for the SA MUST be initialized to zero when the SA is established. | | `3.4.3:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP receive datapath, which checks each arriving packet's Sequence Number against the window. Ze projects the window size that makes the kernel perform this check (replayWindow = 64, internal/component/ike/engine/child.go) and never sees a packet. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | For each received packet, the receiver MUST verify that the packet contains a Sequence Number that does not duplicate the Sequence Number of any other packets received during the life of this SA. | | `3.4.3:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP receive datapath, which verifies the ICV and discards the datagram when verification fails. Ze projects the integrity algorithm and key onto the SA (planStateAlgos, internal/component/ike/dataplane/dataplane.go) and verifies no ICV. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | In either case, if the integrity check fails, the receiver MUST discard the received IP datagram as invalid; this is an auditable event. | | `3.4.4.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP receive datapath, which discards a datagram whose computed and received ICVs differ. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | If the test fails, then the receiver MUST discard the received IP datagram as invalid; this is an auditable event. | | `3.4.4.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP receive datapath, which discards a datagram when the combined mode algorithm reports an integrity failure. Ze projects the AEAD transform name and ICV length as SA parameters (planStateAlgos returns AEAD for a combined mode cipher) and runs no algorithm. Ze installs SA parameters (dataplane.SAParams) into the Linux kernel XFRM state (xfrmStateFromParams, internal/component/ike/dataplane/xfrm_linux.go) and builds, pads, encrypts or parses no ESP packet. | If the integrity check performed by the combined mode algorithm fails, the receiver MUST discard the received IP datagram as invalid; this is an auditable event. | | `4:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ESP implementation that processes packets, whose auditable events are the five this section lists: no valid SA, an IP fragment offered to ESP, sequence number overflow, an anti-replay failure and an integrity failure. Every one of them occurs in the kernel receive or transmit path, so the kernel audit subsystem is the auditor. Ze samples the kernel's XFRM drop counters for its own metrics (readXfrmDrops, internal/plugins/ospf/ipsec_install.go), which is a report of the kernel's decisions and not the ESP audit trail this sentence requires. | However, if ESP is incorporated into a system that supports auditing, then the ESP implementation MUST also support auditing and MUST allow a system administrator to enable or disable auditing for ESP. | | `5:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The sentence's own added obligation is compliance with the packet processing requirements of the Security Architecture document [Ken-Arch], which is RFC 4301. That RFC is enrolled and summarised at rfc/short/rfc4301.md and carries its own requirement rows. The first clause is a conformance umbrella over this document's own obligations, each of which this walk maps or excludes on its own site. | Implementations that claim conformance or compliance with this specification MUST implement the ESP syntax and processing described here for unicast traffic, and MUST comply with all additional packet processing requirements levied by the Security Architecture document [Ken-Arch]. | | `5:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The conformance section restates the multicast obligation captured as RFC4303-3.4.2-2, which site 2.1:2 maps. | Additionally, if an implementation claims to support multicast traffic, it MUST comply with the additional requirements specified for support of such traffic. | | `5:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds an implementation that offers the confidentiality-only ESP service, which Ze does not offer. parseESPProposal (internal/component/ike/ipsec/config.go) refuses a non-AEAD ESP proposal that carries no hash, and validateIPsecInterface (internal/plugins/ospf/config_ipsec.go) refuses an ESP interface with no integrity algorithm and key, so no configuration reaches the antecedent of this sentence. | If an implementation offers this service, it MUST also support the negotiation of the "NULL" integrity algorithm. | | `5:4` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The conformance section restates the both-NULL prohibition captured as RFC4303-3.2-1, which site 3.2:1 maps. | NOTE that although integrity and encryption may each be "NULL" under the circumstances noted above, they MUST NOT both be "NULL". | | `7:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Section 7 is the change log against RFC 2406. The keywords appear inside a description of what this revision changed, 'Confidentiality-only service -- now a MAY, not a MUST', so the sentence reports a level rather than stating one. The obligations it points at are stated normatively in sections 1, 2.1, 2.2.1, 2.6, 2.7 and 3.4.4. | o Confidentiality-only service -- now a MAY, not a MUST. o SPI -- modified to specify a uniform algorithm for SAD lookup for unicast and multicast SAs, covering a wider range of multicast technologies. | ## Superseded No document obsoletes RFC 4303, so its obligations are stated where they were written. --- ### Page: RFC 4360 - BGP Extended Communities Attribute https://ze-software.net/quality/rfc-compliance/rfc4360/ # RFC 4360 - BGP Extended Communities Attribute Supported. Every requirement this repository extracted from RFC 4360, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 33.3% | 2 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 4 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 10 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 4 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 66.7% | 4 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 10 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 4 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 4 | | Tagged units | 4 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4360.md` | | Requirement shard | `rfc/requirements/rfc4360.md` | | RFC text | `rfc/full/rfc4360.txt` | ## Enrolment Enrolled: BGP Extended Communities: six MUST-level requirements. Two are tested with both polarities over the codec (internal/core/bgp/attribute/community.go): RFC4360-2-1 (equal only when all 8 octets match) via ExtendedCommunity [8]byte equality, RFC4360-x-1 (length a multiple of 8) via ParseExtendedCommunities (wired at wire.go:412). Four are {not-applicable}: RFC4360-6-1 (best-path/forwarding-loop) since ze's best-path (rib/bestpath.go) never reads Extended Communities; RFC4360-7-1 and RFC4360-7-2 are IANA type-registry allocation rules, not implementation behavior; RFC4360-x-2 governs a non-supporting speaker's pass-through, whereas ze supports the attribute. The 6-2 SHOULD, 6-3 SHOULD NOT and 6-4/6-5 MAYs are not gated. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** Extended community attribute parsing, encoding, JSON, and policy use. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC4360-2-1`](#rfc4360-2-1), [`RFC4360-x-1`](#rfc4360-x-1) **Annotated instead of tested (4):** [`RFC4360-6-1`](#rfc4360-6-1), [`RFC4360-7-1`](#rfc4360-7-1), [`RFC4360-7-2`](#rfc4360-7-2), [`RFC4360-x-2`](#rfc4360-x-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4360-6-1` | The Extended Communities attribute MUST NOT be used to modify the BGP best path selection algorithm in a way that leads to forwarding loops (§6) | MUST NOT | 6 - Operations | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's best-path decision (internal/component/bgp/plugins/rib/bestpath.go) selects on the RFC 4271 tie-breakers only -- LOCAL_PREF, AS_PATH length, ORIGIN, MED, ..., Router ID -- and never reads the Extended Communities attribute, so there is no ext-comm-driven path selection that could create a forwarding loop | | `RFC4360-7-1` | The value allocated for a regular Type MUST NOT be reused as the high-order octet when allocating an extended Type (§7) | MUST NOT | 7 - IANA Considerations | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this constrains the IANA Extended Community type-code registry allocation, not a BGP implementation; ze consumes registry type codes and does not allocate them | | `RFC4360-7-2` | The value of the high-order octet allocated for an extended Type MUST NOT be reused when allocating a regular Type (§7) | MUST NOT | 7 - IANA Considerations | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** same as 7-1 -- an IANA type-allocation rule on the registry, not implementation behavior; ze does not allocate Extended Community type codes | | `RFC4360-2-1` | Two extended communities are equal only when all 8 octets are equal (§2) | MUST | 2 - BGP Extended Communities Attribute | **positive:** `unit/verify` [`TestExtendedCommunityEquality`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L297). **negative:** `unit/verify` [`TestExtendedCommunityEquality`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L299) | | `RFC4360-x-1` | Attribute length MUST be a multiple of 8 octets (Encoding Rules) | MUST | x | **positive:** `unit/verify` [`TestExtendedCommunitiesParse`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L277). **negative:** `unit/verify` [`TestExtendedCommunitiesParseRejectsBadLength`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L287) | | `RFC4360-x-2` | Non-supporting peers MUST pass the attribute unchanged (RFC 4271 behavior for optional transitive) (Compatibility) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this governs a speaker that does NOT support Extended Communities passing the optional-transitive attribute through unchanged (RFC 4271); ze supports the attribute -- it parses and re-encodes it, ExtendedCommunities.Flags is optional-transitive (internal/core/bgp/attribute/community.go:244) -- so ze is a supporting speaker, and the general RFC 4271 optional-transitive pass-through is tracked under RFC 4271/7606 | | `RFC4360-6-2` | Non-transitive extended communities SHOULD be removed before advertising the route across the AS boundary (§6) | SHOULD | 6 - Operations | **positive:** no positive test. **negative:** no negative test | | `RFC4360-6-3` | Non-transitive extended communities SHOULD NOT be removed when advertising the route across the BGP Confederation boundary (§6) | SHOULD NOT | 6 - Operations | **positive:** no positive test. **negative:** no negative test | | `RFC4360-6-4` | A BGP speaker receiving a route without Extended Communities MAY append this attribute when propagating (§6) | MAY | 6 - Operations | **positive:** no positive test. **negative:** no negative test | | `RFC4360-6-5` | A BGP speaker receiving a route with Extended Communities MAY modify this attribute according to local policy (§6) | MAY | 6 - Operations | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4360-6-1`](#rfc4360-6-1) The Extended Communities attribute MUST NOT be used to modify the BGP best path selection algorithm in a way that leads to forwarding loops (§6) | no test | no test carries this requirement id; annotated {not-applicable}: ze's best-path decision (internal/component/bgp/plugins/rib/bestpath.go) selects on the RFC 4271 tie-breakers only -- LOCAL_PREF, AS_PATH length, ORIGIN, MED, ..., Router ID -- and never reads the Extended Communities attribute, so there is no ext-comm-driven path selection that could create a forwarding loop | | [`RFC4360-7-1`](#rfc4360-7-1) The value allocated for a regular Type MUST NOT be reused as the high-order octet when allocating an extended Type (§7) | no test | no test carries this requirement id; annotated {not-applicable}: this constrains the IANA Extended Community type-code registry allocation, not a BGP implementation; ze consumes registry type codes and does not allocate them | | [`RFC4360-7-2`](#rfc4360-7-2) The value of the high-order octet allocated for an extended Type MUST NOT be reused when allocating a regular Type (§7) | no test | no test carries this requirement id; annotated {not-applicable}: same as 7-1 -- an IANA type-allocation rule on the registry, not implementation behavior; ze does not allocate Extended Community type codes | | [`RFC4360-x-2`](#rfc4360-x-2) Non-supporting peers MUST pass the attribute unchanged (RFC 4271 behavior for optional transitive) (Compatibility) | no test | no test carries this requirement id; annotated {not-applicable}: this governs a speaker that does NOT support Extended Communities passing the optional-transitive attribute through unchanged (RFC 4271); ze supports the attribute -- it parses and re-encodes it, ExtendedCommunities.Flags is optional-transitive (internal/core/bgp/attribute/community.go:244) -- so ze is a supporting speaker, and the general RFC 4271 optional-transitive pass-through is tracked under RFC 4271/7606 | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4360-6-1`](#rfc4360-6-1) The Extended Communities attribute MUST NOT be used to modify the BGP best path selection algorithm in a way that leads to forwarding loops (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4360-6-1, so no unit is bound to it. ### [`RFC4360-7-1`](#rfc4360-7-1) The value allocated for a regular Type MUST NOT be reused as the high-order octet when allocating an extended Type (§7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4360-7-1, so no unit is bound to it. ### [`RFC4360-7-2`](#rfc4360-7-2) The value of the high-order octet allocated for an extended Type MUST NOT be reused when allocating a regular Type (§7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4360-7-2, so no unit is bound to it. ### [`RFC4360-2-1`](#rfc4360-2-1) Two extended communities are equal only when all 8 octets are equal (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestExtendedCommunityEquality`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L299) | unit/verify | unproven | | positive | [`TestExtendedCommunityEquality`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L297) | unit/verify | unproven | ### [`RFC4360-x-1`](#rfc4360-x-1) Attribute length MUST be a multiple of 8 octets (Encoding Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestExtendedCommunitiesParseRejectsBadLength`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L287) | unit/verify | unproven | | positive | [`TestExtendedCommunitiesParse`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/community_test.go#L277) | unit/verify | unproven | ### [`RFC4360-x-2`](#rfc4360-x-2) Non-supporting peers MUST pass the attribute unchanged (RFC 4271 behavior for optional transitive) (Compatibility) Audit verdict: not audited: no reader has judged these tests No test carries RFC4360-x-2, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase 5, rfc4360 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc4360.txt | | Source fingerprint | 759f302ace542b9f | | Record | rfc/extraction/rfc4360.json | | Mapped sentences | 3 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of This Memo, Copyright Notice and Abstract. The Abstract restates section 1: the attribute labels information carried in BGP-4 and the labels can control its distribution. No sentence directs a speaker. | | `1` | Introduction | 0 | walked | Introduction. Indicative prose: the two enhancements over RFC 1997 (an extended range and a Type field), and what structure buys a policy writer. No directive. | | `1.1` | Specification of Requirements | 0 | walked | Specification of Requirements. The RFC 2119 key-words paragraph. It tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. Unlike RFC 7313 section 2 it does not add the 'only when in all upper case' sentence, so this walk reads a lowercase modal on its own merits rather than as a key word. | | `2` | BGP Extended Communities Attribute | 0 | walked | BGP Extended Communities Attribute. The document's wire-format section, written entirely in the indicative, so the site scan sees nothing in it and three declared MUST-level rows are read from here. It states the attribute is transitive optional with Type Code 16, that each extended community is an 8-octet quantity, that the Type Field is 1 octet for a Regular type and 2 for an Extended type, that the high-order octet carries the I (IANA authority) and T (transitive) bits with T=0 transitive and T=1 non-transitive across ASes, and that 'Two extended communities are declared equal only when all 8 octets of the community are equal.' The three unsourced ids below are those obligations. The remaining sentences are value definitions carried by the Wire Formats and Type Classification tables of rfc/short/rfc4360.md, and the closing 'The two members in the tuple <Type, Value> should be enumerated to specify any community value' is a lowercase modal describing how a value is written down, not a directive to a speaker. | | `3` | Defined BGP Extended Community Types | 0 | walked | Defined BGP Extended Community Types. One paragraph naming what 3.1 to 3.3 define: templates identified by the high-order octet, with the low-order octet as sub-type. No directive. | | `3.1` | Two-Octet AS Specific Extended Community | 0 | walked | Two-Octet AS Specific Extended Community. Value assignment: high-order octet 0x00 or 0x40, a 2-octet Global Administrator holding an IANA-assigned AS number and a 4-octet Local Administrator. Its one modal, 'The format and meaning of the value encoded in this sub-field should be defined by the sub-type of the community', is lowercase and binds whoever defines a future sub-type, not a speaker encoding one. The layout is carried by the Two-Octet AS Specific table of rfc/short/rfc4360.md. | | `3.2` | IPv4 Address Specific Extended Community | 0 | walked | IPv4 Address Specific Extended Community. Value assignment: high-order octet 0x01 or 0x41, a 4-octet Global Administrator holding a registry-assigned IPv4 address and a 2-octet Local Administrator. Its lowercase 'should be defined by the sub-type' sentence binds a future sub-type definition, as in 3.1. The layout is carried by the IPv4 Address Specific table of rfc/short/rfc4360.md. | | `3.3` | Opaque Extended Community | 0 | walked | Opaque Extended Community. Value assignment: high-order octet 0x03 or 0x43 and a 6-octet opaque Value. States that the sub-type defining the Value Field is to be assigned by IANA, which is an allocation fact rather than a directive to a speaker. The layout is carried by the Opaque Extended Community table of rfc/short/rfc4360.md. | | `4` | Route Target Community | 0 | walked | Route Target Community. Value assignment: an extended type whose high-order octet is 0x00, 0x01 or 0x02 and whose low-order octet is 0x02, transitive across the AS boundary, with the Local Administrator drawn from the numbering space of the organization holding the AS number or IP address in the Global Administrator sub-field. Every sentence is indicative, and the use is deferred to RFC 4364. Carried by the Sub-Types table of rfc/short/rfc4360.md. | | `5` | Route Origin Community | 0 | walked | Route Origin Community. The same shape as section 4 with low-order octet 0x03: an extended type, transitive across the AS boundary, Local Administrator drawn from the holder's numbering space, use deferred to RFC 4364. Indicative throughout. Carried by the Sub-Types table of rfc/short/rfc4360.md. | | `6` | Operations | 1 | walked | Operations. The only section that directs a BGP speaker. Its one MUST-level site is 6:1, mapped below to RFC4360-6-1. Its four remaining directives are the two MAYs (append the attribute to a route that lacks it, modify the attribute per local policy) and the SHOULD and SHOULD NOT that bracket a non-transitive community at an AS boundary against a Confederation boundary; those are the four unsourced ids below, advisory and never gated. Two further sentences the site scan does not see are not obligations: the aggregation paragraph states a lowercase-'should' DEFAULT that the same paragraph says 'could be overridden via local configuration', and the closing paragraph is indicative division of labour, saying a route may carry both attributes and that the RFC 1997 one is handled per RFC 1997. | | `7` | IANA Considerations | 3 | walked | IANA Considerations. Walked rather than skipped because two of its three sites are MUST-level sentences the summary declares as rows (RFC4360-7-1 and RFC4360-7-2), so an `iana` skip would hide two gated ids behind a skip kind. Both are mapped below, and both bind the registry: rfc/short/rfc4360.md records that as their {not-applicable} annotation, which is the summary's judgement and not this walk's. The rest of the section creates the 'BGP Extended Communities Type' registry and the three per-class registries, fixes the Standards Action, Early IANA Allocation, First Come First Served and Experimental ranges, and assigns 0x0002, 0x0003, 0x0102 and 0x0103. Those ranges and values are carried by the Type Classification and IANA Registered Type Values tables of rfc/short/rfc4360.md. | | `8` | Security Considerations | 1 | walked | Security Considerations. States that the extension has security implications similar to RFC 1997 and changes no underlying security issue, then places one condition on the operator (site 8:1), and puts the mechanism providing that trust relationship out of scope. No countermeasure is directed at a speaker. | | `9` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `10` | not stated | 0 | skipped (references) | Normative References: RFC 4271, RFC 1997, RFC 2119, RFC 2434, RFC 4020. | | `11` | Informative References: RFC 4364 | 1 | skipped (references) | Informative References: RFC 4364. The derived section span runs to the end of the document, so it also holds Authors' Addresses, the Full Copyright Statement, the Intellectual Property boilerplate and the RFC Editor funding note. Site 11:1 is one sentence of that IPR boilerplate and is excluded below; nothing in the span states an obligation on a BGP speaker. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `7:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the party filing a future Type value assignment request with IANA, and IANA which records the answer: the sentence directs what such a REQUEST must specify, namely whether the value is for a transitive or a non-transitive Extended Community. Ze consumes allocated type codes and files no assignment request, so it never plays the requester role. The lowercase 'must' is why the site scan sees it only under the prose register. The role is a registry act by a person, so no producer could perform it. Ze CONSUMES the resulting assignment: the extended community codec reads the type octet it is given (`internal/component/bgp/attribute`). | Future requests for assignment of a Type value must specify whether the Type value is intended for a transitive or a non- transitive Extended Community. | | `8:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the network operator: the sentence conditions an operator who RELIES on information carried in BGP on having a transitive trust relationship back to the source, and the next sentence puts the mechanism providing that relationship beyond the scope of the document. It directs no encoding, decoding or propagation behavior a BGP speaker performs, and there is nothing for a daemon to implement. The role is the AS operator, so no producer could act as it. Ze CONSUMES the operator's decision: the capability is negotiated per session in the reactor (`internal/component/bgp/reactor`), which advertises what it is configured to and decides no AS-wide policy. | Specifically, an operator who is relying on the information carried in BGP must have a transitive trust relationship back to the source of the information. | | `11:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: this is the IETF's standard Intellectual Property boilerplate, which the derived section span carries into section 11 after the Informative References. The sentence invites interested parties to disclose patent rights to the IETF, and its 'may be required to implement this standard' describes what a patent might cover rather than requiring anything of an implementation. | The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. | ## Superseded No document obsoletes RFC 4360, so its obligations are stated where they were written. --- ### Page: RFC 4364 - BGP/MPLS IP Virtual Private Networks (VPNs) https://ze-software.net/quality/rfc-compliance/rfc4364/ # RFC 4364 - BGP/MPLS IP Virtual Private Networks (VPNs) Partial. Every requirement this repository extracted from RFC 4364, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 8 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 8 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 8 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 8 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 8 | of 13 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 8 | of 8 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 8 of 8 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 8 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 8 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 8 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 13 | | Gated MUST-level | 8 | | Not applicable, so out of scope | 8 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4364.md` | | Requirement shard | `rfc/requirements/rfc4364.md` | | RFC text | `rfc/full/rfc4364.txt` | ## Enrolment Enrolled: BGP/MPLS IP Virtual Private Networks (L3VPN): eight MUST-level requirements, all {not-applicable}. ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go) with Route-Distinguisher, label, and Route-Target wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path. The gated MUSTs (CE Route-Target authorization/filtering, customer-route to VPN-IPv4 conversion, LDP for VPN LSPs, Downstream-Unsolicited and Downstream-on-Demand label distribution, backbone labeled-packet acceptance, and MPLS-in-IP/GRE tunneling with IPsec or egress-PE source validation) are all PE data-plane, forwarding, and VRF-policy duties ze does not perform. ## What the public ledger says **Status:** Partial **What the ledger says is covered:** VPNv4 NLRI, RD, labels, route config, encode, and decode. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 8 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **8** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (8):** [`RFC4364-4.3.1-1`](#rfc4364-4.3.1-1), [`RFC4364-4.3.1-2`](#rfc4364-4.3.1-2), [`RFC4364-4.3.2-1`](#rfc4364-4.3.2-1), [`RFC4364-4.3.2-2`](#rfc4364-4.3.2-2), [`RFC4364-4.3.2-3`](#rfc4364-4.3.2-3), [`RFC4364-6-1`](#rfc4364-6-1), [`RFC4364-13.1-1`](#rfc4364-13.1-1), [`RFC4364-13.1-2`](#rfc4364-13.1-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4364-4.3.1-1` | If the CE is allowed to attach RTs to its routes, the PE MUST filter out all routes that contain RTs the customer is not allowed to use (§4.3.1) | MUST | 4.3.1 - The Route Target Attribute | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so there is no CE-attached-Route-Target authorization/filter code path to enforce | | `RFC4364-4.3.1-2` | If the CE is not allowed to attach RTs but does so anyway, the PE MUST remove the RT before converting the customer's route to a VPN-IPv4 route (§4.3.1) | MUST | 4.3.1 - The Route Target Attribute | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path; there is no customer-route-to-VPN-IPv4 conversion stage (no VRF ingest), so the strip-disallowed-RT obligation has no host | | `RFC4364-4.3.2-1` | All systems implementing this VPN architecture using MPLS LSPs MUST support Label Distribution Protocol (LDP) (§4.3.2) | MUST | 4.3.2 - Route Distribution Among PEs by BGP | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** LDP support for VPN LSP tunneling is a PE data-plane duty; ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so it neither sets up nor forwards over VPN LSPs | | `RFC4364-4.3.2-2` | Downstream Unsolicited mode MUST be supported on interfaces that are neither LC-ATM nor LC-FR interfaces (§4.3.2) | MUST | 4.3.2 - Route Distribution Among PEs by BGP | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** LDP Downstream Unsolicited mode is a PE label-distribution/data-plane property; ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path | | `RFC4364-4.3.2-3` | Downstream on Demand mode MUST be supported on LC-ATM and LC-FR interfaces (§4.3.2) | MUST | 4.3.2 - Route Distribution Among PEs by BGP | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** LDP Downstream on Demand on LC-ATM/LC-FR interfaces is a PE link-layer duty; ze has no such interfaces and no VPN forwarding path (ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path) | | `RFC4364-6-1` | A router in the backbone MUST NOT accept a labeled packet from any adjacent non-backbone device unless conditions in §6 are met (§6) | MUST NOT | 6 - Maintaining Proper Isolation of VPNs | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** accepting or rejecting a labeled packet at a backbone router is an MPLS forwarding/security duty; ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so it is not a P or PE forwarder | | `RFC4364-13.1-1` | Any implementation allowing VPN packets to be tunneled via MPLS-in-IP/GRE MUST contain an implementation of IPsec (§13.1) | MUST | 13.1 - Data Plane | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so it never tunnels VPN packets via MPLS-in-IP/GRE and the must-contain-IPsec trigger never fires (the IKE/IPsec engine in internal/component/ike is unrelated to VPN tunneling) | | `RFC4364-13.1-2` | Any implementation allowing MPLS-in-IP/GRE tunneling without IPsec MUST allow the egress PE to validate the IP source address of tunneled packets (§13.1) | MUST | 13.1 - Data Plane | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so there is no MPLS-in-IP/GRE VPN data path at an egress PE to validate a tunneled packet's source address | | `RFC4364-4.3.2-4` | A PE router (unless it is a route reflector or ASBR) should not install a VPN-IPv4 route unless it has at least one VRF with a matching Import Target (§4.3.2) | SHOULD | 4.3.2 - Route Distribution Among PEs by BGP | **positive:** no positive test. **negative:** no negative test | | `RFC4364-4.3.2-5` | Inbound filtering should be used to discard unneeded VPN-IPv4 routes (§4.3.2) | SHOULD | 4.3.2 - Route Distribution Among PEs by BGP | **positive:** no positive test. **negative:** no negative test | | `RFC4364-13.2-1` | TCP/IP MD5 authentication option should be used with both BGP and LDP (§13.2) | SHOULD | 13.2 - Control Plane | **positive:** no positive test. **negative:** no negative test | | `RFC4364-4.3.2-6` | PE may distribute exact VRF routes, aggregates, or both (§4.3.2) | MAY | 4.3.2 - Route Distribution Among PEs by BGP | **positive:** no positive test. **negative:** no negative test | | `RFC4364-4.3.2-7` | Multiple label allocation strategies: per-VRF, per-attachment-circuit, or per-route (§4.3.2) | MAY | 4.3.2 - Route Distribution Among PEs by BGP | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4364-4.3.1-1`](#rfc4364-4.3.1-1) If the CE is allowed to attach RTs to its routes, the PE MUST filter out all routes that contain RTs the customer is not allowed to use (§4.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so there is no CE-attached-Route-Target authorization/filter code path to enforce | | [`RFC4364-4.3.1-2`](#rfc4364-4.3.1-2) If the CE is not allowed to attach RTs but does so anyway, the PE MUST remove the RT before converting the customer's route to a VPN-IPv4 route (§4.3.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path; there is no customer-route-to-VPN-IPv4 conversion stage (no VRF ingest), so the strip-disallowed-RT obligation has no host | | [`RFC4364-4.3.2-1`](#rfc4364-4.3.2-1) All systems implementing this VPN architecture using MPLS LSPs MUST support Label Distribution Protocol (LDP) (§4.3.2) | no test | no test carries this requirement id; annotated {not-applicable}: LDP support for VPN LSP tunneling is a PE data-plane duty; ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so it neither sets up nor forwards over VPN LSPs | | [`RFC4364-4.3.2-2`](#rfc4364-4.3.2-2) Downstream Unsolicited mode MUST be supported on interfaces that are neither LC-ATM nor LC-FR interfaces (§4.3.2) | no test | no test carries this requirement id; annotated {not-applicable}: LDP Downstream Unsolicited mode is a PE label-distribution/data-plane property; ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path | | [`RFC4364-4.3.2-3`](#rfc4364-4.3.2-3) Downstream on Demand mode MUST be supported on LC-ATM and LC-FR interfaces (§4.3.2) | no test | no test carries this requirement id; annotated {not-applicable}: LDP Downstream on Demand on LC-ATM/LC-FR interfaces is a PE link-layer duty; ze has no such interfaces and no VPN forwarding path (ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path) | | [`RFC4364-6-1`](#rfc4364-6-1) A router in the backbone MUST NOT accept a labeled packet from any adjacent non-backbone device unless conditions in §6 are met (§6) | no test | no test carries this requirement id; annotated {not-applicable}: accepting or rejecting a labeled packet at a backbone router is an MPLS forwarding/security duty; ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so it is not a P or PE forwarder | | [`RFC4364-13.1-1`](#rfc4364-13.1-1) Any implementation allowing VPN packets to be tunneled via MPLS-in-IP/GRE MUST contain an implementation of IPsec (§13.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so it never tunnels VPN packets via MPLS-in-IP/GRE and the must-contain-IPsec trigger never fires (the IKE/IPsec engine in internal/component/ike is unrelated to VPN tunneling) | | [`RFC4364-13.1-2`](#rfc4364-13.1-2) Any implementation allowing MPLS-in-IP/GRE tunneling without IPsec MUST allow the egress PE to validate the IP source address of tunneled packets (§13.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze is not a BGP/MPLS L3VPN Provider Edge: its VPN support is a control-plane VPNv4/VPNv6 (SAFI 128) NLRI codec in decode mode (internal/component/bgp/plugins/nlri/vpn/vpn.go:61-66 Mode "decode") with RD/label/RT wire decoding, and it has no VRF, no PE/CE protocol, and no MPLS-VPN forwarding data path, so there is no MPLS-in-IP/GRE VPN data path at an egress PE to validate a tunneled packet's source address | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4364-4.3.1-1`](#rfc4364-4.3.1-1) If the CE is allowed to attach RTs to its routes, the PE MUST filter out all routes that contain RTs the customer is not allowed to use (§4.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4364-4.3.1-1, so no unit is bound to it. ### [`RFC4364-4.3.1-2`](#rfc4364-4.3.1-2) If the CE is not allowed to attach RTs but does so anyway, the PE MUST remove the RT before converting the customer's route to a VPN-IPv4 route (§4.3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4364-4.3.1-2, so no unit is bound to it. ### [`RFC4364-4.3.2-1`](#rfc4364-4.3.2-1) All systems implementing this VPN architecture using MPLS LSPs MUST support Label Distribution Protocol (LDP) (§4.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4364-4.3.2-1, so no unit is bound to it. ### [`RFC4364-4.3.2-2`](#rfc4364-4.3.2-2) Downstream Unsolicited mode MUST be supported on interfaces that are neither LC-ATM nor LC-FR interfaces (§4.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4364-4.3.2-2, so no unit is bound to it. ### [`RFC4364-4.3.2-3`](#rfc4364-4.3.2-3) Downstream on Demand mode MUST be supported on LC-ATM and LC-FR interfaces (§4.3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4364-4.3.2-3, so no unit is bound to it. ### [`RFC4364-6-1`](#rfc4364-6-1) A router in the backbone MUST NOT accept a labeled packet from any adjacent non-backbone device unless conditions in §6 are met (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4364-6-1, so no unit is bound to it. ### [`RFC4364-13.1-1`](#rfc4364-13.1-1) Any implementation allowing VPN packets to be tunneled via MPLS-in-IP/GRE MUST contain an implementation of IPsec (§13.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4364-13.1-1, so no unit is bound to it. ### [`RFC4364-13.1-2`](#rfc4364-13.1-2) Any implementation allowing MPLS-in-IP/GRE tunneling without IPsec MUST allow the egress PE to validate the IP source address of tunneled packets (§13.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4364-13.1-2, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6, rfc4364 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc4364.txt | | Source fingerprint | 68e6a7bce4ad9d4b | | Record | rfc/extraction/rfc4364.json | | Mapped sentences | 7 | | Declined as scope | 50 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of This Memo, Copyright Notice, Abstract and Table of Contents. The Abstract restates section 1: a Service Provider uses an IP backbone to offer IP VPNs under a peer model, CE routers send their routes to PE routers, and data packets are tunneled so the core routers need not know the VPN routes. No site and no directive. | | `1` | Introduction | 0 | walked | Introduction. Restates the peer model, records that BGP carries a VPN's routes among the PE routers attached to it, that each VPN route is assigned an MPLS label distributed with the route, and that the labeled packet is further encapsulated so it tunnels across the backbone. Indicative throughout and no site. | | `1.1` | Virtual Private Networks | 0 | walked | Virtual Private Networks. Defines a VPN as a subset of sites with the rule that two sites have IP connectivity over the backbone only if some VPN contains both, names the customers and the Service Providers, and states that the policies deciding what is a VPN are the customers'. Definitions and scope, no directive and no site. | | `1.2` | Customer Edge and Provider Edge | 2 | walked | Customer Edge and Provider Edge. Defines attachment circuit, CE, PE and P router, ingress and egress attachment circuit, and records that a CE router peers with its PEs and not with CE routers at other sites. Both of its sites are properties of that deployment model and are excluded below. | | `1.3` | VPNs with Overlapping Address Spaces | 1 | walked | VPNs with Overlapping Address Spaces. States that two VPNs with no site in common may use the same addresses for different systems, which is common with RFC 1918 space. Its one site is the addressing constraint inside each VPN and is excluded below. | | `1.4` | VPNs with Different Routes to the Same System | 0 | walked | VPNs with Different Routes to the Same System. Works an example where intranet traffic reaches a server directly while extranet traffic is routed through a firewall at another site, and shows two routes to the same server. Illustration, no directive and no site. | | `1.5` | SP Backbone Routers | 0 | walked | SP Backbone Routers. States the scalability property the architecture rests on: routing information about a VPN is needed only in the PE routers attached to that VPN, and the P routers need no per-VPN routing information at all. Indicative and no site. | | `1.6` | Security | 1 | walked | Security. States that the architecture gives security equivalent to a layer 2 backbone, that it does not encrypt data or detect tampering, and points at section 13. Its one site is the conditional about applying cryptography and is excluded below. | | `2` | Sites and CEs | 1 | walked | Sites and CEs. Defines a site topologically rather than geographically, records that a site may belong to several VPNs, that a PE may attach to CEs of many sites, and that a CE may attach to several PEs for robustness. Its one site is a false positive of the prose scan and is excluded below. | | `3` | VRFs: Multiple Forwarding Tables in PEs | 0 | walked | VRFs: Multiple Forwarding Tables in PEs. Three sentences: each PE maintains several forwarding tables, one is the default forwarding table, and the others are VPN Routing and Forwarding tables. Definitions, no directive and no site. | | `3.1` | VRFs and Attachment Circuits | 0 | walked | VRFs and Attachment Circuits. States that every PE/CE attachment circuit is associated by configuration with one or more VRFs, that a packet arriving on one is looked up in the associated VRF, that a packet on a non-VRF circuit uses the default forwarding table, and how restricting the route set in a VRF restricts connectivity. Indicative and no site. | | `3.2` | Associating IP Packets with VRFs | 4 | walked | Associating IP Packets with VRFs. How a PE decides which attachment circuit a packet arrived on and therefore which VRF to use, why a customer must not be able to forge that decision by writing layer 2 header fields, and how virtual sites are selected by VLAN tag or IP source address. All four sites bind the PE ingress classifier or the customer host and are excluded below. | | `3.3` | Populating the VRFs | 0 | walked | Populating the VRFs. Works the PE1/PE2/PE3 example: PE1 learns routes from CE1 and distributes them by BGP, and PE2 and PE3 use them to populate the VRFs for their own sites. Records that learning covers static configuration, and that two attachment circuits share a VRF only when their CEs are in exactly the same set of VPNs. Indicative and no site. | | `4` | VPN Route Distribution via BGP | 1 | walked | VPN Route Distribution via BGP. States why two routes to the same IPv4 prefix in different VPNs must not be comparable, and announces the new address family that meets the goal. Its one site is that design statement and is excluded below. | | `4.1` | The VPN-IPv4 Address Family | 0 | walked | The VPN-IPv4 Address Family. Defines a VPN-IPv4 address as an 8-byte Route Distinguisher followed by a 4-byte IPv4 address, states that BGP never compares a VPN-IPv4 address with an IPv4 address, that an RD carries no inherent information and exists only to create distinct routes to a common prefix, and that its structure is not meaningful to BGP. This is the layout ze implements, in internal/component/bgp/plugins/nlri/vpn.ParseVPN and internal/core/bgp/nlri.RouteDistinguisher, and the summary carries it in the Wire Formats tables. No directive and no site. | | `4.2` | Encoding of Route Distinguishers | 6 | walked | Encoding of Route Distinguishers. The RD's 2-byte Type field and 6-byte Value field, and the Administrator and Assigned Number subfields of types 0, 1 and 2. The layout is carried by the Route Distinguisher Format tables of rfc/short/rfc4364.md and is decoded by internal/core/bgp/nlri.ParseRouteDistinguisher, which reads the type field and keeps the six value octets, with RouteDistinguisher.String rendering each type. All six sites constrain the number the assigning authority puts in the Administrator subfield rather than the encoding, and are excluded below. | | `4.3` | Controlling Route Distribution | 0 | walked | Controlling Route Distribution. Describes the life of a route: learned from a CE into a VRF, converted to a VPN-IPv4 route and exported to BGP, best-path selected, distributed to the PEs that need it, imported back into VRFs and distributed to the associated CE routers. Indicative and no site. | | `4.3.1` | The Route Target Attribute | 3 | walked | The Route Target Attribute. Defines the Export Targets and Import Targets of a VRF, records that Route Targets are encoded as BGP Extended Community Route Targets of RFC 4360, and closes with the two capitalised MUSTs about a CE that attaches Route Targets to its own routes, mapped below to RFC4364-4.3.1-1 and RFC4364-4.3.1-2. Site 4.3.1:1 is the distribution rule for a route carrying Route Target T and is excluded. | | `4.3.2` | Route Distribution Among PEs by BGP | 6 | walked | Route Distribution Among PEs by BGP. How PEs distribute labeled VPN-IPv4 routes over IBGP or a route reflector, with the PE's own address as BGP next hop encoded as a VPN-IPv4 address with an RD of zero, and the three label allocation strategies. Sites 4.3.2:4 and 4.3.2:5 carry the capitalised MUSTs and are mapped below to RFC4364-4.3.2-1 and RFC4364-4.3.2-2. Five further declared rows are read from prose here and are the unsourced ids: RFC4364-4.3.2-3 is the second half of the sentence site 4.3.2:5 quotes, 'Downstream on Demand mode MUST be supported on LC-ATM interfaces and LC-FR interfaces', which the splitter keeps whole; RFC4364-4.3.2-4 and RFC4364-4.3.2-5 are the two lowercase 'should' sentences about not installing a VPN-IPv4 route without a matching Import Target and discarding the rest by inbound filtering; and RFC4364-4.3.2-6 and RFC4364-4.3.2-7 are the exact-or-aggregate choice and the per-VRF, per-attachment-circuit or per-route label strategies, both stated as a choice left to the implementation. | | `4.3.3` | Use of Route Reflectors | 1 | walked | Use of Route Reflectors. Two ways to partition VPN-IPv4 routes among route reflectors: preconfigure each with a list of Route Targets and derive inbound filters and ORFs from it, or let each learn the set of Route Targets its clients use. Its one site is the BGP Refresh obligation in the second method and is excluded below. | | `4.3.4` | How VPN-IPv4 NLRI Is Carried in BGP | 1 | walked | How VPN-IPv4 NLRI Is Carried in BGP. Assigns AFI 1 with SAFI 128 to MPLS-labeled VPN-IPv4 addresses, records that AFI 1 is used because the network layer protocol is still IP, that unlabeled VPN-IPv4 addresses need not be distributed, and that the NLRI is encoded as in RFC 3107 with an 8-byte RD before the IPv4 prefix. Those values are carried by the Constants and Wire Formats tables of rfc/short/rfc4364.md and are what internal/component/bgp/plugins/nlri/vpn registers and decodes. Its one site is the capability-advertisement requirement, which the sentence itself delegates to RFC 4760, and is excluded below. | | `4.3.5` | Building VPNs Using Route Targets | 0 | walked | Building VPNs Using Route Targets. Shows how Import and Export Targets build a fully meshed closed user group and a hub-and-spoke VPN. Illustration, no directive and no site. | | `4.3.6` | Route Distribution Among VRFs in a Single PE | 0 | walked | Route Distribution Among VRFs in a Single PE. States that a route can be distributed from one VRF to another inside one PE, and that the decision is the same one that would be made if the VRFs sat on different PEs. Indicative and no site. | | `5` | Forwarding | 4 | walked | Forwarding. The whole forwarding path: choosing the VRF from the ingress attachment circuit, sending directly on an egress attachment circuit of the same PE, or pushing the VPN route label and tunneling to the BGP Next Hop with a tunnel label, and what the egress PE does with the VPN route label. Its four sites are excluded below: two are statements about what necessarily follows, one restates the LDP obligation of section 4.3.2, and one qualifies a kind of tunnel. | | `6` | Maintaining Proper Isolation of VPNs | 2 | walked | Maintaining Proper Isolation of VPNs. The rule that no backbone router accepts a tunneled packet from outside the backbone unless both tunnel endpoints are outside it, stated for MPLS as the capitalised MUST NOT of site 6:1, mapped below to RFC4364-6-1, with its two conditions. Site 6:2 is the corresponding filtering rule when MPLS is not the tunneling technology and is excluded. | | `7` | How PEs Learn Routes from CEs | 4 | walked | How PEs Learn Routes from CEs. The four PE/CE route distribution techniques: static routing, RIP, OSPF and BGP, with the loop-prevention rules each needs and the Site of Origin attribute that identifies the routes learned from one site. All four sites bind the PE or the CE of a PE/CE session and are excluded below. | | `8` | How CEs Learn Routes from PEs | 1 | walked | How CEs Learn Routes from PEs. States that a PE may distribute a route in a CE's VRF to that CE when the PE/CE protocol permits, adds the Site of Origin restriction, and observes that distributing the default route is usually enough. Its one site is that restriction and is excluded below. | | `9` | Carriers' Carriers | 6 | walked | Carriers' Carriers. How a VPN that is itself an ISP or SP network takes backbone service from a carrier's carrier, with the CE routers supporting MPLS: the CEs distribute only internal routes, the PEs distribute labels for the routes they give the CEs, and BGP connections between the sites carry the external routes. All six sites bind the carrier's carrier PE or the customer's CE routers and are excluded below. | | `10` | Multi-AS Backbones | 3 | walked | Multi-AS Backbones. The three inter-AS procedures: VRF-to-VRF connections at the AS border routers, EBGP redistribution of labeled VPN-IPv4 routes between ASBRs, and multi-hop EBGP between the source and destination ASes with labeled IPv4 /32 routes between the ASBRs. All three sites bind the SPs or their ASBRs and are excluded below. | | `11` | Accessing the Internet from a VPN | 3 | walked | Accessing the Internet from a VPN. Four ways a VPN site reaches the public Internet: a non-VRF interface to an ISP, a VRF interface falling back to the default forwarding table, non-VPN routes held in the VRF, and Internet routes carried in the VRF itself. All three sites bind the ISP or the PE that joins a VRF to the Internet forwarding table and are excluded below. | | `12` | Management VPNs | 0 | walked | Management VPNs. How an SP-managed CE is reached: address the PE/CE sub-interface out of the SP's space, attach the network management system to a PE over a VRF interface, and use two Route Targets so the management system and the CEs can talk without the CEs reaching each other. Indicative and no site. | | `13` | Security Considerations | 0 | walked | Security Considerations. The heading for sections 13.1 to 13.3. | | `13.1` | Data Plane | 4 | walked | Data Plane. States what data plane security means here, the three conditions it rests on, and the precise rule for discarding a labeled packet from a neighbor. It closes on MPLS-in-IP and MPLS-in-GRE tunneling per RFC 4023: sites 13.1:2 and 13.1:3 carry the two capitalised MUST-level obligations and are mapped below to RFC4364-13.1-1 and RFC4364-13.1-2, while site 13.1:1 points at RFC 4023's own security considerations and site 13.1:4 states the LAN-interface conditions, both excluded. | | `13.2` | Control Plane | 0 | walked | Control Plane. States that data plane security depends on control plane security, that neither BGP nor LDP connections should be made with untrusted peers, and that the routing protocol inside the SP's network should be secured in the same way. The declared row RFC4364-13.2-1 is read from its lowercase 'should' sentence, 'The TCP/IP MD5 authentication option [TCP-MD5] should be used with both these protocols', and is the unsourced id here. No site. | | `13.3` | Security of P and PE Devices | 0 | walked | Security of P and PE Devices. States that compromising the physical security of these devices can compromise data plane security, and that the usual steps are taken so Internet traffic cannot reconfigure them or mount a denial of service. Advisory prose with no site. | | `14` | Quality of Service | 0 | walked | Quality of Service. Records that L3 QoS applies to labeled packets through the experimental bits of the shim header or through ATM QoS, that RSVP-TE traffic engineering applies, and that an SP may apply intserv or diffserv to a VPN. No directive and no site. | | `15` | Scalability | 2 | walked | Scalability. Summarises what each component holds: P routers hold no VPN route, a PE holds routes only for the VPNs it attaches to, route reflectors and ASBRs can be partitioned among VPNs. Both of its sites state that something is NOT required and are excluded below. | | `16` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records the new Route Distinguisher Type Field registry with types 0, 1 and 2 defined here, the First Come First Served and IETF consensus ranges, and the change of SAFI 128 from private use to MPLS-labeled VPN address. Binds IANA, not a speaker. | | `17` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `18` | Contributors | 0 | skipped (acknowledgements) | Contributors. The postal and electronic addresses of the contributors listed for the document. | | `19` | not stated | 0 | skipped (references) | Normative References: RFC 4271, RFC 4760, RFC 4023, RFC 3107, RFC 3031, RFC 3032, RFC 5036, RFC 4360, RFC 2385, RFC 4456, RFC 2918, RFC 5291 and the ATM and Frame Relay label switching documents. | | `20` | Informative References | 1 | walked | Informative References. Because no numbered heading follows it, the derived span also carries the Authors' Addresses, the Full Copyright Statement, the Intellectual Property boilerplate and the RFC Editor funding note. Walked rather than skipped because the prose scan attributes one site here; that site is IPR boilerplate and is excluded below. Nothing in the span states an obligation on an implementation. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the customer's VPN site: each one contains one or more CE devices, hosts or routers, attached to the PE over an attachment circuit. It states what the deployment model is made of rather than directing a protocol speaker, and the role it names is one ze does not play. ze is not a BGP/MPLS L3VPN Provider Edge: its RFC 4364 surface is the control-plane VPN-IPv4/VPN-IPv6 (SAFI 128) NLRI codec of internal/component/bgp/plugins/nlri/vpn (ParseVPN decodes the Route Distinguisher, the label stack and the prefix, WriteTo and EncodeRoute build them, and the plugin registers both families in decode mode) over the Route Distinguisher type 0/1/2 layout of internal/core/bgp/nlri.RouteDistinguisher. It has no VRF, no PE/CE protocol and no MPLS-VPN forwarding path. | Each VPN site must contain one or more Customer Edge (CE) devices. | | `1.2:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the sentence states that something is NOT required. It records the administrative boundary the architecture keeps -- customers need no access to the PE or P routers for management, and the SP needs none to the CE devices -- and places no obligation on either party. | Customers are not required to access the PE or P routers for management purposes, nor is the SP required to access the CE devices for management purposes. | | `1.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the party administering addresses inside a VPN, the customer or the SP acting for it: two VPNs with no site in common may reuse the same addresses, but within one VPN each address is unambiguous. It is an addressing-plan constraint on a numbering authority, not behavior of a BGP speaker. Ze administers no VPN address plan; the RD that makes two identical prefixes distinct on the wire is decoded by internal/core/bgp/nlri.ParseRouteDistinguisher and carried opaquely. | Of course, within each VPN, each address must be unambiguous. | | `1.6:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the SP or customer that wants confidentiality: the section states that these methods do not themselves encrypt data or detect tampering, and that cryptographic measures are applied in addition if that is desired, pointing at [MPLS/BGP-IPsec]. The obligation falls on whoever deploys that additional layer, and ze provides no VPN data path over which to deploy it. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | If this is desired, cryptographic measures must be applied in addition. | | `2:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the prose scan matches the authorial cross-reference 'as we shall see in Section 3.2', not a directive. The sentence itself is a definition -- a CE device is always regarded as being in a single site, though a site may consist of several virtual sites -- and RFC 4364 has no RFC 2119 key-words section, so nothing in it gives a lowercase modal a level. | A CE device is always regarded as being in a single site (though as we shall see in Section 3.2, a site may consist of multiple "virtual sites"). | | `3.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE ingress classifier: on a packet from a CE the PE determines the attachment circuit it arrived on, because that determines the VRF used to forward it. ze is not a BGP/MPLS L3VPN Provider Edge: its RFC 4364 surface is the control-plane VPN-IPv4/VPN-IPv6 (SAFI 128) NLRI codec of internal/component/bgp/plugins/nlri/vpn (ParseVPN decodes the Route Distinguisher, the label stack and the prefix, WriteTo and EncodeRoute build them, and the plugin registers both families in decode mode) over the Route Distinguisher type 0/1/2 layout of internal/core/bgp/nlri.RouteDistinguisher. It has no VRF, no PE/CE protocol and no MPLS-VPN forwarding path, so it classifies no customer packet and selects no VRF. | When a PE router receives a packet from a CE device, it must determine the attachment circuit over which the packet arrived, as this determines in turn the VRF (or set of VRFs) that can be used for forwarding that packet. | | `3.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the SP's PE and the layer 2 access it controls: a customer must not be able to forge the attachment-circuit decision by writing the layer 2 header fields, the Frame Relay DLCI in the section's own example. Ze terminates no customer attachment circuit and makes no such decision. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | Although the PE's conclusion that a particular packet arrived on a particular attachment circuit may be partially determined by the packet's layer 2 header, it must be impossible for a customer, by writing the header fields, to fool the SP into thinking that a packet that was received over one attachment circuit really arrived over a different one. | | `3.2:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same SP-controlled attachment circuit as site 3.2:2: the layer 2 field is set to the value the SP specified or the packet does not reach the PE. It constrains the provisioning of the access link, and ze provisions none. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | Rather, it must be set to a value specified by the SP, or else the packet cannot arrive at the PE router. | | `3.2:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the customer HOST that sits in several virtual sites: it decides, for each packet, which virtual site the packet belongs to, by sending on different VLANs or out different interfaces. Ze is neither that host nor the PE that reads its VLAN tag. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | If it is desired to have a particular host be in multiple virtual sites, then that host must determine, for each packet, which virtual site the packet is associated with. | | `4:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE that maintains VRFs. The sentence is the authors stating the design goal the address family then meets ('Further, we must ensure that POLICY is used to determine which packets get sent on which routes'), and its operative constraint is on the VRF: of several routes BGP installs, only one appears in any particular VRF. Ze holds no VRF, so nothing applies the constraint. Its VPN-IPv4 routes are decoded and stored as raw NLRI, never installed into a per-VPN forwarding table. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | Further, we must ensure that POLICY is used to determine which packets get sent on which routes; given that several such routes are installed by BGP, only one such must appear in any particular VRF. | | `4.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the assigning authority for a type 0 Route Distinguisher, the enterprise or Service Provider that administers its own RD numbering space: the Administrator subfield carries an Autonomous System number they hold. Ze assigns no RD. It implements the LAYOUT this list defines -- internal/core/bgp/nlri.ParseRouteDistinguisher reads the 2-byte type and keeps the 6-byte value, RouteDistinguisher.String renders type 0 as ASN:number -- and the summary carries that layout in its Route Distinguisher Format tables. What this sentence adds beyond the layout is the numbering constraint, which binds the assigner. | The Administrator subfield must contain an Autonomous System number. | | `4.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the numbering authority and the enterprise it assigns to: an ASN from the public space in a type 0 RD has been assigned by the appropriate authority, and using a private-space value is strongly discouraged. Nothing here directs an encoder or a decoder, and ze never assigns an ASN. The producer that would act as it if ze did is the RD codec, `internal/core/bgp/nlri/rd.go`: it parses and formats the eight octets and assigns no number to anybody. | If this ASN is from the public ASN space, it must have been assigned by the appropriate authority (use of ASN values from the private ASN space is strongly discouraged). | | `4.2:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The type 1 counterpart of site 4.2:1, binding the same assigning authority: the Administrator subfield carries an IP address held by the assigner. Ze decodes the six value octets of a type 1 RD without judging whose address they are. The producer that would act as it if ze did is the RD codec, `internal/core/bgp/nlri/rd.go`: it parses and formats the eight octets and assigns no number to anybody. | The Administrator subfield must contain an IP address. | | `4.2:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The type 1 counterpart of site 4.2:2: a public IP address in the Administrator subfield has been assigned by an appropriate authority, and private space is strongly discouraged. It binds the address registry and the assignee, neither of which ze is. The producer that would act as it if ze did is the RD codec, `internal/core/bgp/nlri/rd.go`: it parses and formats the eight octets and assigns no number to anybody. | If this IP address is from the public IP address space, it must have been assigned by an appropriate authority (use of addresses from the private IP address space is strongly discouraged). | | `4.2:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The type 2 counterpart of site 4.2:1: the Administrator subfield carries a 4-byte Autonomous System number, assigned to whoever administers the RD. Ze decodes the field and assigns no number. The producer that would act as it if ze did is the RD codec, `internal/core/bgp/nlri/rd.go`: it parses and formats the eight octets and assigns no number to anybody. | The Administrator subfield must contain a 4-byte Autonomous System number [BGP-AS4]. | | `4.2:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The type 2 counterpart of site 4.2:2, binding the ASN registry and its assignee: a public 4-byte ASN has been assigned by the appropriate authority, and private-space values are strongly discouraged. The producer that would act as it if ze did is the RD codec, `internal/core/bgp/nlri/rd.go`: it parses and formats the eight octets and assigns no number to anybody. | If this ASN is from the public ASN space, it must have been assigned by the appropriate authority (use of ASN values from the private ASN space is strongly discouraged). | | `4.3.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the VPN route distribution system: a route carrying Route Target T reaches every PE that has a VRF associated with T, where it becomes eligible for installation in those VRFs. The obligation is stated over VRFs and their Import Targets, and ze is not a BGP/MPLS L3VPN Provider Edge: its RFC 4364 surface is the control-plane VPN-IPv4/VPN-IPv6 (SAFI 128) NLRI codec of internal/component/bgp/plugins/nlri/vpn (ParseVPN decodes the Route Distinguisher, the label stack and the prefix, WriteTo and EncodeRoute build them, and the plugin registers both families in decode mode) over the Route Distinguisher type 0/1/2 layout of internal/core/bgp/nlri.RouteDistinguisher. It has no VRF, no PE/CE protocol and no MPLS-VPN forwarding path, so no PE-with-VRF exists here for the rule to reach. A VPN-IPv4 route ze receives is held as an opaque NLRI, with no Route-Target-driven import. | Any route associated with Route Target T must be distributed to every PE router that has a VRF associated with Route Target T. | | `4.3.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE that assigned the label: when the labeled route is an aggregate, packets arriving from the backbone with that label have their destination addresses looked up in a VRF. It describes a PE forwarding decision, and ze holds no VRF and forwards no labeled packet. The MPLS entries ze does program are RSVP-TE and LDP transport labels, through fibkernel.handleMPLSEntry (internal/plugins/fib/kernel/mpls.go), with no VPN label behind them. | If R is an aggregate of a set of routes in the VRF, the PE will know that packets from the backbone that arrive with this label must have their destination addresses looked up in a VRF. | | `4.3.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same PE, on the receive side: looking the label up in its Label Information Base tells it which VRF to use. Ze keeps no Label Information Base for VPN labels and no VRF to select. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | When the PE looks up the label in its Label Information Base, it learns which VRF must be used. | | `4.3.2:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the egress PE under the per-VRF label strategy: on a packet carrying the VRF's single label it looks the destination address up in that VRF to find the egress attachment circuit and the data link encapsulation. Ze is not an egress PE and terminates no attachment circuit. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | Then when the egress PE receives a packet with that label, it must look up the packet's IP destination address in that VRF (the packet's "egress VRF"), in order to determine the packet's egress attachment circuit and the corresponding data link encapsulation. | | `4.3.2:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE managing its VRFs' Import Targets: after a VPN Join adds a new Import Target, the PE acquires the routes it previously discarded, which the section says can be done with the refresh mechanism of RFC 2918. Ze holds no VRF and no Import Target set, so no Join can occur. It does implement the mechanism the sentence names, ROUTE-REFRESH per RFC 2918 (rfc/short/rfc2918.md), which is a separate obligation and is discharged there. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | If a new Import Target is later added to one of the PE's VRFs (a "VPN Join" operation), it must then acquire the routes it may previously have discarded. | | `4.3.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the VPN-partitioning route reflector of method 2: one that tracks the set of Route Targets carried by its clients' routes and derives its inbound route filtering from that set, and so must issue BGP Refresh to the other route reflectors when the set gains a Route Target and it does not use ORFs. Ze's route reflector (internal/component/bgp/plugins/rr) reflects routes without maintaining a VPN Route Target set, so there is no set whose change triggers this. As with site 4.3.2:6, ze does implement the ROUTE-REFRESH message the sentence names. | If the route reflector doesn't use ORFs, and a new Route Target is added to the set, the route reflector, after changing its inbound route filtering, must issue BGP Refresh to other route reflectors. | | `4.3.4:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 4760, which this document cites as [BGP-MP] and which the sentence itself defers to: 'This is done as specified in [BGP-MP], by using capability code 1 (multiprotocol BGP), with an AFI of 1 and an SAFI of 128.' RFC 4760 section 8 is what binds a speaker to advertise the Multiprotocol capability for an AFI/SAFI pair, and rfc/short/rfc4760.md carries it as RFC4760-8-1 at [MUST], where ze implements it. This sentence applies that mechanism to AFI 1 with SAFI 128 and adds no obligation of its own. | In order for two BGP speakers to exchange labeled VPN-IPv4 NLRI, they must use BGP Capabilities Advertisement to ensure that they both are capable of properly processing such NLRI. | | `5:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the modal states what NECESSARILY FOLLOWS, not what anyone owes. Given that the packet's next hop is not reached over a VRF attachment circuit of this PE, the packet travels at least one hop through the backbone, which is why the next sentences give it a BGP Next Hop and a VPN route label. The forwarding obligation itself is site 5:2. | If the packet's next hop is NOT reached through a VRF attachment circuit, then the packet must travel at least one hop through the backbone. | | `5:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ingress PE forwarding path: having turned the IP packet into an MPLS packet carrying the VPN route label, it tunnels the packet to the BGP Next Hop. ze is not a BGP/MPLS L3VPN Provider Edge: its RFC 4364 surface is the control-plane VPN-IPv4/VPN-IPv6 (SAFI 128) NLRI codec of internal/component/bgp/plugins/nlri/vpn (ParseVPN decodes the Route Distinguisher, the label stack and the prefix, WriteTo and EncodeRoute build them, and the plugin registers both families in decode mode) over the Route Distinguisher type 0/1/2 layout of internal/core/bgp/nlri.RouteDistinguisher. It has no VRF, no PE/CE protocol and no MPLS-VPN forwarding path, so it imposes no VPN route label and tunnels no customer packet. | The packet must then be tunneled to the BGP Next Hop. | | `5:3` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Restates the LDP interoperability obligation of section 4.3.2 in the forwarding section: 'To ensure interoperability among different implementations, it is required to support LDP for setting up the label switched paths across the backbone.' Site 4.3.2:4 maps that obligation to RFC4364-4.3.2-1, and this sentence adds only that other methods of setting up the label switched paths remain possible. | To ensure interoperability among different implementations, it is required to support LDP for setting up the label switched paths across the backbone. | | `5:4` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the modal sits in a relative clause naming a KIND of tunnel, and the sentence is a compatibility statement. Having listed four things the specification DOES NOT require of its tunnels, it adds that it is nonetheless compatible with point-to-point tunnels 'that must be explicitly configured and/or signaled'. Nothing is required of an implementation. | Of course, this specification is compatible with the use of point- to-point tunnels that must be explicitly configured and/or signaled, and in some situations there may be reasons for using such tunnels. | | `6:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the backbone border router when the tunneling technology is not MPLS: it filters so an MPLS-in-IP or MPLS-in-GRE packet enters the backbone only when its IP destination address will send it back out. Ze operates no VPN backbone edge and accepts no MPLS-in-IP or MPLS-in-GRE packet; it has no MPLS-VPN forwarding path at all. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | If MPLS is not being used as the tunneling technology, then filtering must be done to ensure that an MPLS-in-IP or MPLS-in-GRE packet can be accepted into the backbone only if the packet's IP destination address will cause it to be sent outside the backbone. | | `7:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE/CE RIP session: when RIP is configured in the CE, prefixes the CE learned from the PE are never advertised back to it. Ze implements no PE/CE routing protocol and runs no RIP at all, so it is neither end of this session. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | When RIP is configured in the CE, care must be taken to ensure that address prefixes from other sites (i.e., address prefixes learned by the CE router from the PE router) are never advertised to the PE. | | `7:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same PE/CE route distribution, stated precisely: a route the PE derived from a VPN-IPv4 route and gave to a CE is not distributed back from that site to a PE unless that PE maps it to a VPN-IPv4 route with a different RD. Ze converts no VPN-IPv4 route into a CE route and holds no VRF in which the loop could form. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | More precisely: if a PE router, say, PE1, receives a VPN-IPv4 route R1, and as a result distributes an IPv4 route R2 to a CE, then R2 must not be distributed back from that CE's site to a PE router, say, PE2, (where PE1 and PE2 may be the same router or different routers), unless PE2 maps R2 to a VPN-IPv4 route that is different than (i.e., contains a different RD than) R1. 3. | | `7:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE that is an OSPF peer of CE routers in distinct VPNs: it runs multiple instances of OSPF. Ze implements OSPF (internal/plugins/ospf), but not as a PE/CE protocol: it has no VRF, no per-VPN OSPF instance and no VPN-IPv4 redistribution between OSPF and BGP, so it never plays the PE end of a PE/CE OSPF session. | If a PE router is an OSPF peer of CE routers that are in distinct VPNs, the PE must of course be running multiple instances of OSPF. | | `7:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE redistributing a route learned from a site: before it does, it assigns a Route Target attribute to the route, and it may assign a Site of Origin attribute. Ze performs no such redistribution, because there is no VRF to learn a site's routes into. Attaching a Route Target extended community to a route ze originates is operator-driven configuration (internal/component/bgp/config, the extended-community parser), not a PE's conversion of a CE route. | Before a PE can redistribute a VPN-IPv4 route learned from a site, it must assign a Route Target attribute (see Section 4.3.1) to the route, and it may assign a Site of Origin attribute to the route. | | `8:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE distributing routes to a CE: a route whose Site of Origin attribute identifies a site is never redistributed to any CE at that site. Ze distributes no route to a CE and holds no VRF from which to do so. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | (For example, if a particular PE/CE protocol has "split horizon", certain routes in the VRF cannot be redistributed back to the CE.) We add one more restriction on the distribution of routes from PE to CE: if a route's Site of Origin attribute identifies a particular site, that route must never be redistributed to any CE at that site. | | `9:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the carriers' carrier PE distributing labels to its CE routers: it does not give the same label to two different CEs unless they share exactly the same set of VRFs, or it keeps a separate Incoming Label Map per CE. Ze distributes no per-CE labels and keeps no Incoming Label Map. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | The PE must not distribute the same label to two different CEs unless one of the following conditions holds: | | `9:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same carriers' carrier PE on the data path: on a labeled packet from a CE it verifies that the top label is one it distributed to that CE. Ze receives no labeled customer packet; MPLS packets are handled by the kernel AF_MPLS datapath or by VPP, which ze programs but does not sit in. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | Further, when the PE receives a labeled packet from a CE, it must verify that the top label is one that was distributed to that CE. | | `9:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the CE routers of a carriers' carrier VPN: all the external routes are known to them, because they are the routers that resolve a packet's destination to an internal BGP next hop and label it. Ze is not a CE router in such a VPN. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | - All the external routes must be known to the CE routers. | | `9:4` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the sentence RELAXES a requirement rather than stating one. When every router at a VPN site supports MPLS, 'it is no longer required that the CE routers know all the external routes', and the next sentence says what is required instead, which is site 9:5. | If, on the other hand, all the routers at a particular VPN site support MPLS, then it is no longer required that the CE routers know all the external routes. | | `9:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds whichever routers at the customer's site impose the label stack on a hitherto unlabeled packet, and the label switched path from them to their BGP peers at other sites. Ze is not a router of a carriers' carrier customer site and imposes no VPN label stack. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | All that is required is that the external routes be known to whatever routers are responsible for putting the label stack on a hitherto unlabeled packet and that there be label switched path that leads from those routers to their BGP peers at other sites. | | `9:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the CE router of the carriers' carrier customer in that same case: for each internal route it distributes to a PE router it also distributes a label. Ze plays no CE role and distributes no labels with its routes on any PE/CE session. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | In this case, for each internal route that a CE router distributes to a PE router, it must also distribute a label. | | `10:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the Service Providers whose ASes the label switched path crosses: the appropriate trust relationships exist between and among them. It is an inter-provider business arrangement, and the sentence directs no protocol behavior. Ze is a daemon, not a party to an SP agreement. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | Hence the appropriate trust relationships must exist between and among the set of ASes along the path. | | `10:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same set of Service Providers: they agree which border routers receive routes with which Route Targets. It is an inter-provider agreement rather than an implementation obligation. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | Also, there must be agreement among the set of SPs as to which border routers need to receive routes with which Route Targets. | | `10:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the inter-AS VPN ASBR of method (c): it maintains labeled IPv4 /32 routes to the PE routers of its own AS and distributes them by EBGP, which builds the label switched path between the ingress and egress PEs. Ze is not a VPN ASBR: it originates no labeled /32 routes for PE loopbacks and takes no part in inter-provider VPN label stacking. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | An ASBR must maintain labeled IPv4 /32 routes to the PE routers within its AS. | | `11:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the ISP that gives a VPN site its Internet access: it distributes to the Internet the routes leading to the addresses inside the VPN, which the section says is completely independent of the route distribution procedures of this document. Ze provides no Internet gateway for a VPN and holds no VPN address space to announce. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | In order to properly handle traffic from the Internet, the ISP must distribute, to the Internet, routes leading to addresses that are within the VPN. | | `11:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the PE offering VRF Internet access: some of the VRF's routes are exported into the Internet forwarding table so traffic can flow natively from the Internet to the VRF interface. Ze holds neither a VRF nor a per-PE default forwarding table to export between. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | In order for traffic to flow natively in the opposite direction (from Internet to VRF interface), some of the routes from the VRF must be exported to the Internet forwarding table. | | `11:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same PE and the operator configuring it: any route exported from a VRF to the Internet forwarding table corresponds to a globally unique address. Ze exports nothing between a VRF and an Internet table, having neither. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | Needless to say, any such routes must correspond to globally unique addresses. | | `13.1:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 4023, 'Encapsulating MPLS in IP or Generic Routing Encapsulation (GRE)', which this document cites as [MPLS-in-IP-GRE] and which the sentence points at by section: 'the security considerations described in Section 8 of that document must be fully understood'. It directs a reader to another document's analysis and states no requirement of its own. The two obligations this section does state about those tunnels are sites 13.1:2 and 13.1:3, mapped below. | If it is desired to use such tunnels to carry VPN packets, then the security considerations described in Section 8 of that document must be fully understood. | | `13.1:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the SP deploying a PE whose LAN interface carries several CE routers: either every CE on the LAN belongs to one VPN, or a trusted and secured LAN switch splits it into per-VPN VLANs and tags each packet before it reaches the PE. It constrains the access network and the switch in it, and ze operates neither. The producer that would act as it if ze did is the whole of ze's RFC 4364 code, the VPN NLRI codec `internal/component/bgp/plugins/nlri/vpn/vpn.go`, which decodes and encodes the route and nothing else: the tree holds no VRF, no attachment circuit, no PE-to-CE label distribution and no VPN forwarding path for the role to inhabit. | In the case where a number of CE routers attach to a PE router via a LAN interface, to ensure proper security, one of the following conditions must hold: | | `15:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the sentence states that something is NOT required. It is the scalability summary's conclusion about route reflectors, that partitioning them among VPNs leaves no single one holding routes for all VPNs, and it directs nobody. | Thus, no single route reflector is required to maintain routes for all VPNs. | | `15:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the same negative form for ASBRs. Partitioning the ASBRs that maintain and distribute VPN-IPv4 routes leaves no single ASBR holding routes for all the inter-provider VPNs, and if multi-hop EBGP is used the ASBRs need not maintain those routes at all. | For inter-provider VPNs, if the ASBRs maintain and distribute VPN- IPv4 routes, then the ASBRs can be partitioned among VPNs in a similar manner, with the result that no single ASBR is required to maintain routes for all the inter-provider VPNs. | | `20:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: this is the IETF's standard Intellectual Property boilerplate, which the derived section span carries into section 20 after the Informative References. The sentence invites interested parties to disclose patent rights to the IETF, and its 'may be required to implement this standard' describes what a patent might cover rather than requiring anything of an implementation. | The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. | ## Superseded No document obsoletes RFC 4364, so its obligations are stated where they were written. --- ### Page: RFC 4456 - BGP Route Reflection: An Alternative to Full Mesh Internal BGP (IBGP) https://ze-software.net/quality/rfc-compliance/rfc4456/ # RFC 4456 - BGP Route Reflection: An Alternative to Full Mesh Internal BGP (IBGP) Supported. Every requirement this repository extracted from RFC 4456, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 83.3% | 5 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 16.7% | 1 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 20 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 9 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 9 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 20 | | Tagged units | 20 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4456.md` | | Requirement shard | `rfc/requirements/rfc4456.md` | | RFC text | `rfc/full/rfc4456.txt` | ## Enrolment Enrolled: BGP Route Reflection: six MUST-level requirements over the reactor RS-fast-path route-reflection code (internal/component/bgp/reactor/forward_rs.go, filter_delta_handlers.go). Five have both polarities: RFC4456-8-1 (set ORIGINATOR_ID if absent) and RFC4456-8-4 (preserve if present) via originatorIDHandler; RFC4456-8-2 (prepend CLUSTER_ID) via clusterListHandler AttrModPrepend; RFC4456-8-3 (ORIGINATOR_ID not fabricated -- it is the originator's id, not the RR's, and a present one is not replaced); RFC4456-x-2 (a non-client route is not reflected to a non-client but is to a client). RFC4456-x-1 (must not modify NEXT_HOP/AS_PATH/LOCAL_PREF/MED) is {single-polarity: positive}: on reflection only ORIGINATOR_ID/CLUSTER_LIST ops are emitted, so those four ride through unchanged. Tests: forward_rr_test.go (TestReactorForwardRRInjects/PreservesOriginator/NonClientRule). The 8-5/8-6 SHOULDs are not gated. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** ORIGINATOR_ID, CLUSTER_LIST, route-reflector plugin behavior, cluster checks. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 5 | one part of the gated population | | Annotated instead of tested | 1 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (5):** [`RFC4456-8-1`](#rfc4456-8-1), [`RFC4456-8-2`](#rfc4456-8-2), [`RFC4456-8-3`](#rfc4456-8-3), [`RFC4456-8-4`](#rfc4456-8-4), [`RFC4456-x-2`](#rfc4456-x-2) **Annotated instead of tested (1):** [`RFC4456-x-1`](#rfc4456-x-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4456-x-1` | An RR MUST NOT modify the NEXT_HOP, AS_PATH, LOCAL_PREF, or MED attributes of a reflected route (Route Reflection Rules) | MUST | x | **positive:** `unit/verify` [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L157). **negative:** no negative test. **{single-polarity}:** on reflection the RR forwarding path emits only ORIGINATOR_ID and CLUSTER_LIST modifications (internal/component/bgp/reactor/forward_rs.go:337-339) and never a NEXT_HOP/AS_PATH/LOCAL_PREF/MED op, so those four are always carried through in the verbatim wire; there is no RR scenario that modifies them to assert as a negative. The positive is proven byte-identical in TestReactorForwardRRInjects | | `RFC4456-8-1` | When an RR reflects a route from a client to a non-client or to another client, it MUST set the ORIGINATOR_ID to the BGP Identifier of the originator if not already present (§8) | MUST | 8 - Avoiding Routing Information Loops | **positive:** `unit/verify` [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L149). **negative:** `unit/verify` [`TestForwardReflectionLeavesAWithdrawalUntouched`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_build_withdraw_shape_test.go#L436). **negative:** `unit/verify` [`TestReactorForwardRRPreservesOriginator`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L180). **positive:** `interop/nightly` [`checkReflectorWithdrawal`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_special.go#L137). **negative:** `interop/nightly` [`checkReflectorWithdrawal`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_special.go#L138) | | `RFC4456-8-2` | When an RR reflects a route, it MUST prepend its local CLUSTER_ID to the CLUSTER_LIST (creating one if absent) (§8) | MUST | 8 - Avoiding Routing Information Loops | **positive:** `unit/verify` [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L151). **negative:** `unit/verify` [`TestReactorForwardRRPreservesOriginator`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L184). **positive:** `interop/nightly` [`checkReflectorWithdrawal`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_special.go#L139). **negative:** `interop/nightly` [`checkReflectorWithdrawal`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_special.go#L140) | | `RFC4456-8-3` | ORIGINATOR_ID MUST NOT be created by a speaker that did not originate the route within the local AS (§8) | MUST NOT | 8 - Avoiding Routing Information Loops | **positive:** `unit/verify` [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L153). **negative:** `unit/verify` [`TestReactorForwardRRPreservesOriginator`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L182) | | `RFC4456-8-4` | ORIGINATOR_ID value MUST be preserved unchanged through the reflection chain (§8) | MUST | 8 - Avoiding Routing Information Loops | **positive:** `unit/verify` [`TestReactorForwardRRPreservesOriginator`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L178). **negative:** `unit/verify` [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L155) | | `RFC4456-x-2` | A non-client peer route MUST NOT be reflected to other non-client peers (Route Reflection Rules) | MUST | x | **positive:** `unit/verify` [`TestReactorForwardRRNonClientRule`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L211). **negative:** `unit/verify` [`TestReactorForwardRRNonClientRule`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L213) | | `RFC4456-8-5` | A router that recognizes ORIGINATOR_ID SHOULD ignore a route received with its own BGP Identifier as the ORIGINATOR_ID (§8) | SHOULD | 8 - Avoiding Routing Information Loops | **positive:** no positive test. **negative:** no negative test | | `RFC4456-8-6` | If the local CLUSTER_ID is found in the CLUSTER_LIST, the advertisement SHOULD be ignored (§8) | SHOULD | 8 - Avoiding Routing Information Loops | **positive:** no positive test. **negative:** no negative test | | `RFC4456-9-1` | A BGP Speaker SHOULD prefer a route with the shorter CLUSTER_LIST length; the length is zero when the route carries no CLUSTER_LIST attribute, and the rule is inserted between RFC 4271 Section 9.1.2.2 Steps f) and g) (§9) | SHOULD | 9 - Impact on Route Selection | **positive:** `unit/verify` [`TestBestPath_ClusterListLength`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L1207). **positive:** `unit/verify` [`TestClusterListEntries`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L1302). **negative:** `unit/verify` [`TestBestPath_ClusterListLength`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L1213). **positive:** `interop/nightly` [`checkClusterListLengthTieBreak`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc4456.go#L47) | ## Gaps and untested MUSTs RFC 4456 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4456-x-1`](#rfc4456-x-1) An RR MUST NOT modify the NEXT_HOP, AS_PATH, LOCAL_PREF, or MED attributes of a reflected route (Route Reflection Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L157) | unit/verify | unproven | ### [`RFC4456-8-1`](#rfc4456-8-1) When an RR reflects a route from a client to a non-client or to another client, it MUST set the ORIGINATOR_ID to the BGP Identifier of the originator if not already present (§8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestForwardReflectionLeavesAWithdrawalUntouched`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_build_withdraw_shape_test.go#L436) | unit/verify | unproven | | negative | [`TestReactorForwardRRPreservesOriginator`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L180) | unit/verify | unproven | | negative | [`checkReflectorWithdrawal`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_special.go#L138) | interop/nightly | unproven | | positive | [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L149) | unit/verify | unproven | | positive | [`checkReflectorWithdrawal`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_special.go#L137) | interop/nightly | unproven | ### [`RFC4456-8-2`](#rfc4456-8-2) When an RR reflects a route, it MUST prepend its local CLUSTER_ID to the CLUSTER_LIST (creating one if absent) (§8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReactorForwardRRPreservesOriginator`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L184) | unit/verify | unproven | | negative | [`checkReflectorWithdrawal`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_special.go#L140) | interop/nightly | unproven | | positive | [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L151) | unit/verify | unproven | | positive | [`checkReflectorWithdrawal`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_special.go#L139) | interop/nightly | unproven | ### [`RFC4456-8-3`](#rfc4456-8-3) ORIGINATOR_ID MUST NOT be created by a speaker that did not originate the route within the local AS (§8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReactorForwardRRPreservesOriginator`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L182) | unit/verify | unproven | | positive | [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L153) | unit/verify | unproven | ### [`RFC4456-8-4`](#rfc4456-8-4) ORIGINATOR_ID value MUST be preserved unchanged through the reflection chain (§8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReactorForwardRRInjects`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L155) | unit/verify | unproven | | positive | [`TestReactorForwardRRPreservesOriginator`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L178) | unit/verify | unproven | ### [`RFC4456-x-2`](#rfc4456-x-2) A non-client peer route MUST NOT be reflected to other non-client peers (Route Reflection Rules) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestReactorForwardRRNonClientRule`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L213) | unit/verify | unproven | | positive | [`TestReactorForwardRRNonClientRule`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L211) | unit/verify | unproven | ### [`RFC4456-9-1`](#rfc4456-9-1) A BGP Speaker SHOULD prefer a route with the shorter CLUSTER_LIST length; the length is zero when the route carries no CLUSTER_LIST attribute, and the rule is inserted between RFC 4271 Section 9.1.2.2 Steps f) and g) (§9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBestPath_ClusterListLength`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L1213) | unit/verify | unproven | | positive | [`TestBestPath_ClusterListLength`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L1207) | unit/verify | unproven | | positive | [`TestClusterListEntries`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L1302) | unit/verify | unproven | | positive | [`checkClusterListLengthTieBreak`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc4456.go#L47) | interop/nightly | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase 5, rfc4456 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc4456.txt | | Source fingerprint | efe974c45b13bec4 | | Record | rfc/extraction/rfc4456.json | | Mapped sentences | 2 | | Declined as scope | 10 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 1 | skipped (front-matter) | Title block, Status of This Memo, Copyright Notice, Abstract and Table of Contents. The Abstract states the scaling problem this document alleviates and announces that it obsoletes RFC 2796 and RFC 1966. Its one site is the lowercase restatement of the existing full-mesh model, excluded below. | | `1` | Introduction | 1 | walked | Introduction. Indicative prose: n*(n-1)/2 iBGP sessions do not scale, other proposals exist, this document proposes route reflection, and it adds two new optional non-transitive attributes to prevent loops. The one site repeats the Abstract's description of the existing model and directs no speaker. | | `2` | Specification of Requirements | 0 | walked | Specification of Requirements. The RFC 2119 key-words paragraph, which lists the key words in upper case. It binds no speaker, and it is what makes every lowercase 'must' in sections 1, 3, 4, 5 and 6 a description of the scheme rather than a directive. | | `3` | Design Criteria | 3 | walked | Design Criteria. Three criteria route reflection 'was designed to satisfy': simplicity, easy transition and compatibility with noncompliant iBGP peers. Three sites, all excluded below: they measure a design against a goal and name no act a speaker performs on the wire. | | `4` | Route Reflection | 1 | walked | Route Reflection. The Figure 1 and Figure 2 worked example that motivates the scheme, ending in 'The route reflection scheme is based upon this basic principle'. Its one site describes what RTR-A does under the EXISTING BGP model, so it is excluded below. | | `5` | Terminology and Concepts | 1 | walked | Terminology and Concepts. Defines route reflection, route reflector, reflected route, client and non-client peers, and cluster, with Figure 3. Its one site is the topology constraint that non-clients stay fully meshed, which binds the operator and is excluded below. | | `6` | Operation | 2 | walked | Operation. The distribution rule an RR applies after it selects the best path, in two cases: a route from a non-client is reflected to all clients, and a route from a client is reflected to all non-clients and to the other clients. Site 6:1 is the sentence that introduces both cases and is mapped below to RFC4456-x-2, the gated row rfc/short/rfc4456.md derives from its Route Reflection Rules table. The remainder of the section is indicative: an AS can have many RRs, an RR treats other RRs as ordinary internal speakers, and conventional speakers that do not understand route reflection can coexist in either group, which is a migration statement and not a directive. | | `7` | Redundant RRs | 0 | walked | Redundant RRs. Value and configuration statement: a single-RR cluster is identified by the BGP Identifier of the RR, and to remove that single point of failure all RRs in one cluster can be configured with a 4-byte CLUSTER_ID so that an RR can discard routes from other RRs in the same cluster. It is permissive ('can be configured') and states no obligation; the discard itself is section 8's SHOULD, declared as RFC4456-8-6. Ze reads the configured cluster-id and falls back to the Router ID for exactly this default (internal/component/bgp/reactor/filter.LoopIngress). | | `8` | Avoiding Routing Information Loops | 2 | walked | Avoiding Routing Information Loops. Defines ORIGINATOR_ID (optional non-transitive, type code 9, 4 bytes) and CLUSTER_LIST (optional non-transitive, type code 10, a sequence of CLUSTER_ID values). Its two capitalised MUSTs are the CLUSTER_LIST prepend and the create-if-empty clause, mapped below. Five further obligations are stated in shapes a MUST-level site scan cannot see, so they are the unsourced ids here: the attribute 'will be created by an RR in reflecting a route' (RFC4456-8-1) and 'will carry the BGP Identifier of the originator of the route in the local AS' (RFC4456-8-4, the value preserved through the chain), 'A BGP speaker SHOULD NOT create an ORIGINATOR_ID attribute if one already exists' (RFC4456-8-3), 'A router that recognizes the ORIGINATOR_ID attribute SHOULD ignore a route received with its BGP Identifier as the ORIGINATOR_ID' (RFC4456-8-5), and 'If the local CLUSTER_ID is found in the CLUSTER_LIST, the advertisement received SHOULD be ignored' (RFC4456-8-6). rfc/short/rfc4456.md declares RFC4456-8-3 at MUST NOT where the source says SHOULD NOT: the summary is STRICTER than the source, which conformance permits, and Ze meets the stricter reading. | | `9` | Impact on Route Selection | 0 | walked | Impact on Route Selection. Two modifications to the RFC 4271 tie-breaking rules, both SHOULD, neither visible to a MUST-level site scan. Ze implements both. The first, 'If a route carries the ORIGINATOR_ID attribute, then in Step f) the ORIGINATOR_ID SHOULD be treated as the BGP Identifier of the BGP speaker that has advertised the route', is the OriginatorIP field of rib.Candidate, filled from the ORIGINATOR_ID attribute and falling back to the peer's Router ID (the candidate build in rib_commands.go), compared at the Router ID step of rib.comparePair. It has no declared id. The second, 'a BGP Speaker SHOULD prefer a route with the shorter CLUSTER_LIST length', was NOT implemented when this walk was performed and was reported to the owner rather than recorded as satisfied. He ruled it be implemented unconditionally, without a configuration option, and it landed the same day: Candidate.ClusterListEntries, filled beside OriginatorIP, compared between the Router ID and peer address steps of both rib.comparePair and rib.comparePairWithReason, and declared as RFC4456-9-1 [SHOULD] in rfc/short/rfc4456.md with tests in both polarities. Corrected 2026-08-31: this reason previously said Ze did not implement it, which was true at sign-off and false within hours. The exclusion count is unchanged by the correction, so no resign-reason is owed. | | `10` | Implementation Considerations | 0 | walked | Implementation Considerations. Two directives, both invisible to a MUST-level site scan. 'Care should be taken to make sure that none of the BGP path attributes defined above can be modified through configuration when exchanging internal routing information between RRs and Clients and Non-Clients' constrains what configuration may offer, and 'when a RR reflects a route, it SHOULD NOT modify the following path attributes: NEXT_HOP, AS_PATH, LOCAL_PREF, and MED' is the source of RFC4456-x-1, the unsourced id recorded here. rfc/short/rfc4456.md declares that row at MUST where the source says SHOULD NOT, so the summary is stricter than the source and Ze meets the stricter reading. | | `11` | Configuration and Deployment Considerations | 0 | walked | Configuration and Deployment Considerations. Operator guidance throughout: a client cannot identify itself dynamically so it is configured by hand, incomparable MEDs and differing IGP metrics can make reflection select a different route than a full mesh would, and three ways to avoid that (local preference set at the border router, distinct AS-path lengths, community-based policy), then POP-based topology advice. Every sentence directs the person designing the reflection topology, in lowercase 'may' and 'should', and none directs a speaker. | | `12` | Security Considerations | 0 | walked | Security Considerations. One sentence: this extension to BGP does not change the underlying security issues inherent in the existing iBGP. No countermeasure is directed at a speaker. | | `13` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `14` | References | 0 | skipped (references) | References. The heading over the two reference lists below it. | | `14.1` | Normative References: RFC 4271 | 0 | skipped (references) | Normative References: RFC 4271. | | `14.2` | not stated | 1 | walked | Informative References (RFC 4223, RFC 3065, RFC 1966, RFC 2385, RFC 2796 and RFC 2119), and, because no numbered heading follows it, the whole tail of the document: Appendix A, Appendix B, Authors' Addresses, the Full Copyright Statement, the Intellectual Property notice and the RFC Editor funding acknowledgement. The section is recorded as walked rather than skipped because that span holds prose beyond a reference list. Appendix A and Appendix B are non-normative comparisons with RFC 2796 and RFC 1966 that record what changed, including that the CLUSTER_ID addition moved from 'append' to 'prepend' to match deployed code, which is stated as an obligation by section 8 and captured there. The one site is the Intellectual Property notice, excluded below. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `front:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Abstract. Lowercase 'must', twice, describing the property of the EXISTING BGP model that creates the scaling problem: speakers are typically fully meshed, so external routing information is re-distributed to every other router in the AS. Section 2 lists the key words in upper case, so this is not one of them, and the sentence states no obligation this document creates. | Typically, all BGP speakers within a single AS must be fully meshed so that any external routing information must be re-distributed to all other routers within that Autonomous System (AS). | | `1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Introduction. The same description of the existing full-mesh model as the Abstract, in the body text. The paragraph's next sentence draws the consequence ('This "full mesh" requirement clearly does not scale'), which is the problem statement this document answers rather than a directive it issues. | Typically, all BGP speakers within a single AS must be fully meshed and any external routing information must be re-distributed to all other routers within that AS. | | `3:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Design Criteria, bullet 'Simplicity'. The section opens 'Route reflection was designed to satisfy the following criteria', so the sentence measures a design against a goal in the past tense. Being simple to configure and easy to understand is not an act a speaker performs, and no code path can carry it. | Any alternative must be simple to configure and easy to understand. | | `3:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Design Criteria, bullet 'Easy Transition'. Same frame as 3:1: a criterion the design had to meet, namely that an operator can transition from a full mesh without changing topology or AS. The sentence then contrasts the technique of RFC 3065 as unfortunate management overhead, which is commentary, not a requirement. | It must be possible to transition from a full-mesh configuration without the need to change either topology or AS. | | `3:3` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Design Criteria, bullet 'Compatibility'. Same frame as 3:1: a criterion the design had to meet, that noncompliant iBGP peers can stay in the AS without losing routing information. The mechanism that delivers it is section 6's statement that conventional speakers can be members of either group, which is itself indicative. | It must be possible for noncompliant IBGP peers to continue to be part of the original AS or domain without any loss of BGP routing information. | | `4:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Route Reflection. A worked example of the Figure 1 full mesh, and it says so in its own opening clause: 'With the existing BGP model'. It describes what RTR-A does BEFORE the rule this document relaxes, and the next sentence then relaxes it. Nothing here binds an RR. | With the existing BGP model, if RTR-A receives an external route and it is selected as the best path it must advertise the external route to both RTR-B and RTR-C. | | `5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Terminology and Concepts. The role is the operator who designs the AS's iBGP topology. The sentence states which sessions have to exist, not what a speaker does with a message: non-clients stay fully meshed among themselves while clients need not be. A route reflector cannot create or police its non-clients' sessions with each other, and Ze reads client versus non-client from configuration. The corresponding wire behaviour, that a non-client's route is not reflected to another non-client, is section 6's rule and is mapped at site 6:1. The role is a person designing a topology, so no producer could act as it. Ze CONSUMES the topology the operator built: the route reflector plugin (`internal/component/bgp/plugins/rr/register.go`) reflects over the sessions it is configured with and designs none. | The Non-Client peer must be fully meshed but the Client peers need not be fully meshed. | | `6:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Operation, case 2. A parenthetical drawing the consequence of the rule above it: because a client's route is reflected to the other clients, the clients are not required to be fully meshed. The site scan sees it for the word 'required', and it states that a requirement does NOT apply rather than imposing one. | (Hence the Client peers are not required to be fully meshed.) | | `8:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Avoiding Routing Information Loops, CLUSTER_LIST. The second half of the same obligation: the sentence before it requires the prepend, and this one says what to do when there is nothing to prepend to. rfc/short/rfc4456.md carries both in one row, 'MUST prepend its local CLUSTER_ID to the CLUSTER_LIST (creating one if absent)', which site 8:1 maps. | If the CLUSTER_LIST is empty, it MUST create a new one. | | `14.2:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The Intellectual Property notice from the RFC's closing boilerplate, which the section splitter attributes to 14.2 because no numbered heading follows the informative reference list. It invites interested parties to tell the IETF about patents and directs no protocol behaviour. It is boilerplate the extractor did not strip. | The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. | ## Superseded No document obsoletes RFC 4456, so its obligations are stated where they were written. --- ### Page: RFC 4486 - Subcodes for BGP Cease NOTIFICATION Message https://ze-software.net/quality/rfc-compliance/rfc4486/ # RFC 4486 - Subcodes for BGP Cease NOTIFICATION Message Supported. Every requirement this repository extracted from RFC 4486, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 1 of 1 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 1 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 1 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 1 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 4 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 1 | of 11 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 1 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 1 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 1 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 1 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 1 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 11 | | Gated MUST-level | 1 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 4 | | Tagged units | 4 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4486.md` | | Requirement shard | `rfc/requirements/rfc4486.md` | | RFC text | `rfc/full/rfc4486.txt` | ## Enrolment Enrolled: Subcodes for BGP Cease NOTIFICATION Message: one MUST-level requirement, RFC4486-4-1 -- terminating a peering because the prefix count exceeded a locally configured upper bound MUST send a NOTIFICATION with Error Code Cease and Error Subcode "Maximum Number of Prefixes Reached". Both polarities are proven over the reactor's prefix-limit path (internal/component/bgp/reactor/session_prefix.go:399 decides, :448 builds the NOTIFICATION with message.NotifyCease and message.NotifyCeaseMaxPrefixes): positive in TestPrefixExceedTeardown, and negative in TestPrefixWarningThreshold, where a peer at the warning threshold but below the maximum receives no NOTIFICATION at all -- so the Cease is bound to the termination and not merely to a counter moving. test/plugin/prefix-maximum-enforce.ci pins the same bytes on the wire (error code 06, subcode 01, plus the AFI/SAFI/count Data field of RFC4486-4-10). The other ten rows in section 4 are SHOULD, RECOMMENDED or MAY and are not gated. ## What the public ledger says **Status:** Supported **What the ledger says is covered** The subcode catalog for error code 6, subcodes 1 to 8 ([`internal/component/bgp/message/notification.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/notification.go)). 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. [`test/plugin/prefix-maximum-enforce.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/prefix-maximum-enforce.ci) 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 [`rfc/short/rfc4486.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4486.md). **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 1 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **1** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (1):** [`RFC4486-4-1`](#rfc4486-4-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4486-4-1` | If a BGP speaker decides to terminate its peering with a neighbor because the number of address prefixes received from the neighbor exceeds a locally configured upper bound (as described in [BGP-4]), then the speaker MUST send to the neighbor a NOTIFICATION message with the Error Code Cease and the Error Subcode "Maximum Number of Prefixes Reached" (§4) | MUST | 4 - Subcode Usage | **positive:** `unit/verify` [`TestPrefixExceedTeardown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L114). **negative:** `unit/verify` [`TestPrefixWarningThreshold`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L89). **positive:** `functional/verify` [`prefix-maximum-enforce.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/prefix-maximum-enforce.ci#L6) | | `RFC4486-4-2` | If a BGP speaker decides to administratively shut down its peering with a neighbor, then the speaker SHOULD send a NOTIFICATION message with the Error Code Cease and the Error Subcode "Administrative Shutdown" (§4) | SHOULD | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | | `RFC4486-4-3` | If a BGP speaker decides to de-configure a peer, then the speaker SHOULD send a NOTIFICATION message with the Error Code Cease and the Error Subcode "Peer De-configured" (§4) | SHOULD | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | | `RFC4486-4-4` | If a BGP speaker decides to administratively reset the peering with a neighbor, then the speaker SHOULD send a NOTIFICATION message with the Error Code Cease and the Error Subcode "Administrative Reset" (§4) | SHOULD | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | | `RFC4486-4-5` | If a BGP speaker decides to disallow a BGP connection (e.g., the peer is not configured locally) after the speaker accepts a transport protocol connection, then the BGP speaker SHOULD send a NOTIFICATION message with the Error Code Cease and the Error Subcode "Connection Rejected" (§4) | SHOULD | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | | `RFC4486-4-6` | If a BGP speaker decides to administratively reset the peering with a neighbor due to a configuration change other than the ones described above, then the speaker SHOULD send a NOTIFICATION message with the Error Code Cease and the Error Subcode "Other Configuration Change" (§4) | SHOULD | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | | `RFC4486-4-7` | If a BGP speaker decides to send a NOTIFICATION message with the Error Code Cease as a result of the collision resolution procedure (as described in [BGP-4]), then the subcode SHOULD be set to "Connection Collision Resolution" (§4) | SHOULD | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | | `RFC4486-4-8` | An implementation SHOULD impose an upper bound on the number of consecutive automatic retries (§4) | SHOULD | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | | `RFC4486-4-9` | It is RECOMMENDED that a BGP speaker behave as though the DampPeerOscillations attribute [BGP-4] were true for this peer when re-trying a BGP connection after the speaker receives a Cease NOTIFICATION message with a subcode of "Administrative Shutdown", "Peer De-configured", "Connection Rejected", or "Out of Resources" (§4) | RECOMMENDED | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | | `RFC4486-4-10` | The message MAY optionally include the Address Family information [BGP-MP] and the upper bound in the "Data" field, as shown in Figure 1 (§4) | MAY | 4 - Subcode Usage | **positive:** `unit/verify` [`TestPrefixNotificationDataCarriesTheConfiguredUpperBound`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L240). **negative:** no negative test | | `RFC4486-4-11` | If a BGP speaker runs out of resources (e.g., memory) and decides to reset a session, then the speaker MAY send a NOTIFICATION message with the Error Code Cease and the Error Subcode "Out of Resources" (§4) | MAY | 4 - Subcode Usage | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 4486 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4486-4-1`](#rfc4486-4-1) If a BGP speaker decides to terminate its peering with a neighbor because the number of address prefixes received from the neighbor exceeds a locally configured upper bound (as described in [BGP-4]), then the speaker MUST send to the neighbor a NOTIFICATION message with the Error Code Cease and the Error Subcode "Maximum Number of Prefixes Reached" (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPrefixWarningThreshold`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L89) | unit/verify | unproven | | positive | [`TestPrefixExceedTeardown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L114) | unit/verify | unproven | | positive | [`prefix-maximum-enforce.ci`](https://github.com/ze-software/ze/blob/main/test/plugin/prefix-maximum-enforce.ci#L6) | functional/verify | unproven | ### [`RFC4486-4-10`](#rfc4486-4-10) The message MAY optionally include the Address Family information [BGP-MP] and the upper bound in the "Data" field, as shown in Figure 1 (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestPrefixNotificationDataCarriesTheConfiguredUpperBound`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_prefix_test.go#L240) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement agent, spec-rfcgate-4-ledger phase 6 | | Signed off | 2026-07-30 | | Register | rfc2119 | | Source | rfc/full/rfc4486.txt | | Source fingerprint | ee862bf55c82959b | | Record | rfc/extraction/rfc4486.json | | Mapped sentences | 1 | | Declined as scope | 0 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, copyright notice and Abstract. The Abstract restates section 1 word for word and states no obligation. | | `1` | Introduction | 0 | walked | Introduction. Says what the document defines and that it 'also recommends' a backoff mechanism. The recommendation itself is stated normatively in section 4 and is captured there as RFC4486-4-9. | | `2` | not stated | 0 | walked | Specification of Requirements: the RFC 2119 key-words paragraph. It tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. | | `3` | Subcode Definition | 0 | walked | Subcode Definition. The registry table assigning subcodes 1 to 8 to their symbolic names. A value assignment, not a directive: the obligation to USE each value is in section 4. | | `4` | Subcode Usage | 1 | walked | Subcode Usage. The only normative section. Its one capitalised MUST is the site below; the remaining ten sentences are SHOULD, RECOMMENDED or MAY and are captured as RFC4486-4-2 through RFC4486-4-11. | | `5` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Defines subcodes 1 to 8 in the registry and names the process for future assignments. Binds IANA, not a speaker. | | `6` | Security Considerations | 0 | walked | Security Considerations. One sentence: the extension does not change BGP's underlying security issues. No countermeasure is directed at a speaker. | | `7` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. | | `8` | References heading | 0 | skipped (references) | References heading. | | `8.1` | Normative References: RFC 2119, RFC 4271, RFC 4760 | 0 | skipped (references) | Normative References: RFC 2119, RFC 4271, RFC 4760. | | `8.2` | Informative References: RFC 2434, RFC 4020 | 0 | skipped (references) | Informative References: RFC 2434, RFC 4020. | ### Excluded sentences The walk over RFC 4486 declined no sentence: every site it found is mapped to a requirement. ## Superseded No document obsoletes RFC 4486, so its obligations are stated where they were written. --- ### Page: RFC 4552 - Authentication/Confidentiality for OSPFv3 https://ze-software.net/quality/rfc-compliance/rfc4552/ # RFC 4552 - Authentication/Confidentiality for OSPFv3 Partial. Every requirement this repository extracted from RFC 4552, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 14.8% | 4 of 27 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 40.7% | 11 of 27 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 27 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 19 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 27 | of 38 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 27 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 3.7% | 1 of 27 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 22.2% | 6 of 27 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 27 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 18.5% | 5 of 27 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 27 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 38 | | Gated MUST-level | 27 | | Not applicable, so out of scope | 1 | | Declared gaps | 5 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 19 | | Tagged units | 19 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4552.md` | | Requirement shard | `rfc/requirements/rfc4552.md` | | RFC text | `rfc/full/rfc4552.txt` | ## Enrolment Enrolled: Authentication/Confidentiality for OSPFv3 (RFC 4552): OSPFv3 IPsec SA/policy installer over kernel XFRM. 4 MET (authentication support, confidentiality via ESP not AH, stream-cipher prohibition, hexadecimal key configuration) + 11 single-polarity positive (transport-mode SA + policies, per-interface SPD selection, source/dest/proto/direction selectors, inbound interface tagging, manual keying, shared inbound/outbound SA, IPsec barrier around all OSPF, protect + bypass SPD rules) + 5 gap (virtual-link IPsec: distinct SA, source/destination LA-bit selection, transit-area SPD rules) + 6 lower-layer (per-packet AH/ESP discard, ESP encapsulation and multi-SA storage performed by Linux XFRM on the state ze installs) + 1 not-applicable (the §6 clause that imports RFC 4305 algorithm keyword levels) ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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. **What the ledger says remains:** 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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 23 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **27** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`RFC4552-3-1`](#rfc4552-3-1), [`RFC4552-4-2`](#rfc4552-4-2), [`RFC4552-6-6`](#rfc4552-6-6), [`RFC4552-12-1`](#rfc4552-12-1) **Annotated instead of tested (23):** [`RFC4552-2-2`](#rfc4552-2-2), [`RFC4552-3-2`](#rfc4552-3-2), [`RFC4552-3-4`](#rfc4552-3-4), [`RFC4552-3-5`](#rfc4552-3-5), [`RFC4552-4-3`](#rfc4552-4-3), [`RFC4552-4-4`](#rfc4552-4-4), [`RFC4552-6-1`](#rfc4552-6-1), [`RFC4552-6-2`](#rfc4552-6-2), [`RFC4552-6-3`](#rfc4552-6-3), [`RFC4552-6-4`](#rfc4552-6-4), [`RFC4552-6-5`](#rfc4552-6-5), [`RFC4552-6-7`](#rfc4552-6-7), [`RFC4552-6-9`](#rfc4552-6-9), [`RFC4552-6-11`](#rfc4552-6-11), [`RFC4552-7-1`](#rfc4552-7-1), [`RFC4552-9-1`](#rfc4552-9-1), [`RFC4552-9-2`](#rfc4552-9-2), [`RFC4552-9-3`](#rfc4552-9-3), [`RFC4552-9-4`](#rfc4552-9-4), [`RFC4552-11-1`](#rfc4552-11-1), [`RFC4552-11-2`](#rfc4552-11-2), [`RFC4552-11-3`](#rfc4552-11-3), [`RFC4552-11-4`](#rfc4552-11-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4552-2-2` | All implementations conforming to this specification MUST support transport mode SA to provide required IPsec security to OSPFv3 packets (§2) | MUST | 2 | **positive:** `unit/verify` [`TestIPsecSAIsWildcardWithOSPFSelector`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L152). **negative:** no negative test. **{single-polarity}:** the installer only ever builds a transport-mode SA (buildIPsecSA Mode=ModeTransport, ipsec_install.go), so there is no tunnel-mode reject path to exercise | | `RFC4552-3-1` | Implementations conforming to this specification MUST support authentication for OSPFv3 (§3) | MUST | 3 | **positive:** `unit/verify` [`TestParseOSPFIPsecConfig`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L49). **negative:** `unit/verify` [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L105) | | `RFC4552-3-2` | In order to provide authentication to OSPFv3, implementations MUST support ESP (§3) | MUST | 3 | **positive:** `unit/verify` [`TestIPsecInstallOnInterfaceUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L69). **negative:** no negative test. **{single-polarity}:** an esp interface always installs an SA with Proto=ProtoESP (ipsecProtoNumber, ipsec_install.go); "supporting ESP" has no reject path of its own | | `RFC4552-3-4` | When OSPFv3 authentication is enabled, OSPFv3 packets that are not protected with AH or ESP MUST be silently discarded (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecPolicies installs the inbound require-policy (SADirIn, transport mode, upper-protocol 89, scoped to the arrival ifindex), and Linux XFRM discards an OSPFv3 packet that arrives on that interface without the required AH or ESP transform. Ze only samples the resulting counters (readXfrmDropsPlatform, ipsec_drops_linux.go) | | `RFC4552-3-5` | When OSPFv3 authentication is enabled, OSPFv3 packets that fail the authentication checks MUST be silently discarded (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams installs the integrity transform and its truncation length, and Linux XFRM discards a packet whose ICV does not verify. Ze holds no verifying branch of its own and reads only the XfrmInIntegFailures/XfrmInStateProtoError counters | | `RFC4552-4-2` | If confidentiality is provided, ESP MUST be used (§4) | MUST | 4 | **positive:** `unit/verify` [`TestIPsecESPConfidentialityValid`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L203). **negative:** `unit/verify` [`TestIPsecAHWithEncryptionRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L88) | | `RFC4552-4-3` | When OSPFv3 confidentiality is enabled, OSPFv3 packets that are not protected with ESP MUST be silently discarded (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecPolicies installs the inbound require-policy on a confidentiality-enabled interface, and Linux XFRM discards an OSPFv3 packet that arrives there without the required ESP transform | | `RFC4552-4-4` | When OSPFv3 confidentiality is enabled, OSPFv3 packets that fail the confidentiality checks MUST be silently discarded (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams installs the ESP encryption transform beside the integrity one, and Linux XFRM discards a packet whose decryption or integrity check fails | | `RFC4552-6-1` | IPsec in transport mode MUST be supported (§6) | MUST | 6 | **positive:** `unit/verify` [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L190). **negative:** no negative test. **{single-polarity}:** every installed policy is transport mode (buildIPsecPolicies Mode=ModeTransport, ipsec_install.go); there is no non-transport policy to reject | | `RFC4552-6-2` | The implementation MUST support multiple SPDs with an SPD selection function that chooses a specific SPD based on interface (§6) | MUST | 6 | **positive:** `unit/verify` [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L192). **negative:** no negative test. **{single-polarity}:** each interface's policies are scoped by IfIndex (buildIPsecPolicies IfIndex, ipsec_install.go), so the per-interface policy set is the interface-selected SPD; there is no reject path | | `RFC4552-6-3` | The implementation MUST be able to use source address, destination address, protocol, and direction as selectors in the SPD (§6) | MUST | 6 | **positive:** `unit/verify` [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L195). **negative:** no negative test. **{single-polarity}:** every policy carries source, destination, upper-protocol 89, and direction selectors (buildIPsecPolicies, ipsec_install.go); a selector set is emitted, never rejected | | `RFC4552-6-4` | The implementation MUST be able to tag inbound packets with the ID of the (physical or virtual) interface via which they arrived (§6) | MUST | 6 | **positive:** `unit/verify` [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L198). **negative:** no negative test. **{single-polarity}:** the inbound require-policy is scoped to the arrival ifindex (buildIPsecPolicies SADirIn IfIndex, ipsec_install.go); the kernel does the per-packet tagging, ze only supplies the interface selector | | `RFC4552-6-5` | Manually configured keys MUST be able to secure the specified traffic (§6) | MUST | 6 | **positive:** `unit/verify` [`TestSAParamsSharedKey`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L248). **negative:** no negative test. **{single-polarity}:** the SA is keyed from the statically configured SPI+key with no IKE (buildIPsecSA, ipsec_install.go); manual keying has no reject path | | `RFC4552-6-6` | The implementation MUST NOT allow the user to choose stream ciphers as the encryption algorithm for securing OSPFv3 packets (§6) | MUST NOT | 6 | **positive:** `unit/verify` [`TestIPsecESPConfidentialityValid`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L205). **negative:** `unit/verify` [`TestIPsecStreamCipherRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L224) | | `RFC4552-6-7` | The algorithm key words (MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT) that appear in RFC 4305 [N6] are to be interpreted per RFC 2119 for OSPFv3 support as well, except when in conflict with the stream-cipher prohibition (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** an interpretation clause importing RFC 4305 [N6] algorithm keywords; RFC 4552 defines no independent behavior here. The concrete algorithm conformance lives in the config enums (ipsecAuthKeyLen and ipsecEncKeyLen, internal/plugins/ospf/config.go) and the stream-cipher carve-out is RFC4552-6-6 | | `RFC4552-6-9` | IP encapsulation of ESP packets MUST be supported (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode ESP SA and asks for no UDP encapsulation, so xfrmStateFromParams writes no Encap, and Linux XFRM carries every ESP packet directly in IP | | `RFC4552-6-11` | The IPsec implementation MUST support the establishment and maintenance of multiple SAs with the same selectors between a given sender and receiver (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{lower-layer}:** Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes each state with its own SPI, and Linux XFRM keys the SAD by destination, SPI and protocol, so several SAs carrying one selector set coexist between a given sender and receiver | | `RFC4552-7-1` | The implementations MUST use manually configured keys with the same SA parameters (SPI, keys, etc.) for both inbound and outbound SAs (§7) | MUST | 7 | **positive:** `unit/verify` [`TestIPsecSAIsWildcardWithOSPFSelector`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L139). **negative:** no negative test. **{single-polarity}:** one manually configured SPI/key drives the single shared SA that both protects egress and verifies ingress (buildIPsecSA installed once, ipsec_install.go); there is no reject path | | `RFC4552-9-1` | A different SA than the SA of the underlying interface MUST be provided for virtual links (§9) | MUST | 9 | **positive:** no positive test. **negative:** no negative test. **{gap}:** OSPFv3 virtual-link IPsec is unimplemented; the installer consumes only configured-interface IPsec blocks (setConfig over cfg.Interfaces, ipsec_install.go) and no virtual link ever installs an SA. Disclosed in docs/features/rfc-status.md RFC 4552 row | | `RFC4552-9-2` | Routers that implement this specification MUST change the way source and destination addresses are chosen for packets exchanged over virtual links when IPsec is enabled (§9) | MUST | 9 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze never enables IPsec on a virtual link, so it does not switch virtual-link source/destination selection when IPsec is on; the endpoints stay RFC 5340 §2.9 routed globals (v6ResolveVirtualEndpointLocked, virtuallink_v6.go:28-35). Disclosed in docs/features/rfc-status.md RFC 4552 row | | `RFC4552-9-3` | The first IPv6 address with the "LA-bit" set in prefixes advertised in intra-area-prefix-LSAs in the transit area MUST be used as the source address for packets exchanged over the virtual link (§9) | MUST | 9 | **positive:** no positive test. **negative:** no negative test. **{gap}:** virtual-link IPsec is unimplemented, so the RFC 4552 §9 rule of using the first LA-bit intra-area-prefix address as the source is not applied; ze selects the source from RFC 5340 routed globals (v6RouterGlobalAddr, virtuallink_v6.go:42-81). Disclosed in docs/features/rfc-status.md RFC 4552 row | | `RFC4552-9-4` | The first IPv6 address with the "LA-bit" set in prefixes received in intra-area-prefix-LSAs from the virtual neighbor in the transit area MUST be used as the destination address for packets exchanged over the virtual link (§9) | MUST | 9 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the matching destination rule (first LA-bit intra-area-prefix address of the virtual neighbor) is likewise unapplied because virtual-link IPsec is unimplemented (virtuallink_v6.go:28-35). Disclosed in docs/features/rfc-status.md RFC 4552 row | | `RFC4552-11-1` | The IPsec protection barrier MUST be around the OSPF protocol so that all inbound and outbound OSPF traffic goes through IPsec processing (§11) | MUST | 11 | **positive:** `unit/verify` [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L201). **negative:** no negative test. **{single-polarity}:** the out/in/fwd proto-89 policies put the IPsec barrier around all OSPF traffic on the interface (buildIPsecPolicies, ipsec_install.go); the barrier is installed, never rejected | | `RFC4552-11-2` | The SPD selection function MUST return an SPD with the bypass rule for all interfaces that have OSPFv3 authentication/confidentiality disabled (§11) | MUST | 11 | **positive:** `unit/verify` [`TestIPsecDisabledInterfaceBypass`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L278). **negative:** no negative test. **{single-polarity}:** a disabled interface installs no require-policy (setConfig adds only interfaces with an IPsec block, ipsec_install.go), so its OSPF is bypassed by the kernel default; the bypass has no reject path | | `RFC4552-11-3` | The SPD selection function MUST return an SPD with the protect rules for all interfaces that have OSPFv3 authentication/confidentiality enabled (§11) | MUST | 11 | **positive:** `unit/verify` [`TestIPsecInstallOnInterfaceUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L74). **negative:** no negative test. **{single-polarity}:** an enabled interface installs the out/in/fwd protect (require) policies (installLocked, ipsec_install.go); the protect rules are installed, never rejected | | `RFC4552-11-4` | The virtual-link SPD rules MUST be installed in the SPD for the interfaces that are connected to the transit area for the virtual link (§11) | MUST | 11 | **positive:** no positive test. **negative:** no negative test. **{gap}:** no virtual-link SPD rules are installed on transit-area interfaces because virtual-link IPsec is unimplemented (ipsec_install.go installs only configured-interface policies). Disclosed in docs/features/rfc-status.md RFC 4552 row | | `RFC4552-12-1` | The implementations MUST allow the administrator to configure the cryptographic and authentication keys in hexadecimal format (§12) | MUST | 12 | **positive:** `unit/verify` [`TestParseOSPFIPsecConfig`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L43). **negative:** `unit/verify` [`TestIPsecNonHexKeyRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L238) | | `RFC4552-4-1` | Implementations conforming to this specification SHOULD support confidentiality for OSPFv3 (§4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-6-8` | The routing module SHOULD be able to configure, modify, and delete IPsec rules on the fly (mainly for securing virtual links) (§6) | SHOULD | 6 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-6-10` | For simplicity, UDP encapsulation of ESP packets SHOULD NOT be used (§6) | SHOULD NOT | 6 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-8-1` | The user SHOULD be given the choice of sharing the same SA among multiple interfaces or using a unique SA per interface (§8) | SHOULD | 8 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-10-1` | To maintain the security of a link, the authentication and encryption key values SHOULD be changed periodically (§10) | SHOULD | 10 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-10.1-1` | The three-step rekey procedure SHOULD be provided to rekey the routers on a link without dropping OSPFv3 protocol packets or disrupting the adjacency (§10.1) | SHOULD | 10.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-10.3-1` | The encryption and authentication keys SHOULD be changed at least every 90 days (§10.3) | SHOULD | 10.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-2-1` | Two hosts MAY establish a tunnel mode SA between themselves (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-2-3` | Implementations MAY also support tunnel mode SA to provide required IPsec security to OSPFv3 packets (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-3-3` | In order to provide authentication to OSPFv3, implementations MAY support AH (§3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC4552-11-5` | The virtual-link SPD rules MAY alternatively be installed on all the interfaces (§11) | MAY | 11 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4552-3-4`](#rfc4552-3-4) When OSPFv3 authentication is enabled, OSPFv3 packets that are not protected with AH or ESP MUST be silently discarded (§3) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecPolicies installs the inbound require-policy (SADirIn, transport mode, upper-protocol 89, scoped to the arrival ifindex), and Linux XFRM discards an OSPFv3 packet that arrives on that interface without the required AH or ESP transform. Ze only samples the resulting counters (readXfrmDropsPlatform, ipsec_drops_linux.go) | | [`RFC4552-3-5`](#rfc4552-3-5) When OSPFv3 authentication is enabled, OSPFv3 packets that fail the authentication checks MUST be silently discarded (§3) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams installs the integrity transform and its truncation length, and Linux XFRM discards a packet whose ICV does not verify. Ze holds no verifying branch of its own and reads only the XfrmInIntegFailures/XfrmInStateProtoError counters | | [`RFC4552-4-3`](#rfc4552-4-3) When OSPFv3 confidentiality is enabled, OSPFv3 packets that are not protected with ESP MUST be silently discarded (§4) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecPolicies installs the inbound require-policy on a confidentiality-enabled interface, and Linux XFRM discards an OSPFv3 packet that arrives there without the required ESP transform | | [`RFC4552-4-4`](#rfc4552-4-4) When OSPFv3 confidentiality is enabled, OSPFv3 packets that fail the confidentiality checks MUST be silently discarded (§4) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams installs the ESP encryption transform beside the integrity one, and Linux XFRM discards a packet whose decryption or integrity check fails | | [`RFC4552-6-7`](#rfc4552-6-7) The algorithm key words (MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT) that appear in RFC 4305 [N6] are to be interpreted per RFC 2119 for OSPFv3 support as well, except when in conflict with the stream-cipher prohibition (§6) | no test | no test carries this requirement id; annotated {not-applicable}: an interpretation clause importing RFC 4305 [N6] algorithm keywords; RFC 4552 defines no independent behavior here. The concrete algorithm conformance lives in the config enums (ipsecAuthKeyLen and ipsecEncKeyLen, internal/plugins/ospf/config.go) and the stream-cipher carve-out is RFC4552-6-6 | | [`RFC4552-6-9`](#rfc4552-6-9) IP encapsulation of ESP packets MUST be supported (§6) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/plugins/ospf/ipsec_install.go::buildIPsecSA installs the transport-mode ESP SA and asks for no UDP encapsulation, so xfrmStateFromParams writes no Encap, and Linux XFRM carries every ESP packet directly in IP | | [`RFC4552-6-11`](#rfc4552-6-11) The IPsec implementation MUST support the establishment and maintenance of multiple SAs with the same selectors between a given sender and receiver (§6) | no test | no test carries this requirement id; annotated {lower-layer}: Linux XFRM; internal/component/ike/dataplane/xfrm_linux.go::xfrmStateFromParams writes each state with its own SPI, and Linux XFRM keys the SAD by destination, SPI and protocol, so several SAs carrying one selector set coexist between a given sender and receiver | | [`RFC4552-9-1`](#rfc4552-9-1) A different SA than the SA of the underlying interface MUST be provided for virtual links (§9) | {gap}, no test | OSPFv3 virtual-link IPsec is unimplemented; the installer consumes only configured-interface IPsec blocks (setConfig over cfg.Interfaces, ipsec_install.go) and no virtual link ever installs an SA. Disclosed in docs/features/rfc-status.md RFC 4552 row | | [`RFC4552-9-2`](#rfc4552-9-2) Routers that implement this specification MUST change the way source and destination addresses are chosen for packets exchanged over virtual links when IPsec is enabled (§9) | {gap}, no test | ze never enables IPsec on a virtual link, so it does not switch virtual-link source/destination selection when IPsec is on; the endpoints stay RFC 5340 §2.9 routed globals (v6ResolveVirtualEndpointLocked, virtuallink_v6.go:28-35). Disclosed in docs/features/rfc-status.md RFC 4552 row | | [`RFC4552-9-3`](#rfc4552-9-3) The first IPv6 address with the "LA-bit" set in prefixes advertised in intra-area-prefix-LSAs in the transit area MUST be used as the source address for packets exchanged over the virtual link (§9) | {gap}, no test | virtual-link IPsec is unimplemented, so the RFC 4552 §9 rule of using the first LA-bit intra-area-prefix address as the source is not applied; ze selects the source from RFC 5340 routed globals (v6RouterGlobalAddr, virtuallink_v6.go:42-81). Disclosed in docs/features/rfc-status.md RFC 4552 row | | [`RFC4552-9-4`](#rfc4552-9-4) The first IPv6 address with the "LA-bit" set in prefixes received in intra-area-prefix-LSAs from the virtual neighbor in the transit area MUST be used as the destination address for packets exchanged over the virtual link (§9) | {gap}, no test | the matching destination rule (first LA-bit intra-area-prefix address of the virtual neighbor) is likewise unapplied because virtual-link IPsec is unimplemented (virtuallink_v6.go:28-35). Disclosed in docs/features/rfc-status.md RFC 4552 row | | [`RFC4552-11-4`](#rfc4552-11-4) The virtual-link SPD rules MUST be installed in the SPD for the interfaces that are connected to the transit area for the virtual link (§11) | {gap}, no test | no virtual-link SPD rules are installed on transit-area interfaces because virtual-link IPsec is unimplemented (ipsec_install.go installs only configured-interface policies). Disclosed in docs/features/rfc-status.md RFC 4552 row | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4552-2-2`](#rfc4552-2-2) All implementations conforming to this specification MUST support transport mode SA to provide required IPsec security to OSPFv3 packets (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecSAIsWildcardWithOSPFSelector`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L152) | unit/verify | unproven | ### [`RFC4552-3-1`](#rfc4552-3-1) Implementations conforming to this specification MUST support authentication for OSPFv3 (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecESPRequiresIntegrity`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L105) | unit/verify | unproven | | positive | [`TestParseOSPFIPsecConfig`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L49) | unit/verify | unproven | ### [`RFC4552-3-2`](#rfc4552-3-2) In order to provide authentication to OSPFv3, implementations MUST support ESP (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecInstallOnInterfaceUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L69) | unit/verify | unproven | ### [`RFC4552-3-4`](#rfc4552-3-4) When OSPFv3 authentication is enabled, OSPFv3 packets that are not protected with AH or ESP MUST be silently discarded (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-3-4, so no unit is bound to it. ### [`RFC4552-3-5`](#rfc4552-3-5) When OSPFv3 authentication is enabled, OSPFv3 packets that fail the authentication checks MUST be silently discarded (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-3-5, so no unit is bound to it. ### [`RFC4552-4-2`](#rfc4552-4-2) If confidentiality is provided, ESP MUST be used (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecAHWithEncryptionRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L88) | unit/verify | unproven | | positive | [`TestIPsecESPConfidentialityValid`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L203) | unit/verify | unproven | ### [`RFC4552-4-3`](#rfc4552-4-3) When OSPFv3 confidentiality is enabled, OSPFv3 packets that are not protected with ESP MUST be silently discarded (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-4-3, so no unit is bound to it. ### [`RFC4552-4-4`](#rfc4552-4-4) When OSPFv3 confidentiality is enabled, OSPFv3 packets that fail the confidentiality checks MUST be silently discarded (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-4-4, so no unit is bound to it. ### [`RFC4552-6-1`](#rfc4552-6-1) IPsec in transport mode MUST be supported (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L190) | unit/verify | unproven | ### [`RFC4552-6-2`](#rfc4552-6-2) The implementation MUST support multiple SPDs with an SPD selection function that chooses a specific SPD based on interface (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L192) | unit/verify | unproven | ### [`RFC4552-6-3`](#rfc4552-6-3) The implementation MUST be able to use source address, destination address, protocol, and direction as selectors in the SPD (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L195) | unit/verify | unproven | ### [`RFC4552-6-4`](#rfc4552-6-4) The implementation MUST be able to tag inbound packets with the ID of the (physical or virtual) interface via which they arrived (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L198) | unit/verify | unproven | ### [`RFC4552-6-5`](#rfc4552-6-5) Manually configured keys MUST be able to secure the specified traffic (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSAParamsSharedKey`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L248) | unit/verify | unproven | ### [`RFC4552-6-6`](#rfc4552-6-6) The implementation MUST NOT allow the user to choose stream ciphers as the encryption algorithm for securing OSPFv3 packets (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecStreamCipherRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L224) | unit/verify | unproven | | positive | [`TestIPsecESPConfidentialityValid`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L205) | unit/verify | unproven | ### [`RFC4552-6-7`](#rfc4552-6-7) The algorithm key words (MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT) that appear in RFC 4305 [N6] are to be interpreted per RFC 2119 for OSPFv3 support as well, except when in conflict with the stream-cipher prohibition (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-6-7, so no unit is bound to it. ### [`RFC4552-6-9`](#rfc4552-6-9) IP encapsulation of ESP packets MUST be supported (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-6-9, so no unit is bound to it. ### [`RFC4552-6-11`](#rfc4552-6-11) The IPsec implementation MUST support the establishment and maintenance of multiple SAs with the same selectors between a given sender and receiver (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-6-11, so no unit is bound to it. ### [`RFC4552-7-1`](#rfc4552-7-1) The implementations MUST use manually configured keys with the same SA parameters (SPI, keys, etc.) for both inbound and outbound SAs (§7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecSAIsWildcardWithOSPFSelector`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L139) | unit/verify | unproven | ### [`RFC4552-9-1`](#rfc4552-9-1) A different SA than the SA of the underlying interface MUST be provided for virtual links (§9) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-9-1, so no unit is bound to it. ### [`RFC4552-9-2`](#rfc4552-9-2) Routers that implement this specification MUST change the way source and destination addresses are chosen for packets exchanged over virtual links when IPsec is enabled (§9) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-9-2, so no unit is bound to it. ### [`RFC4552-9-3`](#rfc4552-9-3) The first IPv6 address with the "LA-bit" set in prefixes advertised in intra-area-prefix-LSAs in the transit area MUST be used as the source address for packets exchanged over the virtual link (§9) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-9-3, so no unit is bound to it. ### [`RFC4552-9-4`](#rfc4552-9-4) The first IPv6 address with the "LA-bit" set in prefixes received in intra-area-prefix-LSAs from the virtual neighbor in the transit area MUST be used as the destination address for packets exchanged over the virtual link (§9) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-9-4, so no unit is bound to it. ### [`RFC4552-11-1`](#rfc4552-11-1) The IPsec protection barrier MUST be around the OSPF protocol so that all inbound and outbound OSPF traffic goes through IPsec processing (§11) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecPoliciesInterfaceScopedWildcard`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L201) | unit/verify | unproven | ### [`RFC4552-11-2`](#rfc4552-11-2) The SPD selection function MUST return an SPD with the bypass rule for all interfaces that have OSPFv3 authentication/confidentiality disabled (§11) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecDisabledInterfaceBypass`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L278) | unit/verify | unproven | ### [`RFC4552-11-3`](#rfc4552-11-3) The SPD selection function MUST return an SPD with the protect rules for all interfaces that have OSPFv3 authentication/confidentiality enabled (§11) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPsecInstallOnInterfaceUp`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ipsec_install_test.go#L74) | unit/verify | unproven | ### [`RFC4552-11-4`](#rfc4552-11-4) The virtual-link SPD rules MUST be installed in the SPD for the interfaces that are connected to the transit area for the virtual link (§11) Audit verdict: not audited: no reader has judged these tests No test carries RFC4552-11-4, so no unit is bound to it. ### [`RFC4552-12-1`](#rfc4552-12-1) The implementations MUST allow the administrator to configure the cryptographic and authentication keys in hexadecimal format (§12) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPsecNonHexKeyRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L238) | unit/verify | unproven | | positive | [`TestParseOSPFIPsecConfig`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_ipsec_test.go#L43) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 4552, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4552, so its obligations are stated where they were written. --- ### Page: RFC 4555 - IKEv2 Mobility and Multihoming Protocol (MOBIKE) https://ze-software.net/quality/rfc-compliance/rfc4555/ # RFC 4555 - IKEv2 Mobility and Multihoming Protocol (MOBIKE) Unsupported. Every requirement this repository extracted from RFC 4555, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 16 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 6 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 6 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Unsupported | | Enrolment | Enrolled | | Requirements | 16 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 6 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4555.md` | | Requirement shard | `rfc/requirements/rfc4555.md` | | RFC text | `rfc/full/rfc4555.txt` | ## Enrolment Enrolled: IKEv2 Mobility and Multihoming Protocol (MOBIKE): six MUST-level requirements, all {not-applicable} to Ze. Ze does not implement the RFC 4555 MOBIKE extension -- its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path (no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, no address-update handling; grep for MOBIKE/UPDATE_SA_ADDRESSES across internal/component/ike/ finds nothing). RFC4555-x-1 (MOBIKE_SUPPORTED in IKE_AUTH), RFC4555-x-2 (switch to port 4500 for MOBIKE+NAT-T), RFC4555-3.9-1 (NO_NATS_ALLOWED in address-updating messages), RFC4555-3.7-1 (responder copies COOKIE2 verbatim), RFC4555-3.8-1 (no dynamic address updates when not behind NAT), RFC4555-3.8-2 (echo NAT_DETECTION in MOBIKE INFORMATIONAL) all govern MOBIKE behaviors Ze does not implement. No SHOULD/MAY requirements are gated. ## What the public ledger says **Status:** Unsupported **What the ledger says is covered:** - None - the IKE engine does not define the MOBIKE notify types (16396/16400) and does not handle UPDATE_SA_ADDRESSES. **What the ledger says remains:** Responder role (announce MOBIKE_SUPPORTED, accept UPDATE_SA_ADDRESSES, migrate XFRM endpoints) is not implemented. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 6 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (6):** [`RFC4555-x-1`](#rfc4555-x-1), [`RFC4555-x-2`](#rfc4555-x-2), [`RFC4555-3.9-1`](#rfc4555-3.9-1), [`RFC4555-3.7-1`](#rfc4555-3.7-1), [`RFC4555-3.8-1`](#rfc4555-3.8-1), [`RFC4555-3.8-2`](#rfc4555-3.8-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4555-x-1` | Both peers MUST include N(MOBIKE_SUPPORTED) in IKE_AUTH to enable MOBIKE for that IKE SA (Capability Negotiation, Sections 3.1-3.2) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | `RFC4555-x-2` | Implementations supporting both MOBIKE and NAT Traversal MUST switch to port 4500 during IKE_AUTH even if no NAT is detected (Capability Negotiation, Sections 3.1-3.2) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | `RFC4555-3.9-1` | When NAT Traversal is NOT enabled, address-updating messages MUST include NO_NATS_ALLOWED containing actual source/destination IP and ports (NAT Prohibition, Section 3.9) | MUST | 3.9 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | `RFC4555-3.7-1` | The exchange responder MUST copy COOKIE2 verbatim into the response (Return Routability Check, Section 3.7) | MUST | 3.7 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | `RFC4555-3.8-1` | When MOBIKE is active, the host not behind a NAT MUST NOT use dynamic IKEv2 address updates for IKE packets (NAT Mapping Changes, Section 3.8) | MUST | 3.8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | `RFC4555-3.8-2` | The responder MUST echo NAT_DETECTION_SOURCE_IP and NAT_DETECTION_DESTINATION_IP if present in any INFORMATIONAL request (NAT Mapping Changes, Section 3.8) | MUST | 3.8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | `RFC4555-3.7-2` | Return routability check SHOULD be performed by default (Return Routability Check, Section 3.7) | SHOULD | 3.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4555-3.8-3` | The initiator behind a NAT SHOULD include NAT detection payloads in DPD messages and compare with previous values (NAT Mapping Changes, Section 3.8) | SHOULD | 3.8 | **positive:** no positive test. **negative:** no negative test | | `RFC4555-3.9-2` | The initiator SHOULD retry several times on UNEXPECTED_NAT_DETECTED (NAT Prohibition, Section 3.9) | SHOULD | 3.9 | **positive:** no positive test. **negative:** no negative test | | `RFC4555-3.11-1` | The responder SHOULD use long timeout intervals (at least 5 minutes for retransmission) to give the initiator time to detect problems and switch paths (Failure Recovery, Section 3.11) | SHOULD | 3.11 | **positive:** no positive test. **negative:** no negative test | | `RFC4555-3.12-1` | MOBIKE nodes SHOULD verify that incoming IPsec packets use expected addresses (Dead Peer Detection, Section 3.12) | SHOULD | 3.12 | **positive:** no positive test. **negative:** no negative test | | `RFC4555-3.12-2` | Packets from stale addresses SHOULD NOT count as evidence that the peer is alive and synchronized (Dead Peer Detection, Section 3.12) | SHOULD NOT | 3.12 | **positive:** no positive test. **negative:** no negative test | | `RFC4555-x-3` | SPD cache links to SAD entries should NOT use (remote IP, remote SPI) as the key (Implementation Considerations, Appendix A) | SHOULD NOT | x | **positive:** no positive test. **negative:** no negative test | | `RFC4555-x-4` | Both peers MAY advertise extra addresses in IKE_AUTH and later in INFORMATIONAL requests (Additional Addresses, Sections 3.4, 3.6) | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC4555-3.7-3` | Return routability check MAY be omitted in trusted environments (Return Routability Check, Section 3.7) | MAY | 3.7 | **positive:** no positive test. **negative:** no negative test | | `RFC4555-3.8-4` | The host not behind a NAT MAY use dynamic IKEv2 address updates for ESP packets when MOBIKE is active (NAT Mapping Changes, Section 3.8) | MAY | 3.8 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4555-x-1`](#rfc4555-x-1) Both peers MUST include N(MOBIKE_SUPPORTED) in IKE_AUTH to enable MOBIKE for that IKE SA (Capability Negotiation, Sections 3.1-3.2) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | [`RFC4555-x-2`](#rfc4555-x-2) Implementations supporting both MOBIKE and NAT Traversal MUST switch to port 4500 during IKE_AUTH even if no NAT is detected (Capability Negotiation, Sections 3.1-3.2) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | [`RFC4555-3.9-1`](#rfc4555-3.9-1) When NAT Traversal is NOT enabled, address-updating messages MUST include NO_NATS_ALLOWED containing actual source/destination IP and ports (NAT Prohibition, Section 3.9) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | [`RFC4555-3.7-1`](#rfc4555-3.7-1) The exchange responder MUST copy COOKIE2 verbatim into the response (Return Routability Check, Section 3.7) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | [`RFC4555-3.8-1`](#rfc4555-3.8-1) When MOBIKE is active, the host not behind a NAT MUST NOT use dynamic IKEv2 address updates for IKE packets (NAT Mapping Changes, Section 3.8) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | | [`RFC4555-3.8-2`](#rfc4555-3.8-2) The responder MUST echo NAT_DETECTION_SOURCE_IP and NAT_DETECTION_DESTINATION_IP if present in any INFORMATIONAL request (NAT Mapping Changes, Section 3.8) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not implement the RFC 4555 MOBIKE (IKEv2 Mobility and Multihoming) extension. Its IKE engine (internal/component/ike/) implements base IKEv2 (RFC 7296) and NAT-Traversal but has no MOBIKE code path -- no N(MOBIKE_SUPPORTED) negotiation, no UPDATE_SA_ADDRESSES exchange, and no address-update handling (grep for MOBIKE / UPDATE_SA_ADDRESSES / MOBIKE_SUPPORTED across internal/component/ike/ finds nothing), so this MOBIKE requirement has no applicable code path. | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4555-x-1`](#rfc4555-x-1) Both peers MUST include N(MOBIKE_SUPPORTED) in IKE_AUTH to enable MOBIKE for that IKE SA (Capability Negotiation, Sections 3.1-3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4555-x-1, so no unit is bound to it. ### [`RFC4555-x-2`](#rfc4555-x-2) Implementations supporting both MOBIKE and NAT Traversal MUST switch to port 4500 during IKE_AUTH even if no NAT is detected (Capability Negotiation, Sections 3.1-3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4555-x-2, so no unit is bound to it. ### [`RFC4555-3.9-1`](#rfc4555-3.9-1) When NAT Traversal is NOT enabled, address-updating messages MUST include NO_NATS_ALLOWED containing actual source/destination IP and ports (NAT Prohibition, Section 3.9) Audit verdict: not audited: no reader has judged these tests No test carries RFC4555-3.9-1, so no unit is bound to it. ### [`RFC4555-3.7-1`](#rfc4555-3.7-1) The exchange responder MUST copy COOKIE2 verbatim into the response (Return Routability Check, Section 3.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC4555-3.7-1, so no unit is bound to it. ### [`RFC4555-3.8-1`](#rfc4555-3.8-1) When MOBIKE is active, the host not behind a NAT MUST NOT use dynamic IKEv2 address updates for IKE packets (NAT Mapping Changes, Section 3.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC4555-3.8-1, so no unit is bound to it. ### [`RFC4555-3.8-2`](#rfc4555-3.8-2) The responder MUST echo NAT_DETECTION_SOURCE_IP and NAT_DETECTION_DESTINATION_IP if present in any INFORMATIONAL request (NAT Mapping Changes, Section 3.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC4555-3.8-2, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 4555, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4555, so its obligations are stated where they were written. --- ### Page: RFC 4576 - Using a Link State Advertisement (LSA) Options Bit to Prevent Looping in BGP/MPLS IP Virtual Private Networks (VPNs) https://ze-software.net/quality/rfc-compliance/rfc4576/ # RFC 4576 - Using a Link State Advertisement (LSA) Options Bit to Prevent Looping in BGP/MPLS IP Virtual Private Networks (VPNs) No row in the public ledger. Every requirement this repository extracted from RFC 4576, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 4 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 4 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 4 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 4 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 4 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4576.md` | | Requirement shard | `rfc/requirements/rfc4576.md` | | RFC text | `rfc/full/rfc4576.txt` | ## Enrolment Enrolled: Using an LSA Options Bit (DN bit) to Prevent Looping in BGP/MPLS IP VPNs: four MUST-level requirements, all {not-applicable} to Ze. RFC 4576 governs OSPF running as a BGP/MPLS-VPN PE-CE protocol (the DN bit prevents PE-CE loops). Ze does NOT run OSPF as an MPLS-VPN PE-CE protocol -- it has no L3VPN OSPF code path (no VPN OSPF route redistribution, no sham links, no PE-CE OSPF; grep across internal/plugins/ospf/ for sham-link/PE-CE/vpn-ospf/domain-id finds nothing), running OSPF only as a plain IGP. RFC4576-4-1 (PE sets DN bit on type 3/5/7 LSA to CE) and RFC4576-4-2 (DN bit clear on all other LSA types) have no PE origination code path; RFC4576-4-3 (ignore DN bit on other LSA types) -- Ze decodes the DN bit as a field (internal/plugins/ospf/v3/types/prefix.go:68) but performs no VPN loop-prevention acting on it; RFC4576-4-4 (PE MUST NOT use a CE DN-bit-set LSA in route calc) has no PE route-calc code path. No SHOULD/MAY requirements are gated. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 4576. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (4):** [`RFC4576-4-1`](#rfc4576-4-1), [`RFC4576-4-2`](#rfc4576-4-2), [`RFC4576-4-3`](#rfc4576-4-3), [`RFC4576-4-4`](#rfc4576-4-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4576-4-1` | "When a type 3, 5, or 7 LSA is sent from a PE to a CE, the DN bit MUST be set." (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze does not run OSPF as a BGP/MPLS-VPN PE-CE protocol -- it has no L3VPN OSPF code path (no VPN OSPF route redistribution, no sham links, no PE-CE OSPF; grep for sham-link / PE-CE / vpn-ospf / domain-id across internal/plugins/ospf/ finds nothing). Ze runs OSPF only as a plain IGP, so it never originates a type 3/5/7 LSA "from a PE to a CE" and has no code path that would set the RFC 4576 DN bit. | | `RFC4576-4-2` | "The DN bit MUST be clear in all other LSA types." (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Ze is not an MPLS-VPN PE and originates no LSAs in a VPN PE-CE context, so it never sets the DN bit on any LSA type; the "DN bit clear in all other LSA types" transmission rule governs a PE role Ze does not implement. | | `RFC4576-4-3` | "The DN bit MUST be ignored in all other LSA types." (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** This governs a VPN PE's receive processing of the DN bit. Ze runs plain (non-VPN) OSPF with no PE-CE role: its OSPFv3 prefix codec merely decodes the DN bit as a field (internal/plugins/ospf/v3/types/prefix.go:68 Down()) and Ze performs no VPN loop-prevention that acts on it, so the PE-CE DN-bit handling this requirement defines has no applicable code path. | | `RFC4576-4-4` | "When the PE receives, from a CE router, a type 3, 5, or 7 LSA with the DN bit set, the information from that LSA MUST NOT be used during the OSPF route calculation." (§4) | MUST NOT | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** This governs a VPN PE ignoring a CE's DN-bit-set LSA during OSPF route calculation. Ze is not an MPLS-VPN PE and runs no OSPF PE-CE / VPN OSPF route calculation, so there is no code path in which a PE would consume a CE's DN-bit-set LSA. | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4576-4-1`](#rfc4576-4-1) "When a type 3, 5, or 7 LSA is sent from a PE to a CE, the DN bit MUST be set." (§4) | no test | no test carries this requirement id; annotated {not-applicable}: Ze does not run OSPF as a BGP/MPLS-VPN PE-CE protocol -- it has no L3VPN OSPF code path (no VPN OSPF route redistribution, no sham links, no PE-CE OSPF; grep for sham-link / PE-CE / vpn-ospf / domain-id across internal/plugins/ospf/ finds nothing). Ze runs OSPF only as a plain IGP, so it never originates a type 3/5/7 LSA "from a PE to a CE" and has no code path that would set the RFC 4576 DN bit. | | [`RFC4576-4-2`](#rfc4576-4-2) "The DN bit MUST be clear in all other LSA types." (§4) | no test | no test carries this requirement id; annotated {not-applicable}: Ze is not an MPLS-VPN PE and originates no LSAs in a VPN PE-CE context, so it never sets the DN bit on any LSA type; the "DN bit clear in all other LSA types" transmission rule governs a PE role Ze does not implement. | | [`RFC4576-4-3`](#rfc4576-4-3) "The DN bit MUST be ignored in all other LSA types." (§4) | no test | no test carries this requirement id; annotated {not-applicable}: This governs a VPN PE's receive processing of the DN bit. Ze runs plain (non-VPN) OSPF with no PE-CE role: its OSPFv3 prefix codec merely decodes the DN bit as a field (internal/plugins/ospf/v3/types/prefix.go:68 Down()) and Ze performs no VPN loop-prevention that acts on it, so the PE-CE DN-bit handling this requirement defines has no applicable code path. | | [`RFC4576-4-4`](#rfc4576-4-4) "When the PE receives, from a CE router, a type 3, 5, or 7 LSA with the DN bit set, the information from that LSA MUST NOT be used during the OSPF route calculation." (§4) | no test | no test carries this requirement id; annotated {not-applicable}: This governs a VPN PE ignoring a CE's DN-bit-set LSA during OSPF route calculation. Ze is not an MPLS-VPN PE and runs no OSPF PE-CE / VPN OSPF route calculation, so there is no code path in which a PE would consume a CE's DN-bit-set LSA. | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4576-4-1`](#rfc4576-4-1) "When a type 3, 5, or 7 LSA is sent from a PE to a CE, the DN bit MUST be set." (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4576-4-1, so no unit is bound to it. ### [`RFC4576-4-2`](#rfc4576-4-2) "The DN bit MUST be clear in all other LSA types." (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4576-4-2, so no unit is bound to it. ### [`RFC4576-4-3`](#rfc4576-4-3) "The DN bit MUST be ignored in all other LSA types." (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4576-4-3, so no unit is bound to it. ### [`RFC4576-4-4`](#rfc4576-4-4) "When the PE receives, from a CE router, a type 3, 5, or 7 LSA with the DN bit set, the information from that LSA MUST NOT be used during the OSPF route calculation." (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4576-4-4, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 4576, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4576, so its obligations are stated where they were written. --- ### Page: RFC 4577 - OSPF as the Provider/Customer Edge Protocol for BGP/MPLS IP Virtual Private Networks (VPNs) https://ze-software.net/quality/rfc-compliance/rfc4577/ # RFC 4577 - OSPF as the Provider/Customer Edge Protocol for BGP/MPLS IP Virtual Private Networks (VPNs) Not supported. Every requirement this repository extracted from RFC 4577, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 5.6% | 2 of 36 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 36 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 36 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 6 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 36 | of 56 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 18 | of 36 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 50.0% | 18 of 36 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 36 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 36 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 44.4% | 16 of 36 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 36 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Not supported | | Enrolment | Enrolled | | Requirements | 56 | | Gated MUST-level | 36 | | Not applicable, so out of scope | 18 | | Declared gaps | 20 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 6 | | Tagged units | 6 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4577.md` | | Requirement shard | `rfc/requirements/rfc4577.md` | | RFC text | `rfc/full/rfc4577.txt` | ## Enrolment Enrolled: OSPF as the PE/CE Protocol for BGP/MPLS IP VPNs ## What the public ledger says **Status:** Not supported **What the ledger says is covered** None of the PE-side procedures: ze has no VRF, no per-VRF OSPF instance, no Domain Identifier / OSPF Route Type / OSPF Router ID extended community, no DN-bit setting or checking, no VPN Route Tag and no sham link. What exists is the OSPF machinery the spec builds on: independent OSPF instances per RFC 6549 Instance ID ([`internal/plugins/ospf/multi_instance.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/multi_instance.go),68), Type 3 / Type 5 / Type 7 origination with a configurable external route tag ([`internal/plugins/ospf/redist_wiring.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/redist_wiring.go), [`internal/plugins/ospf/lsdb/origination.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/origination.go)), OSPF cryptographic authentication ([`internal/plugins/ospf/auth_keystore.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore.go), [`internal/plugins/ospf/packet/auth_verify.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify.go)), virtual links ([`internal/plugins/ospf/virtual_link.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link.go)), and an OSPF-to-BGP redistribution export carrying only route-table prefixes ([`internal/plugins/ospf/redistribute/source.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/redistribute/source.go)). Requirements bound per line in [`rfc/short/rfc4577.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4577.md). **What the ledger says remains** Twenty gaps, annotated in [`rfc/short/rfc4577.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4577.md) and gated by `./le rfc check`: [`RFC4577-4.1.1-1`](#rfc4577-4.1.1-1), [`RFC4577-4.2.1-1`](#rfc4577-4.2.1-1), [`RFC4577-4.2.1-2`](#rfc4577-4.2.1-2), [`RFC4577-4.2.4-1`](#rfc4577-4.2.4-1) and [`RFC4577-4.2.4-2`](#rfc4577-4.2.4-2) (independent instances exist, [`internal/plugins/ospf/multi_instance.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/multi_instance.go),68, but nothing associates one with an OSPF domain, a VRF or a Domain Identifier, and no configuration surface offers such an association); [`RFC4577-4.2.5.1-1`](#rfc4577-4.2.5.1-1), [`RFC4577-4.2.5.1-2`](#rfc4577-4.2.5.1-2) and [`RFC4577-4.2.8.1-1`](#rfc4577-4.2.8.1-1) (OptionDN is defined and encodable, [`internal/plugins/ospf/types/options.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/options.go), but no originator sets it); [`RFC4577-4.2.6-3`](#rfc4577-4.2.6-3) (no receive-side DN check, [`internal/plugins/ospf/spf/external.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external.go)); [`RFC4577-4.2.6-4`](#rfc4577-4.2.6-4) and [`RFC4577-4.2.5.2-7`](#rfc4577-4.2.5.2-7) (the External Route Tag is decoded, [`internal/plugins/ospf/packet/lsa_external.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/lsa_external.go), and is copied through by the NSSA Type 7 -> Type 5 translator, [`internal/plugins/ospf/nssa.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/nssa.go), but no receive-side producer ever tests it -- the route calculation never reads it, [`internal/plugins/ospf/spf/external.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/external.go)); [`RFC4577-4.2.5.1-3`](#rfc4577-4.2.5.1-3), [`RFC4577-4.2.5.2-2`](#rfc4577-4.2.5.2-2), [`RFC4577-4.2.5.2-3`](#rfc4577-4.2.5.2-3), [`RFC4577-4.2.5.2-6`](#rfc4577-4.2.5.2-6), [`RFC4577-4.2.5.2-8`](#rfc4577-4.2.5.2-8) and [`RFC4577-4.2.8.1-2`](#rfc4577-4.2.8.1-2) (a redistribution route tag is configurable and placed in Type 5 LSAs, [`internal/plugins/ospf/redist_wiring.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/redist_wiring.go),169-176, but it defaults to 0 and is not a VRF-scoped VPN Route Tag); [`RFC4577-4.2.5.2-9`](#rfc4577-4.2.5.2-9) (Type 5 origination makes ze an ASBR, LSDB.SelfIsASBR at [`internal/plugins/ospf/lsdb/origination.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/origination.go), but no domain test scopes it); [`RFC4577-4.2.6-8`](#rfc4577-4.2.6-8) (the OSPF distance reaches the redistribution event, [`internal/plugins/ospf/redistribute/source.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/redistribute/source.go), then is dropped before the BGP announcement, [`internal/component/bgp/plugins/redistribute_egress/redistribute.go`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/redistribute_egress/redistribute.go)); [`RFC4577-4.2.8.1-3`](#rfc4577-4.2.8.1-3) (one shared DefaultExternalMetric, [`internal/plugins/ospf/config.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config.go)). The remaining thirty-four requirements are recorded {not-applicable} with the grep proving no producer exists. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 34 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **36** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC4577-4.2.6-5`](#rfc4577-4.2.6-5), [`RFC4577-6-1`](#rfc4577-6-1) **Annotated instead of tested (34):** [`RFC4577-4.1.1-1`](#rfc4577-4.1.1-1), [`RFC4577-4.2.1-1`](#rfc4577-4.2.1-1), [`RFC4577-4.2.1-2`](#rfc4577-4.2.1-2), [`RFC4577-4.1.1-2`](#rfc4577-4.1.1-2), [`RFC4577-4.2.4-1`](#rfc4577-4.2.4-1), [`RFC4577-4.2.4-2`](#rfc4577-4.2.4-2), [`RFC4577-4.2.4-3`](#rfc4577-4.2.4-3), [`RFC4577-4.2.4-4`](#rfc4577-4.2.4-4), [`RFC4577-4.2.4-5`](#rfc4577-4.2.4-5), [`RFC4577-4.2.6-1`](#rfc4577-4.2.6-1), [`RFC4577-4.2.6-2`](#rfc4577-4.2.6-2), [`RFC4577-4.2.5.1-1`](#rfc4577-4.2.5.1-1), [`RFC4577-4.2.5.1-2`](#rfc4577-4.2.5.1-2), [`RFC4577-4.2.8.1-1`](#rfc4577-4.2.8.1-1), [`RFC4577-4.2.6-3`](#rfc4577-4.2.6-3), [`RFC4577-4.2.6-4`](#rfc4577-4.2.6-4), [`RFC4577-4.2.5.1-3`](#rfc4577-4.2.5.1-3), [`RFC4577-4.2.5.2-1`](#rfc4577-4.2.5.2-1), [`RFC4577-4.2.5.2-2`](#rfc4577-4.2.5.2-2), [`RFC4577-4.2.5.2-3`](#rfc4577-4.2.5.2-3), [`RFC4577-4.2.5.2-4`](#rfc4577-4.2.5.2-4), [`RFC4577-4.2.5.2-5`](#rfc4577-4.2.5.2-5), [`RFC4577-4.2.5.2-6`](#rfc4577-4.2.5.2-6), [`RFC4577-4.2.5.2-7`](#rfc4577-4.2.5.2-7), [`RFC4577-4.2.8.1-2`](#rfc4577-4.2.8.1-2), [`RFC4577-4.1.4-1`](#rfc4577-4.1.4-1), [`RFC4577-4.2.7.1-1`](#rfc4577-4.2.7.1-1), [`RFC4577-4.2.7.1-2`](#rfc4577-4.2.7.1-2), [`RFC4577-4.2.7.1-3`](#rfc4577-4.2.7.1-3), [`RFC4577-4.2.7.2-1`](#rfc4577-4.2.7.2-1), [`RFC4577-4.2.7.3-1`](#rfc4577-4.2.7.3-1), [`RFC4577-4.2.7.4-1`](#rfc4577-4.2.7.4-1), [`RFC4577-4.2.7.4-2`](#rfc4577-4.2.7.4-2), [`RFC4577-4.2.7.4-3`](#rfc4577-4.2.7.4-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4577-4.1.1-1` | A PE attaching to more than one OSPF domain must run an independent instance of OSPF for each domain (§4.1.1, §4.2.1) | MUST | 4.1.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze does run fully independent OSPF instances -- one complete engine per configured Instance ID with its own LSDB and neighbor table, demuxed by the shared dispatcher (installInstanceEncoders / recordInstanceMismatch, internal/plugins/ospf/multi_instance.go:33,68; per-instance engine skeleton instance.go) -- but an instance is keyed by the RFC 6549 Instance ID, never by an OSPF domain: ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355), so nothing binds an instance to a domain and a PE attached to two domains cannot separate them. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.1-1` | The PE must support one OSPF instance for each OSPF domain to which it attaches (§4.2.1) | MUST | 4.2.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the per-instance engine exists (multi_instance.go:33,68; instance.go) and several instances run side by side, but the instance set is derived from the configured Instance IDs (ospfConfig.instanceIDSet / forInstance, config.go), not from a set of OSPF domains, and ze has no OSPF-domain concept to derive it from: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355), and `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing. These four requirements share one premise and now share one verdict: ze DOES run OSPF instances (multi_instance.go:33,68; per-instance engine in instance.go), and each is an unconditional MUST to associate that existing instance with an OSPF domain, a VRF or a Domain Identifier. "ze never implemented the thing to associate with" is the unmet obligation itself, not a reason the obligation does not apply -- otherwise every unimplemented feature would read not-applicable and no gap could ever be reached. The conditional siblings (RFC4577-4.1.1-2, 4.2.4-3, 4.2.4-4, 4.2.4-5) stay not-applicable: they constrain a relationship BETWEEN associations that do not exist, so no producer can violate them. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.1-2` | Each OSPF instance must be associated with a single VRF (§4.2.1) | MUST | 4.2.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze runs OSPF instances but associates none of them with a VRF, because it has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355). These four requirements share one premise and now share one verdict: ze DOES run OSPF instances (multi_instance.go:33,68; per-instance engine in instance.go), and each is an unconditional MUST to associate that existing instance with an OSPF domain, a VRF or a Domain Identifier. "ze never implemented the thing to associate with" is the unmet obligation itself, not a reason the obligation does not apply -- otherwise every unimplemented feature would read not-applicable and no gap could ever be reached. The conditional siblings (RFC4577-4.1.1-2, 4.2.4-3, 4.2.4-4, 4.2.4-5) stay not-applicable: they constrain a relationship BETWEEN associations that do not exist, so no producer can violate them. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.1.1-2` | If two interfaces belong to the same OSPF instance, both interfaces must be associated with the same VRF (§4.1.1) | MUST | 4.1.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355); interfaces are enrolled into an area and an Instance ID (interfaceConfig, internal/plugins/ospf/config.go), never into a VRF | | `RFC4577-4.2.4-1` | Each OSPF instance must be associated with one or more Domain Identifiers (§4.2.4) | MUST | 4.2.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze runs OSPF instances but associates none of them with a Domain Identifier, because no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed | | `RFC4577-4.2.4-2` | Domain Identifier association must be configurable (§4.2.4) | MUST | 4.2.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** no configuration surface binds an OSPF instance to a Domain Identifier, because no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed; `grep -rniE "vpn\|domain\|sham" internal/plugins/ospf/yang/` returns nothing, so no such leaf is configurable | | `RFC4577-4.2.4-3` | If an OSPF instance has multiple Domain Identifiers, the primary one must be determinable by configuration (§4.2.4) | MUST | 4.2.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed, so there is no set of Domain Identifiers and no primary to select | | `RFC4577-4.2.4-4` | If an OSPF instance has more than one Domain Identifier, the NULL Domain Identifier must not be one of them (§4.2.4) | MUST NOT | 4.2.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed, so no instance can hold more than one | | `RFC4577-4.2.4-5` | If an OSPF instance has a non-NULL Domain Identifier, BGP-distributed VPN-IPv4 routes from it must carry the Domain Identifier Extended Communities attribute for the instance's primary Domain Identifier (§4.2.4) | MUST | 4.2.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed; additionally no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | `RFC4577-4.2.6-1` | The OSPF Domain Identifier Extended Communities attribute must be present on a PE-originated VPN-IPv4 route if the originating OSPF instance has a non-NULL primary Domain Identifier (§4.2.6) | MUST | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed; additionally no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | `RFC4577-4.2.6-2` | The OSPF Route Type Extended Communities attribute must be present on every PE-originated VPN-IPv4 OSPF route (§4.2.6) | MUST | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Route Type extended community exists -- `grep -rnE "0x0306\|0x8000" --include=*.go internal/core/bgp internal/component/bgp` returns no OSPF-related hit and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing; additionally no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | `RFC4577-4.2.5.1-1` | When a type 3 LSA is sent from a PE to a CE, the DN bit in the LSA Options field must be set (§4.2.5.1) | MUST | 4.2.5.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Type 3 Summary-LSA origination exists and takes an Options argument (LSDB.OriginateSummary, internal/plugins/ospf/lsdb/origination.go:391, header built at :408), but the caller passes the AREA's options (internal/plugins/ospf/spf/summary.go:102 passing in.Options[dst]) and the DN bit itself is defined and wire-codable (OptionDN, internal/plugins/ospf/types/options.go:31, encoded by Options.WriteTo :52), but `grep -rn OptionDN --include=*.go internal/plugins/ospf/` finds only the constant, its display name table and the neighbor detail renderer (neighbor_detail.go:70) -- no originator sets it, so a Type 3 LSA sent to a neighbor never carries DN. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.5.1-2` | When a PE distributes to a CE a route from outside the CE's OSPF domain (type 5 LSA), the DN bit must be set (§4.2.5.1) | MUST | 4.2.5.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Type 5 AS-External origination exists and takes an Options argument (LSDB.OriginateExternal, internal/plugins/ospf/lsdb/origination.go:421), but the redistribution caller passes types.OptionE alone (internal/plugins/ospf/redist_wiring.go:61) and the DN bit itself is defined and wire-codable (OptionDN, internal/plugins/ospf/types/options.go:31, encoded by Options.WriteTo :52), but `grep -rn OptionDN --include=*.go internal/plugins/ospf/` finds only the constant, its display name table and the neighbor detail renderer (neighbor_detail.go:70) -- no originator sets it. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.8.1-1` | The DN bit must be set in the (external) LSA reporting a route from a different domain (§4.2.8.1) | MUST | 4.2.8.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the external LSA that reports a redistributed route is originated with types.OptionE only (internal/plugins/ospf/redist_wiring.go:61 -> internal/plugins/ospf/lsdb/origination.go:421); the DN bit itself is defined and wire-codable (OptionDN, internal/plugins/ospf/types/options.go:31, encoded by Options.WriteTo :52), but `grep -rn OptionDN --include=*.go internal/plugins/ospf/` finds only the constant, its display name table and the neighbor detail renderer (neighbor_detail.go:70) -- no originator sets it, and there is no domain comparison to decide the LSA reports a different-domain route (no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed). Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.6-3` | When a PE receives from a CE any LSA with the DN bit set, the information from that LSA must not be used by the route calculation (§4.2.6) | MUST | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the receive path never consults the DN bit. The OSPFv2 external reader accepts any Type 5 / Type 7 LSA and inspects Options only for OptionNP (v4ExternalReader, internal/plugins/ospf/spf/external.go:131-161, the single Options read at :151); the OSPFv3 accessor PrefixOptions.Down() (internal/plugins/ospf/v3/types/prefix.go:69) has no non-test caller. A received LSA with DN set is used by the route calculation like any other. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.6-4` | If a Type 5 LSA received from the CE has an OSPF route tag equal to the VPN Route Tag, its information must not be used by the route calculation (§4.2.6) | MUST | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the External Route Tag is decoded off the wire into ExternalLSA.ExternalRouteTag (internal/plugins/ospf/packet/lsa_external.go:33) but the route calculation never reads it -- v4ExternalReader builds its ExternalRecord from prefix, metric, type and forwarding address only (internal/plugins/ospf/spf/external.go:131-161); the one receive-side consumer, the NSSA Type 7 -> Type 5 translator, copies the tag through rather than testing it (internal/plugins/ospf/nssa.go:226) -- and no VPN Route Tag value exists to compare against (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments and no code). Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.5.1-3` | All implementations adhering to this specification must by default support the VPN Route Tag procedures of Sections 4.2.5.2, 4.2.8.1, and 4.2.8.2 (§4.2.5.1) | MUST | 4.2.5.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the OSPF route tag machinery is present on both origination (externalParams -> OriginateExternal tag argument, internal/plugins/ospf/redist_wiring.go:169-176,61) and decode (internal/plugins/ospf/packet/lsa_external.go:33), but none of the sec 4.2.5.2 / 4.2.8.1 / 4.2.8.2 VPN Route Tag procedures are built on it: no default VPN tag, no receive-side tag suppression, and ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355). Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.5.2-1` | If a VRF is associated with an OSPF instance, by default it must be configured with a VPN Route Tag value (§4.2.5.2) | MUST | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355), so no VRF can be configured with a VPN Route Tag | | `RFC4577-4.2.5.2-2` | By default the VPN Route Tag must be included in the Type 5 LSAs the PE originates from BGP VPN-IPv4 routes and sends to attached CEs (§4.2.5.2) | MUST | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze does originate Type 5 LSAs from redistributed BGP routes and does place a route tag in them (internal/plugins/ospf/redist_wiring.go:61 -> internal/plugins/ospf/lsdb/origination.go:421 writing ExternalRouteTag), but the tag comes from the per-source redistribute configuration and defaults to 0 (externalParams, internal/plugins/ospf/redist_wiring.go:169-176) -- there is no VPN Route Tag default, and no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.5.2-3` | The VPN Route Tag value must be configurable (§4.2.5.2) | MUST | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** a route tag IS configurable, per redistribution source (redistributeConfig.Tag read by externalParams, internal/plugins/ospf/redist_wiring.go:169-176; parsed at config.go:1189-1190), but it is a redistribution route tag, not a VRF-scoped VPN Route Tag: ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355). Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.5.2-4` | If the VPN backbone AS number is four bytes long, a Route Tag value must be configured (§4.2.5.2) | MUST | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no VPN backbone AS number reaches the OSPF configuration and no VPN Route Tag exists (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments, options.go:30 and v3/types/prefix.go:53, and no code) | | `RFC4577-4.2.5.2-5` | A configured four-byte-AS Route Tag must be distinct from any Route Tag used within the VPN itself (§4.2.5.2) | MUST | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no VPN Route Tag and no VPN exist to be distinct from (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments and no code; ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355)) | | `RFC4577-4.2.5.2-6` | Each PE-originated Type 5 LSA for an extra-domain route must contain an OSPF route tag whose value is the VPN Route Tag (§4.2.5.2) | MUST | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the PE-originated Type 5 carries whatever tag the redistribute entry configures (internal/plugins/ospf/redist_wiring.go:61,169-176), defaulting to 0; no VPN Route Tag value is computed or stored, and no extra-domain test exists to condition it on (no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed). Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.5.2-7` | The VPN Route Tag must be used to ensure a Type 5 LSA originated by a PE is not redistributed to another PE (§4.2.5.2) | MUST | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** no receive-side producer FILTERS on a tag. A received tag is not entirely inert -- the NSSA translator copies a Type 7 body's ExternalRouteTag into the Type 5 it originates (internal/plugins/ospf/nssa.go:226) -- but it is never used as an acceptance test: the external reader builds its ExternalRecord from prefix, metric, type and forwarding address only and never reads ExternalRouteTag (internal/plugins/ospf/spf/external.go:131-161) even though the decoder fills it (internal/plugins/ospf/packet/lsa_external.go:33), so no tag value can stop a Type 5 LSA from being taken into the route calculation and re-exported by the redistribution source (internal/plugins/ospf/redistribute/source.go:99-137). Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.8.1-2` | The VPN Route Tag must be placed in the external LSA unless its use has been turned off by configuration (§4.2.8.1) | MUST | 4.2.8.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the external LSA is originated with the configured redistribution tag, which defaults to 0 rather than to a VPN Route Tag (externalParams, internal/plugins/ospf/redist_wiring.go:169-176 -> OriginateExternal, lsdb/origination.go:421), and there is no VPN Route Tag setting to turn off. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.6-5` | Routes that a PE receives in type 4 LSAs must not be redistributed to BGP (§4.2.6) | MUST NOT | 4.2.6 | **positive:** `unit/verify` [`TestRFC4577Type3SummaryBecomesRedistributableRoute`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc4577_test.go#L18). **negative:** `unit/verify` [`TestRFC4577Type4SummaryNotRedistributed`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc4577_test.go#L41) | | `RFC4577-4.1.4-1` | If the OSPF domain has any area 0 routers other than the PE routers, at least one must be a CE router and must have an area 0 link (possibly a virtual link) to at least one PE router (§4.1.4, §4.2.3) | MUST | 4.1.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this constrains the operator's OSPF domain topology around PE routers; ze models no PE or CE role -- interfaces are configured into an area with no provider/customer distinction (interfaceConfig, internal/plugins/ospf/config.go) and ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355) | | `RFC4577-4.2.7.1-1` | The Sham Link Endpoint Address associated with a VRF must be configurable (§4.2.7.1) | MUST | 4.2.7.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty) | | `RFC4577-4.2.7.1-2` | The Sham Link Endpoint Address must be distributed by BGP as a VPN-IPv4 address whose IPv4 prefix part is 32 bits long (§4.2.7.1) | MUST | 4.2.7.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty); and no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | `RFC4577-4.2.7.1-3` | The Sham Link Endpoint Address must not be advertised by OSPF (§4.2.7.1) | MUST NOT | 4.2.7.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so no such address can be advertised or withheld | | `RFC4577-4.2.7.2-1` | The sham link endpoint address must not be used as the endpoint address of an OSPF Virtual Link (§4.2.7.2) | MUST NOT | 4.2.7.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty); virtual links exist (internal/plugins/ospf/virtual_link.go) but there is no sham link endpoint address that could be offered as a virtual-link endpoint | | `RFC4577-4.2.7.3-1` | The OSPF metric associated with a sham link must be configurable, and there must be a configurable default (§4.2.7.3) | MUST | 4.2.7.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so there is no sham-link metric to configure or default | | `RFC4577-4.2.7.4-1` | Any route (other than one whose next hop is the sham link) advertised in an LSA transmitted over a sham link must also be redistributed into BGP (§4.2.7.4) | MUST | 4.2.7.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so no LSA is ever transmitted over one | | `RFC4577-4.2.7.4-2` | When forwarding a packet whose preferred route has the sham link as its next-hop interface, the packet must be forwarded according to the corresponding BGP route (§4.2.7.4) | MUST | 4.2.7.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so no route can have a sham link as its next-hop interface | | `RFC4577-4.2.7.4-3` | A packet whose IP destination is the remote endpoint address of a sham link must be forwarded according to the corresponding BGP route (§4.2.7.4) | MUST | 4.2.7.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty) | | `RFC4577-6-1` | OSPF cryptographic authentication must be implemented on each PE (§6) | MUST | 6 | **positive:** `unit/verify` [`TestRFC4577CryptographicAuthImplemented`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc4577_test.go#L22). **negative:** `unit/verify` [`TestRFC4577CryptographicAuthRejectsForgery`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc4577_test.go#L46) | | `RFC4577-4.2.4-6` | The default Domain Identifier value (if none is configured) should be NULL (§4.2.4) | SHOULD | 4.2.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed, so there is no value to default | | `RFC4577-4.2.5.2-8` | If the VPN backbone AS number is two bytes long, the default VPN Route Tag should be the automatically computed tag based on that AS number (§4.2.5.2) | SHOULD | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the tag default is the literal 0 set by externalParams (internal/plugins/ospf/redist_wiring.go:170), never a value computed from an AS number; the OSPF configuration carries no VPN backbone AS to compute it from (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments). Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.5.2-9` | A PE distributing to a CE a route from outside the CE's OSPF domain should present itself as an ASBR and should report such routes as AS-external routes (§4.2.5.2) | SHOULD | 4.2.5.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze does present itself as an ASBR and does report redistributed routes as AS-external routes -- the redistribution consumer originates Type 5 AS-External-LSAs (internal/plugins/ospf/redistribute/consumer.go InjectRoute -> redist_wiring.go:61) and the self-originated-external index drives the Router-LSA E-bit (LSDB.SelfIsASBR, internal/plugins/ospf/lsdb/origination.go:518) -- but the condition this SHOULD is scoped to, the route coming from outside the CE's OSPF domain, has no producer: no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.6-6` | For backward compatibility, OSPF Route Type type 8000 should be accepted and treated as 0306 (§4.2.6) | SHOULD | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Route Type extended community is parsed at all -- `grep -rnE "0x0306\|0x8000" --include=*.go internal/core/bgp internal/component/bgp` returns no OSPF-related hit -- so there is no 0306 handling for 8000 to be mapped onto | | `RFC4577-4.2.6-7` | For backward compatibility, OSPF Router ID type 8001 should be accepted and treated as 0107 (§4.2.6) | SHOULD | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Router ID extended community is parsed at all -- `grep -rnE "0x0107\|0x8001" --include=*.go internal/core/bgp internal/component/bgp` returns no OSPF-related hit | | `RFC4577-4.2.6-8` | The MED of a PE-originated VPN-IPv4 OSPF route should by default be set to the OSPF distance of the route plus 1 (§4.2.6) | SHOULD | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the OSPF distance IS carried out of the SPF route table into the redistribution event (addEntry / metricToUint32, internal/plugins/ospf/redistribute/source.go:127-143), but the BGP egress consumer drops it: dispatchEntryToConsumer builds a configredist.RouteEntry from Prefix, NextHop, Source, Peer, OriginASN and Community only (internal/component/bgp/plugins/redistribute_egress/redistribute.go:263-270) and RouteEntry has no MED or metric field (internal/component/config/redistribute/consumer.go:28-54), so no MED is derived from the OSPF distance for any redistributed OSPF route. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.2.7.3-2` | Sham links should be treated by OSPF as OSPF Demand Circuits (§4.2.7.3) | SHOULD | 4.2.7.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty); the DoNotAge machinery exists (LSAge.DoNotAge, internal/plugins/ospf/types/lsage.go:48, honored in lsdb/entry.go:88) but no sham link exists to treat as a demand circuit | | `RFC4577-4.2.7.4-4` | If a PE determines the next hop interface for a route is a sham link, it should not redistribute that route into BGP as a VPN-IPv4 route (§4.2.7.4) | SHOULD NOT | 4.2.7.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so no next-hop interface can be one | | `RFC4577-6-2` | OSPF cryptographic authentication should be used between a PE and a CE (§6) | SHOULD | 6 | **positive:** `unit/verify` [`TestRFC4577InterfaceUsesConfiguredCryptoAuth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc4577_test.go#L69). **negative:** no negative test. **{single-polarity}:** the negative -- an interface with no key chain sending unauthenticated OSPF packets -- is the state this SHOULD recommends against, not a behavior the implementation must exhibit, so asserting it would pin the unrecommended path instead of the requirement | | `RFC4577-4.2.4-7` | If the OSPF instance's Domain Identifier is NULL, the Domain Identifier Extended Communities attribute may be omitted from BGP-distributed routes (§4.2.4) | MAY | 4.2.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed | | `RFC4577-4.2.4-8` | Alternatively, a Domain Identifier Extended Communities attribute value representing NULL may be carried with the route (§4.2.4) | MAY | 4.2.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed | | `RFC4577-4.2.6-9` | If the OSPF instance has only a NULL Domain Identifier, the OSPF Domain Identifier Extended Communities attribute may be omitted from a PE-originated route (§4.2.6) | MAY | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed | | `RFC4577-4.2.6-10` | For backward compatibility, the OSPF Domain Identifier type 8005 may be used and is treated as if it were 0005 (§4.2.6) | MAY | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed, so neither 0005 nor 8005 is ever emitted or read | | `RFC4577-4.2.8.1-3` | The default type 1 metric and the default type 2 metric may be different (§4.2.8.1) | MAY | 4.2.8.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** there is a single default external metric constant, DefaultExternalMetric = 20 (internal/plugins/ospf/config.go:34), applied by externalParams regardless of metric type (internal/plugins/ospf/redist_wiring.go:170) and by the config parser (config.go:1179), so the type 1 and type 2 defaults cannot differ. Disclosed in docs/features/rfc-status.md RFC 4577 row | | `RFC4577-4.1.4-2` | The CE-to-PE area 0 adjacency may be via an OSPF virtual link (OPTIONAL feature) (§4.1.4, §4.2.3) | MAY | 4.1.4 | **positive:** `unit/verify` [`TestRFC4577VirtualLinkGivesArea0Adjacency`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc4577_test.go#L92). **negative:** no negative test. **{single-polarity}:** this is a MAY, the permission to reach the area 0 adjacency over a virtual link. A negative would have to assert that some configuration does NOT yield a backbone virtual link, which exercises virtual-link configuration handling rather than the permission this requirement grants | | `RFC4577-4.2.5.1-4` | When the VPN Route Tag is no longer needed for backward compatibility, its use (sending and receiving) may be disabled by configuration (§4.2.5.1, §4.2.5.2) | MAY | 4.2.5.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no VPN Route Tag exists to enable or disable (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments and no code; the only configurable tag is the redistribution route tag, internal/plugins/ospf/redist_wiring.go:169-176) | | `RFC4577-4.1.3-1` | Sham links are an OPTIONAL feature of this specification (§4.1.3, §4.2.7) | OPTIONAL | 4.1.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty) | | `RFC4577-4.2.6-11` | The OSPF Router ID Extended Communities attribute is an OPTIONAL attribute (§4.2.6) | OPTIONAL | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** no OSPF Router ID extended community exists -- `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing | | `RFC4577-4.2.6-12` | The Site of Origin attribute is OPTIONAL for routes a PE learns from a CE via OSPF (§4.2.6) | OPTIONAL | 4.2.6 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** a Site of Origin extended community can be built from a text route specification (internal/core/bgp/attribute/builder_parse.go:264), but no PE learns routes from a CE via OSPF: ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355), and no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | `RFC4577-4.2.7.1-4` | If a VRF is associated with a single OSPF instance and the PE's router id there is an IP address, the Sham Link Endpoint Address may default to that Router ID (§4.2.7.1) | MAY | 4.2.7.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so there is no endpoint address to default from a router ID | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4577-4.1.1-1`](#rfc4577-4.1.1-1) A PE attaching to more than one OSPF domain must run an independent instance of OSPF for each domain (§4.1.1, §4.2.1) | {gap}, no test | ze does run fully independent OSPF instances -- one complete engine per configured Instance ID with its own LSDB and neighbor table, demuxed by the shared dispatcher (installInstanceEncoders / recordInstanceMismatch, internal/plugins/ospf/multi_instance.go:33,68; per-instance engine skeleton instance.go) -- but an instance is keyed by the RFC 6549 Instance ID, never by an OSPF domain: ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355), so nothing binds an instance to a domain and a PE attached to two domains cannot separate them. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.1-1`](#rfc4577-4.2.1-1) The PE must support one OSPF instance for each OSPF domain to which it attaches (§4.2.1) | {gap}, no test | the per-instance engine exists (multi_instance.go:33,68; instance.go) and several instances run side by side, but the instance set is derived from the configured Instance IDs (ospfConfig.instanceIDSet / forInstance, config.go), not from a set of OSPF domains, and ze has no OSPF-domain concept to derive it from: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355), and `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing. These four requirements share one premise and now share one verdict: ze DOES run OSPF instances (multi_instance.go:33,68; per-instance engine in instance.go), and each is an unconditional MUST to associate that existing instance with an OSPF domain, a VRF or a Domain Identifier. "ze never implemented the thing to associate with" is the unmet obligation itself, not a reason the obligation does not apply -- otherwise every unimplemented feature would read not-applicable and no gap could ever be reached. The conditional siblings (RFC4577-4.1.1-2, 4.2.4-3, 4.2.4-4, 4.2.4-5) stay not-applicable: they constrain a relationship BETWEEN associations that do not exist, so no producer can violate them. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.1-2`](#rfc4577-4.2.1-2) Each OSPF instance must be associated with a single VRF (§4.2.1) | {gap}, no test | ze runs OSPF instances but associates none of them with a VRF, because it has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355). These four requirements share one premise and now share one verdict: ze DOES run OSPF instances (multi_instance.go:33,68; per-instance engine in instance.go), and each is an unconditional MUST to associate that existing instance with an OSPF domain, a VRF or a Domain Identifier. "ze never implemented the thing to associate with" is the unmet obligation itself, not a reason the obligation does not apply -- otherwise every unimplemented feature would read not-applicable and no gap could ever be reached. The conditional siblings (RFC4577-4.1.1-2, 4.2.4-3, 4.2.4-4, 4.2.4-5) stay not-applicable: they constrain a relationship BETWEEN associations that do not exist, so no producer can violate them. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.1.1-2`](#rfc4577-4.1.1-2) If two interfaces belong to the same OSPF instance, both interfaces must be associated with the same VRF (§4.1.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355); interfaces are enrolled into an area and an Instance ID (interfaceConfig, internal/plugins/ospf/config.go), never into a VRF | | [`RFC4577-4.2.4-1`](#rfc4577-4.2.4-1) Each OSPF instance must be associated with one or more Domain Identifiers (§4.2.4) | {gap}, no test | ze runs OSPF instances but associates none of them with a Domain Identifier, because no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed | | [`RFC4577-4.2.4-2`](#rfc4577-4.2.4-2) Domain Identifier association must be configurable (§4.2.4) | {gap}, no test | no configuration surface binds an OSPF instance to a Domain Identifier, because no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed; `grep -rniE "vpn\|domain\|sham" internal/plugins/ospf/yang/` returns nothing, so no such leaf is configurable | | [`RFC4577-4.2.4-3`](#rfc4577-4.2.4-3) If an OSPF instance has multiple Domain Identifiers, the primary one must be determinable by configuration (§4.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed, so there is no set of Domain Identifiers and no primary to select | | [`RFC4577-4.2.4-4`](#rfc4577-4.2.4-4) If an OSPF instance has more than one Domain Identifier, the NULL Domain Identifier must not be one of them (§4.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed, so no instance can hold more than one | | [`RFC4577-4.2.4-5`](#rfc4577-4.2.4-5) If an OSPF instance has a non-NULL Domain Identifier, BGP-distributed VPN-IPv4 routes from it must carry the Domain Identifier Extended Communities attribute for the instance's primary Domain Identifier (§4.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed; additionally no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | [`RFC4577-4.2.6-1`](#rfc4577-4.2.6-1) The OSPF Domain Identifier Extended Communities attribute must be present on a PE-originated VPN-IPv4 route if the originating OSPF instance has a non-NULL primary Domain Identifier (§4.2.6) | no test | no test carries this requirement id; annotated {not-applicable}: no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed; additionally no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | [`RFC4577-4.2.6-2`](#rfc4577-4.2.6-2) The OSPF Route Type Extended Communities attribute must be present on every PE-originated VPN-IPv4 OSPF route (§4.2.6) | no test | no test carries this requirement id; annotated {not-applicable}: no OSPF Route Type extended community exists -- `grep -rnE "0x0306\|0x8000" --include=*.go internal/core/bgp internal/component/bgp` returns no OSPF-related hit and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing; additionally no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | [`RFC4577-4.2.5.1-1`](#rfc4577-4.2.5.1-1) When a type 3 LSA is sent from a PE to a CE, the DN bit in the LSA Options field must be set (§4.2.5.1) | {gap}, no test | Type 3 Summary-LSA origination exists and takes an Options argument (LSDB.OriginateSummary, internal/plugins/ospf/lsdb/origination.go:391, header built at :408), but the caller passes the AREA's options (internal/plugins/ospf/spf/summary.go:102 passing in.Options[dst]) and the DN bit itself is defined and wire-codable (OptionDN, internal/plugins/ospf/types/options.go:31, encoded by Options.WriteTo :52), but `grep -rn OptionDN --include=*.go internal/plugins/ospf/` finds only the constant, its display name table and the neighbor detail renderer (neighbor_detail.go:70) -- no originator sets it, so a Type 3 LSA sent to a neighbor never carries DN. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.5.1-2`](#rfc4577-4.2.5.1-2) When a PE distributes to a CE a route from outside the CE's OSPF domain (type 5 LSA), the DN bit must be set (§4.2.5.1) | {gap}, no test | Type 5 AS-External origination exists and takes an Options argument (LSDB.OriginateExternal, internal/plugins/ospf/lsdb/origination.go:421), but the redistribution caller passes types.OptionE alone (internal/plugins/ospf/redist_wiring.go:61) and the DN bit itself is defined and wire-codable (OptionDN, internal/plugins/ospf/types/options.go:31, encoded by Options.WriteTo :52), but `grep -rn OptionDN --include=*.go internal/plugins/ospf/` finds only the constant, its display name table and the neighbor detail renderer (neighbor_detail.go:70) -- no originator sets it. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.8.1-1`](#rfc4577-4.2.8.1-1) The DN bit must be set in the (external) LSA reporting a route from a different domain (§4.2.8.1) | {gap}, no test | the external LSA that reports a redistributed route is originated with types.OptionE only (internal/plugins/ospf/redist_wiring.go:61 -> internal/plugins/ospf/lsdb/origination.go:421); the DN bit itself is defined and wire-codable (OptionDN, internal/plugins/ospf/types/options.go:31, encoded by Options.WriteTo :52), but `grep -rn OptionDN --include=*.go internal/plugins/ospf/` finds only the constant, its display name table and the neighbor detail renderer (neighbor_detail.go:70) -- no originator sets it, and there is no domain comparison to decide the LSA reports a different-domain route (no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed). Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.6-3`](#rfc4577-4.2.6-3) When a PE receives from a CE any LSA with the DN bit set, the information from that LSA must not be used by the route calculation (§4.2.6) | {gap}, no test | the receive path never consults the DN bit. The OSPFv2 external reader accepts any Type 5 / Type 7 LSA and inspects Options only for OptionNP (v4ExternalReader, internal/plugins/ospf/spf/external.go:131-161, the single Options read at :151); the OSPFv3 accessor PrefixOptions.Down() (internal/plugins/ospf/v3/types/prefix.go:69) has no non-test caller. A received LSA with DN set is used by the route calculation like any other. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.6-4`](#rfc4577-4.2.6-4) If a Type 5 LSA received from the CE has an OSPF route tag equal to the VPN Route Tag, its information must not be used by the route calculation (§4.2.6) | {gap}, no test | the External Route Tag is decoded off the wire into ExternalLSA.ExternalRouteTag (internal/plugins/ospf/packet/lsa_external.go:33) but the route calculation never reads it -- v4ExternalReader builds its ExternalRecord from prefix, metric, type and forwarding address only (internal/plugins/ospf/spf/external.go:131-161); the one receive-side consumer, the NSSA Type 7 -> Type 5 translator, copies the tag through rather than testing it (internal/plugins/ospf/nssa.go:226) -- and no VPN Route Tag value exists to compare against (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments and no code). Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.5.1-3`](#rfc4577-4.2.5.1-3) All implementations adhering to this specification must by default support the VPN Route Tag procedures of Sections 4.2.5.2, 4.2.8.1, and 4.2.8.2 (§4.2.5.1) | {gap}, no test | the OSPF route tag machinery is present on both origination (externalParams -> OriginateExternal tag argument, internal/plugins/ospf/redist_wiring.go:169-176,61) and decode (internal/plugins/ospf/packet/lsa_external.go:33), but none of the sec 4.2.5.2 / 4.2.8.1 / 4.2.8.2 VPN Route Tag procedures are built on it: no default VPN tag, no receive-side tag suppression, and ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355). Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.5.2-1`](#rfc4577-4.2.5.2-1) If a VRF is associated with an OSPF instance, by default it must be configured with a VPN Route Tag value (§4.2.5.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355), so no VRF can be configured with a VPN Route Tag | | [`RFC4577-4.2.5.2-2`](#rfc4577-4.2.5.2-2) By default the VPN Route Tag must be included in the Type 5 LSAs the PE originates from BGP VPN-IPv4 routes and sends to attached CEs (§4.2.5.2) | {gap}, no test | ze does originate Type 5 LSAs from redistributed BGP routes and does place a route tag in them (internal/plugins/ospf/redist_wiring.go:61 -> internal/plugins/ospf/lsdb/origination.go:421 writing ExternalRouteTag), but the tag comes from the per-source redistribute configuration and defaults to 0 (externalParams, internal/plugins/ospf/redist_wiring.go:169-176) -- there is no VPN Route Tag default, and no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.5.2-3`](#rfc4577-4.2.5.2-3) The VPN Route Tag value must be configurable (§4.2.5.2) | {gap}, no test | a route tag IS configurable, per redistribution source (redistributeConfig.Tag read by externalParams, internal/plugins/ospf/redist_wiring.go:169-176; parsed at config.go:1189-1190), but it is a redistribution route tag, not a VRF-scoped VPN Route Tag: ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355). Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.5.2-4`](#rfc4577-4.2.5.2-4) If the VPN backbone AS number is four bytes long, a Route Tag value must be configured (§4.2.5.2) | no test | no test carries this requirement id; annotated {not-applicable}: no VPN backbone AS number reaches the OSPF configuration and no VPN Route Tag exists (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments, options.go:30 and v3/types/prefix.go:53, and no code) | | [`RFC4577-4.2.5.2-5`](#rfc4577-4.2.5.2-5) A configured four-byte-AS Route Tag must be distinct from any Route Tag used within the VPN itself (§4.2.5.2) | no test | no test carries this requirement id; annotated {not-applicable}: no VPN Route Tag and no VPN exist to be distinct from (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments and no code; ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355)) | | [`RFC4577-4.2.5.2-6`](#rfc4577-4.2.5.2-6) Each PE-originated Type 5 LSA for an extra-domain route must contain an OSPF route tag whose value is the VPN Route Tag (§4.2.5.2) | {gap}, no test | the PE-originated Type 5 carries whatever tag the redistribute entry configures (internal/plugins/ospf/redist_wiring.go:61,169-176), defaulting to 0; no VPN Route Tag value is computed or stored, and no extra-domain test exists to condition it on (no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed). Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.5.2-7`](#rfc4577-4.2.5.2-7) The VPN Route Tag must be used to ensure a Type 5 LSA originated by a PE is not redistributed to another PE (§4.2.5.2) | {gap}, no test | no receive-side producer FILTERS on a tag. A received tag is not entirely inert -- the NSSA translator copies a Type 7 body's ExternalRouteTag into the Type 5 it originates (internal/plugins/ospf/nssa.go:226) -- but it is never used as an acceptance test: the external reader builds its ExternalRecord from prefix, metric, type and forwarding address only and never reads ExternalRouteTag (internal/plugins/ospf/spf/external.go:131-161) even though the decoder fills it (internal/plugins/ospf/packet/lsa_external.go:33), so no tag value can stop a Type 5 LSA from being taken into the route calculation and re-exported by the redistribution source (internal/plugins/ospf/redistribute/source.go:99-137). Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.8.1-2`](#rfc4577-4.2.8.1-2) The VPN Route Tag must be placed in the external LSA unless its use has been turned off by configuration (§4.2.8.1) | {gap}, no test | the external LSA is originated with the configured redistribution tag, which defaults to 0 rather than to a VPN Route Tag (externalParams, internal/plugins/ospf/redist_wiring.go:169-176 -> OriginateExternal, lsdb/origination.go:421), and there is no VPN Route Tag setting to turn off. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.1.4-1`](#rfc4577-4.1.4-1) If the OSPF domain has any area 0 routers other than the PE routers, at least one must be a CE router and must have an area 0 link (possibly a virtual link) to at least one PE router (§4.1.4, §4.2.3) | no test | no test carries this requirement id; annotated {not-applicable}: this constrains the operator's OSPF domain topology around PE routers; ze models no PE or CE role -- interfaces are configured into an area with no provider/customer distinction (interfaceConfig, internal/plugins/ospf/config.go) and ze has no VRF model at all: `grep -rni vrf --include=*.go internal/plugins/ospf/` returns nothing, and no VRF table, import/export engine or per-VRF instance exists anywhere (the only `vrf` spellings under internal/component/bgp are flowspec redirect-to-VRF comments, route_community.go:82,183,355) | | [`RFC4577-4.2.7.1-1`](#rfc4577-4.2.7.1-1) The Sham Link Endpoint Address associated with a VRF must be configurable (§4.2.7.1) | no test | no test carries this requirement id; annotated {not-applicable}: `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty) | | [`RFC4577-4.2.7.1-2`](#rfc4577-4.2.7.1-2) The Sham Link Endpoint Address must be distributed by BGP as a VPN-IPv4 address whose IPv4 prefix part is 32 bits long (§4.2.7.1) | no test | no test carries this requirement id; annotated {not-applicable}: `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty); and no PE-originated VPN-IPv4 OSPF route exists: OSPF exports plain IPv4 unicast prefixes into the redistribution bus (emitDelta sets AFI ipv4 / SAFI unicast, internal/plugins/ospf/redistribute/source.go:105-107) and nothing anywhere turns an OSPF route into a VPN-IPv4 (SAFI 128) route | | [`RFC4577-4.2.7.1-3`](#rfc4577-4.2.7.1-3) The Sham Link Endpoint Address must not be advertised by OSPF (§4.2.7.1) | no test | no test carries this requirement id; annotated {not-applicable}: `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so no such address can be advertised or withheld | | [`RFC4577-4.2.7.2-1`](#rfc4577-4.2.7.2-1) The sham link endpoint address must not be used as the endpoint address of an OSPF Virtual Link (§4.2.7.2) | no test | no test carries this requirement id; annotated {not-applicable}: `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty); virtual links exist (internal/plugins/ospf/virtual_link.go) but there is no sham link endpoint address that could be offered as a virtual-link endpoint | | [`RFC4577-4.2.7.3-1`](#rfc4577-4.2.7.3-1) The OSPF metric associated with a sham link must be configurable, and there must be a configurable default (§4.2.7.3) | no test | no test carries this requirement id; annotated {not-applicable}: `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so there is no sham-link metric to configure or default | | [`RFC4577-4.2.7.4-1`](#rfc4577-4.2.7.4-1) Any route (other than one whose next hop is the sham link) advertised in an LSA transmitted over a sham link must also be redistributed into BGP (§4.2.7.4) | no test | no test carries this requirement id; annotated {not-applicable}: `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so no LSA is ever transmitted over one | | [`RFC4577-4.2.7.4-2`](#rfc4577-4.2.7.4-2) When forwarding a packet whose preferred route has the sham link as its next-hop interface, the packet must be forwarded according to the corresponding BGP route (§4.2.7.4) | no test | no test carries this requirement id; annotated {not-applicable}: `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty), so no route can have a sham link as its next-hop interface | | [`RFC4577-4.2.7.4-3`](#rfc4577-4.2.7.4-3) A packet whose IP destination is the remote endpoint address of a sham link must be forwarded according to the corresponding BGP route (§4.2.7.4) | no test | no test carries this requirement id; annotated {not-applicable}: `grep -rni sham --include=*.go internal/` returns nothing: no sham link, no Sham Link Endpoint Address, no sham-link forwarding, and no VRF to relate two of them (`grep -rni vrf --include=*.go internal/plugins/ospf/` is also empty) | | [`RFC4577-4.2.5.2-8`](#rfc4577-4.2.5.2-8) If the VPN backbone AS number is two bytes long, the default VPN Route Tag should be the automatically computed tag based on that AS number (§4.2.5.2) | {gap} | the tag default is the literal 0 set by externalParams (internal/plugins/ospf/redist_wiring.go:170), never a value computed from an AS number; the OSPF configuration carries no VPN backbone AS to compute it from (`grep -rni vpn --include=*.go internal/plugins/ospf/` returns two comments). Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.5.2-9`](#rfc4577-4.2.5.2-9) A PE distributing to a CE a route from outside the CE's OSPF domain should present itself as an ASBR and should report such routes as AS-external routes (§4.2.5.2) | {gap} | ze does present itself as an ASBR and does report redistributed routes as AS-external routes -- the redistribution consumer originates Type 5 AS-External-LSAs (internal/plugins/ospf/redistribute/consumer.go InjectRoute -> redist_wiring.go:61) and the self-originated-external index drives the Router-LSA E-bit (LSDB.SelfIsASBR, internal/plugins/ospf/lsdb/origination.go:518) -- but the condition this SHOULD is scoped to, the route coming from outside the CE's OSPF domain, has no producer: no OSPF Domain Identifier exists: `grep -rniE "domain.identifier\|domain-id\|domainid" --include=*.go internal/plugins/ospf internal/core/bgp internal/component/bgp` returns nothing, and `grep -rni ospf --include=*.go internal/core/bgp/attribute internal/component/bgp/route` returns nothing, so no OSPF extended community is ever built or parsed. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.6-8`](#rfc4577-4.2.6-8) The MED of a PE-originated VPN-IPv4 OSPF route should by default be set to the OSPF distance of the route plus 1 (§4.2.6) | {gap} | the OSPF distance IS carried out of the SPF route table into the redistribution event (addEntry / metricToUint32, internal/plugins/ospf/redistribute/source.go:127-143), but the BGP egress consumer drops it: dispatchEntryToConsumer builds a configredist.RouteEntry from Prefix, NextHop, Source, Peer, OriginASN and Community only (internal/component/bgp/plugins/redistribute_egress/redistribute.go:263-270) and RouteEntry has no MED or metric field (internal/component/config/redistribute/consumer.go:28-54), so no MED is derived from the OSPF distance for any redistributed OSPF route. Disclosed in docs/features/rfc-status.md RFC 4577 row | | [`RFC4577-4.2.8.1-3`](#rfc4577-4.2.8.1-3) The default type 1 metric and the default type 2 metric may be different (§4.2.8.1) | {gap} | there is a single default external metric constant, DefaultExternalMetric = 20 (internal/plugins/ospf/config.go:34), applied by externalParams regardless of metric type (internal/plugins/ospf/redist_wiring.go:170) and by the config parser (config.go:1179), so the type 1 and type 2 defaults cannot differ. Disclosed in docs/features/rfc-status.md RFC 4577 row | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4577-4.1.1-1`](#rfc4577-4.1.1-1) A PE attaching to more than one OSPF domain must run an independent instance of OSPF for each domain (§4.1.1, §4.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.1.1-1, so no unit is bound to it. ### [`RFC4577-4.2.1-1`](#rfc4577-4.2.1-1) The PE must support one OSPF instance for each OSPF domain to which it attaches (§4.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.1-1, so no unit is bound to it. ### [`RFC4577-4.2.1-2`](#rfc4577-4.2.1-2) Each OSPF instance must be associated with a single VRF (§4.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.1-2, so no unit is bound to it. ### [`RFC4577-4.1.1-2`](#rfc4577-4.1.1-2) If two interfaces belong to the same OSPF instance, both interfaces must be associated with the same VRF (§4.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.1.1-2, so no unit is bound to it. ### [`RFC4577-4.2.4-1`](#rfc4577-4.2.4-1) Each OSPF instance must be associated with one or more Domain Identifiers (§4.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.4-1, so no unit is bound to it. ### [`RFC4577-4.2.4-2`](#rfc4577-4.2.4-2) Domain Identifier association must be configurable (§4.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.4-2, so no unit is bound to it. ### [`RFC4577-4.2.4-3`](#rfc4577-4.2.4-3) If an OSPF instance has multiple Domain Identifiers, the primary one must be determinable by configuration (§4.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.4-3, so no unit is bound to it. ### [`RFC4577-4.2.4-4`](#rfc4577-4.2.4-4) If an OSPF instance has more than one Domain Identifier, the NULL Domain Identifier must not be one of them (§4.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.4-4, so no unit is bound to it. ### [`RFC4577-4.2.4-5`](#rfc4577-4.2.4-5) If an OSPF instance has a non-NULL Domain Identifier, BGP-distributed VPN-IPv4 routes from it must carry the Domain Identifier Extended Communities attribute for the instance's primary Domain Identifier (§4.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.4-5, so no unit is bound to it. ### [`RFC4577-4.2.6-1`](#rfc4577-4.2.6-1) The OSPF Domain Identifier Extended Communities attribute must be present on a PE-originated VPN-IPv4 route if the originating OSPF instance has a non-NULL primary Domain Identifier (§4.2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.6-1, so no unit is bound to it. ### [`RFC4577-4.2.6-2`](#rfc4577-4.2.6-2) The OSPF Route Type Extended Communities attribute must be present on every PE-originated VPN-IPv4 OSPF route (§4.2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.6-2, so no unit is bound to it. ### [`RFC4577-4.2.5.1-1`](#rfc4577-4.2.5.1-1) When a type 3 LSA is sent from a PE to a CE, the DN bit in the LSA Options field must be set (§4.2.5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.1-1, so no unit is bound to it. ### [`RFC4577-4.2.5.1-2`](#rfc4577-4.2.5.1-2) When a PE distributes to a CE a route from outside the CE's OSPF domain (type 5 LSA), the DN bit must be set (§4.2.5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.1-2, so no unit is bound to it. ### [`RFC4577-4.2.8.1-1`](#rfc4577-4.2.8.1-1) The DN bit must be set in the (external) LSA reporting a route from a different domain (§4.2.8.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.8.1-1, so no unit is bound to it. ### [`RFC4577-4.2.6-3`](#rfc4577-4.2.6-3) When a PE receives from a CE any LSA with the DN bit set, the information from that LSA must not be used by the route calculation (§4.2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.6-3, so no unit is bound to it. ### [`RFC4577-4.2.6-4`](#rfc4577-4.2.6-4) If a Type 5 LSA received from the CE has an OSPF route tag equal to the VPN Route Tag, its information must not be used by the route calculation (§4.2.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.6-4, so no unit is bound to it. ### [`RFC4577-4.2.5.1-3`](#rfc4577-4.2.5.1-3) All implementations adhering to this specification must by default support the VPN Route Tag procedures of Sections 4.2.5.2, 4.2.8.1, and 4.2.8.2 (§4.2.5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.1-3, so no unit is bound to it. ### [`RFC4577-4.2.5.2-1`](#rfc4577-4.2.5.2-1) If a VRF is associated with an OSPF instance, by default it must be configured with a VPN Route Tag value (§4.2.5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.2-1, so no unit is bound to it. ### [`RFC4577-4.2.5.2-2`](#rfc4577-4.2.5.2-2) By default the VPN Route Tag must be included in the Type 5 LSAs the PE originates from BGP VPN-IPv4 routes and sends to attached CEs (§4.2.5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.2-2, so no unit is bound to it. ### [`RFC4577-4.2.5.2-3`](#rfc4577-4.2.5.2-3) The VPN Route Tag value must be configurable (§4.2.5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.2-3, so no unit is bound to it. ### [`RFC4577-4.2.5.2-4`](#rfc4577-4.2.5.2-4) If the VPN backbone AS number is four bytes long, a Route Tag value must be configured (§4.2.5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.2-4, so no unit is bound to it. ### [`RFC4577-4.2.5.2-5`](#rfc4577-4.2.5.2-5) A configured four-byte-AS Route Tag must be distinct from any Route Tag used within the VPN itself (§4.2.5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.2-5, so no unit is bound to it. ### [`RFC4577-4.2.5.2-6`](#rfc4577-4.2.5.2-6) Each PE-originated Type 5 LSA for an extra-domain route must contain an OSPF route tag whose value is the VPN Route Tag (§4.2.5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.2-6, so no unit is bound to it. ### [`RFC4577-4.2.5.2-7`](#rfc4577-4.2.5.2-7) The VPN Route Tag must be used to ensure a Type 5 LSA originated by a PE is not redistributed to another PE (§4.2.5.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.5.2-7, so no unit is bound to it. ### [`RFC4577-4.2.8.1-2`](#rfc4577-4.2.8.1-2) The VPN Route Tag must be placed in the external LSA unless its use has been turned off by configuration (§4.2.8.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.8.1-2, so no unit is bound to it. ### [`RFC4577-4.2.6-5`](#rfc4577-4.2.6-5) Routes that a PE receives in type 4 LSAs must not be redistributed to BGP (§4.2.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4577Type4SummaryNotRedistributed`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc4577_test.go#L41) | unit/verify | unproven | | positive | [`TestRFC4577Type3SummaryBecomesRedistributableRoute`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc4577_test.go#L18) | unit/verify | unproven | ### [`RFC4577-4.1.4-1`](#rfc4577-4.1.4-1) If the OSPF domain has any area 0 routers other than the PE routers, at least one must be a CE router and must have an area 0 link (possibly a virtual link) to at least one PE router (§4.1.4, §4.2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.1.4-1, so no unit is bound to it. ### [`RFC4577-4.2.7.1-1`](#rfc4577-4.2.7.1-1) The Sham Link Endpoint Address associated with a VRF must be configurable (§4.2.7.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.7.1-1, so no unit is bound to it. ### [`RFC4577-4.2.7.1-2`](#rfc4577-4.2.7.1-2) The Sham Link Endpoint Address must be distributed by BGP as a VPN-IPv4 address whose IPv4 prefix part is 32 bits long (§4.2.7.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.7.1-2, so no unit is bound to it. ### [`RFC4577-4.2.7.1-3`](#rfc4577-4.2.7.1-3) The Sham Link Endpoint Address must not be advertised by OSPF (§4.2.7.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.7.1-3, so no unit is bound to it. ### [`RFC4577-4.2.7.2-1`](#rfc4577-4.2.7.2-1) The sham link endpoint address must not be used as the endpoint address of an OSPF Virtual Link (§4.2.7.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.7.2-1, so no unit is bound to it. ### [`RFC4577-4.2.7.3-1`](#rfc4577-4.2.7.3-1) The OSPF metric associated with a sham link must be configurable, and there must be a configurable default (§4.2.7.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.7.3-1, so no unit is bound to it. ### [`RFC4577-4.2.7.4-1`](#rfc4577-4.2.7.4-1) Any route (other than one whose next hop is the sham link) advertised in an LSA transmitted over a sham link must also be redistributed into BGP (§4.2.7.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.7.4-1, so no unit is bound to it. ### [`RFC4577-4.2.7.4-2`](#rfc4577-4.2.7.4-2) When forwarding a packet whose preferred route has the sham link as its next-hop interface, the packet must be forwarded according to the corresponding BGP route (§4.2.7.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.7.4-2, so no unit is bound to it. ### [`RFC4577-4.2.7.4-3`](#rfc4577-4.2.7.4-3) A packet whose IP destination is the remote endpoint address of a sham link must be forwarded according to the corresponding BGP route (§4.2.7.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4577-4.2.7.4-3, so no unit is bound to it. ### [`RFC4577-6-1`](#rfc4577-6-1) OSPF cryptographic authentication must be implemented on each PE (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4577CryptographicAuthRejectsForgery`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc4577_test.go#L46) | unit/verify | unproven | | positive | [`TestRFC4577CryptographicAuthImplemented`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc4577_test.go#L22) | unit/verify | unproven | ### [`RFC4577-6-2`](#rfc4577-6-2) OSPF cryptographic authentication should be used between a PE and a CE (§6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4577InterfaceUsesConfiguredCryptoAuth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc4577_test.go#L69) | unit/verify | unproven | ### [`RFC4577-4.1.4-2`](#rfc4577-4.1.4-2) The CE-to-PE area 0 adjacency may be via an OSPF virtual link (OPTIONAL feature) (§4.1.4, §4.2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC4577VirtualLinkGivesArea0Adjacency`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc4577_test.go#L92) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 4577, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4577, so its obligations are stated where they were written. --- ### Page: RFC 4578 - Dynamic Host Configuration Protocol (DHCP) Options for the Intel Preboot eXecution Environment (PXE) https://ze-software.net/quality/rfc-compliance/rfc4578/ # RFC 4578 - Dynamic Host Configuration Protocol (DHCP) Options for the Intel Preboot eXecution Environment (PXE) Supported. Every requirement this repository extracted from RFC 4578, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 20.0% | 1 of 5 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 5 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 5 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 5 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 2 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 5 | of 8 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 4 | of 5 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 80.0% | 4 of 5 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 5 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 5 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 5 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 8 | | Gated MUST-level | 5 | | Not applicable, so out of scope | 4 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 2 | | Tagged units | 2 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4578.md` | | Requirement shard | `rfc/requirements/rfc4578.md` | | RFC text | `rfc/full/rfc4578.txt` | ## Enrolment Enrolled: RFC 4578 PXE DHCP options (93/94/97): five gated MUST requirements, one tested and four {not-applicable} (ze is a DHCP server, not a PXE client or Boot Server). RFC4578-2.1-2 (option 93 Client System Architecture Len MUST be an even number greater than zero) is MET and tested via TestParsePXEArch (internal/plugins/dhcpserver/handler_test.go): the producer parsePXEArch (internal/plugins/dhcpserver/handler.go:505) rejects a length < 2 or odd, positive case an even Len 2 is accepted and parsed, negative case an odd Len 3 is rejected and returns 0 (BIOS default). The other four are {not-applicable}: RFC4578-2.1-1 (option 93 present in sent packets) -- ze reads option 93 for bootfile selection but emits none in OFFER/ACK (appendPXEOptions handler.go:292-331 emits only 60/66/67/43); RFC4578-2.2-1 (option 94 Client Network Interface Identifier present) and RFC4578-2.3-1 (option 97 Client Machine Identifier present) -- ze has no option 94/97 code path; RFC4578-2.4-1 (PXE clients MUST request options 128-135) -- binds the PXE client role ze never plays. No SHOULD/MAY requirements are gated. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** PXE boot option injection for BIOS and UEFI bootfile selection. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 1 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **5** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (1):** [`RFC4578-2.1-2`](#rfc4578-2.1-2) **Annotated instead of tested (4):** [`RFC4578-2.1-1`](#rfc4578-2.1-1), [`RFC4578-2.2-1`](#rfc4578-2.2-1), [`RFC4578-2.3-1`](#rfc4578-2.3-1), [`RFC4578-2.4-1`](#rfc4578-2.4-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4578-2.1-1` | Option 93 (Client System Architecture Type) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.1) | MUST | 2.1 - Client System Architecture Type Option Definition, option 93 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is a DHCP server, not a PXE client; it reads a received option 93 to select a bootfile (parsePXEArch, internal/plugins/dhcpserver/handler.go:505) but emits no option 93 in its OFFER/ACK replies (appendPXEOptions, internal/plugins/dhcpserver/handler.go:292) and ze has no PXE Boot Server Discovery echo code path -- it sets PXE_DISCOVERY_CONTROL to skip that exchange (internal/plugins/dhcpserver/handler.go:325) | | `RFC4578-2.1-2` | Option 93 Len field MUST be an even number greater than zero (Section 2.1) | MUST | 2.1 - Client System Architecture Type Option Definition, option 93 | **positive:** `unit/verify` [`TestParsePXEArch`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L923). **negative:** `unit/verify` [`TestParsePXEArch`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L926) | | `RFC4578-2.2-1` | Option 94 (Client Network Interface Identifier) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is a DHCP server, not a PXE client; ze has no option 94 code path -- it neither reads nor emits option 94 anywhere under internal/plugins/dhcpserver/ (grep for 94/UNDI in handler.go finds nothing) | | `RFC4578-2.3-1` | Option 97 (Client Machine Identifier) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.3) | MUST | 2.3 - Client Machine Identifier Option Definition, option 97 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is a DHCP server, not a PXE client; ze has no option 97 code path -- it neither reads nor emits option 97 (the client machine GUID) anywhere under internal/plugins/dhcpserver/ | | `RFC4578-2.4-1` | All compliant PXE clients MUST include a request for DHCP options 128 through 135 in all DHCP and PXE packets (Section 2.4) | MUST | 2.4 - Options Requested by PXE Clients | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is a DHCP server, not a PXE client; this requirement binds the PXE client to request options 128-135, a role ze never plays | | `RFC4578-2.1-3` | Clients that support more than one architecture type MAY include a list of these types in their initial DHCP and PXE boot server packets (Section 2.1) | MAY | 2.1 - Client System Architecture Type Option Definition, option 93 | **positive:** no positive test. **negative:** no negative test | | `RFC4578-2.1-4` | The list of supported architecture types MAY be reduced in any packet exchange between the client and server(s) (Section 2.1) | MAY | 2.1 - Client System Architecture Type Option Definition, option 93 | **positive:** no positive test. **negative:** no negative test | | `RFC4578-2.4-2` | Options 128-135 MAY be present in the DHCP and PXE boot server replies (Section 2.4) | MAY | 2.4 - Options Requested by PXE Clients | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4578-2.1-1`](#rfc4578-2.1-1) Option 93 (Client System Architecture Type) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze is a DHCP server, not a PXE client; it reads a received option 93 to select a bootfile (parsePXEArch, internal/plugins/dhcpserver/handler.go:505) but emits no option 93 in its OFFER/ACK replies (appendPXEOptions, internal/plugins/dhcpserver/handler.go:292) and ze has no PXE Boot Server Discovery echo code path -- it sets PXE_DISCOVERY_CONTROL to skip that exchange (internal/plugins/dhcpserver/handler.go:325) | | [`RFC4578-2.2-1`](#rfc4578-2.2-1) Option 94 (Client Network Interface Identifier) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.2) | no test | no test carries this requirement id; annotated {not-applicable}: ze is a DHCP server, not a PXE client; ze has no option 94 code path -- it neither reads nor emits option 94 anywhere under internal/plugins/dhcpserver/ (grep for 94/UNDI in handler.go finds nothing) | | [`RFC4578-2.3-1`](#rfc4578-2.3-1) Option 97 (Client Machine Identifier) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze is a DHCP server, not a PXE client; ze has no option 97 code path -- it neither reads nor emits option 97 (the client machine GUID) anywhere under internal/plugins/dhcpserver/ | | [`RFC4578-2.4-1`](#rfc4578-2.4-1) All compliant PXE clients MUST include a request for DHCP options 128 through 135 in all DHCP and PXE packets (Section 2.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze is a DHCP server, not a PXE client; this requirement binds the PXE client to request options 128-135, a role ze never plays | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4578-2.1-1`](#rfc4578-2.1-1) Option 93 (Client System Architecture Type) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4578-2.1-1, so no unit is bound to it. ### [`RFC4578-2.1-2`](#rfc4578-2.1-2) Option 93 Len field MUST be an even number greater than zero (Section 2.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParsePXEArch`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L926) | unit/verify | unproven | | positive | [`TestParsePXEArch`](https://github.com/ze-software/ze/blob/main/internal/plugins/dhcpserver/handler_test.go#L923) | unit/verify | unproven | ### [`RFC4578-2.2-1`](#rfc4578-2.2-1) Option 94 (Client Network Interface Identifier) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4578-2.2-1, so no unit is bound to it. ### [`RFC4578-2.3-1`](#rfc4578-2.3-1) Option 97 (Client Machine Identifier) MUST be present in all DHCP and PXE packets sent by PXE-compliant clients and servers (Section 2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4578-2.3-1, so no unit is bound to it. ### [`RFC4578-2.4-1`](#rfc4578-2.4-1) All compliant PXE clients MUST include a request for DHCP options 128 through 135 in all DHCP and PXE packets (Section 2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4578-2.4-1, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6, rfc4578 | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc4578.txt | | Source fingerprint | 0c9586ab5658d5ee | | Record | rfc/extraction/rfc4578.json | | Mapped sentences | 5 | | Declined as scope | 0 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of This Memo, Copyright Notice, Abstract and Table of Contents. The Status of This Memo states the document specifies no Internet standard, and the Abstract restates section 1. Neither directs a DHCP speaker. | | `1` | Introduction | 0 | walked | Introduction. Explains why the MAC address in the chaddr field was deemed insufficient to identify a booting machine (shared docking stations reusing a MAC, one machine holding several interfaces) and says the three options were defined by Intel in the PXE and EFI specifications and are documented here for completeness. Entirely indicative. No gated requirement of rfc/short/rfc4578.md is read from this section. | | `1.1` | Requirements Language: the RFC 2119 key-words paragraph | 0 | walked | Requirements Language: the RFC 2119 key-words paragraph. It tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. | | `2` | Option Definitions | 0 | walked | Option Definitions. One sentence: 'There are three DHCP options [5] defined for use by PXE clients.' It counts the subsections that follow and directs nobody. | | `2.1` | Client System Architecture Type Option Definition, option 93 | 2 | walked | Client System Architecture Type Option Definition, option 93. Holds two of the document's five sites, both mapped below: the Len constraint (2.1:1) and the presence obligation (2.1:2). The rest of the section is the option figure, the architecture type table (types 0 to 9), the statement that octets n1 and n2 encode a 16-bit architecture type identifier, and two MAY sentences that are advisory and never gate: a client supporting more than one architecture type MAY list them (RFC4578-2.1-3) and the list MAY be reduced in any packet exchange (RFC4578-2.1-4). Ze reads the first type from a received option 93 in parsePXEArch (internal/plugins/dhcpserver/handler.go), which is why a multi-type list is parsed rather than refused. | | `2.2` | not stated | 1 | walked | Client Network Interface Identifier Option Definition, option 94. The figure, the statement that octet t encodes an interface type whose only supported value is 1 (UNDI), the M and m revision encoding, and the UNDI revision table are indicative. The one normative sentence is site 2.2:1, mapped below. | | `2.3` | Client Machine Identifier Option Definition, option 97 | 1 | walked | Client Machine Identifier Option Definition, option 97. The figure, the statement that type 0 is the only defined value and describes the remaining octets as a 16-octet GUID, and the note that octet n is 17 for type 0 are indicative. The one normative sentence is site 2.3:1, mapped below. | | `2.4` | Options Requested by PXE Clients | 1 | walked | Options Requested by PXE Clients. Its one MUST is site 2.4:1, mapped below. The rest states that the format and contents of options 128 to 135 are not defined by the PXE specification, that they MAY be present in replies (RFC4578-2.4-2, advisory and never gated), that they are not used by the PXE boot ROMs, and that because they were site-specific options before November 2004 their use for PXE may conflict with other uses on the same network. | | `3` | Acknowledgements | 0 | skipped (acknowledgements) | Acknowledgements. One sentence thanking Bernie Volz. | | `4` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records that IANA updated the public DHCP option numbering space with references to this document for options 93, 94 and 97, and marked options 128 to 135 as used by PXE. Binds IANA, not a DHCP speaker, and is written in the past tense. | | `5` | Security Considerations | 0 | walked | Security Considerations. Two observations and no countermeasure: a client specifying incorrect option values may reach code intended for another platform, and the options reveal a client's system architecture and pre-OS runtime environment to anyone listening to its DHCP messages. Neither sentence directs a speaker to do or refuse anything, so the section states no requirement. rfc/short/rfc4578.md carries both as its Security Considerations table. | | `6` | not stated | 0 | skipped (references) | Normative References: RFC 2119, RFC 2131, the Intel PXE and EFI specifications, RFC 2132, RFC 3942, RFC 2939 and RFC 3679. The derivation folds the trailing matter under this heading as well -- the Authors' Addresses, the Full Copyright Statement, the Intellectual Property notice and the RFC Editor funding acknowledgement. None of it binds a DHCP speaker, and the site scan derives no site here. | ### Excluded sentences The walk over RFC 4578 declined no sentence: every site it found is mapped to a requirement. ## Superseded No document obsoletes RFC 4578, so its obligations are stated where they were written. --- ### Page: RFC 4659 - BGP-MPLS IP Virtual Private Network (VPN) Extension for IPv6 VPN https://ze-software.net/quality/rfc-compliance/rfc4659/ # RFC 4659 - BGP-MPLS IP Virtual Private Network (VPN) Extension for IPv6 VPN Partial. Every requirement this repository extracted from RFC 4659, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 12.5% | 2 of 16 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 12.5% | 2 of 16 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 16 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 6 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 16 | of 22 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 9 | of 16 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 56.2% | 9 of 16 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 16 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 16 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 18.8% | 3 of 16 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 16 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 22 | | Gated MUST-level | 16 | | Not applicable, so out of scope | 9 | | Declared gaps | 3 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 6 | | Tagged units | 6 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4659.md` | | Requirement shard | `rfc/requirements/rfc4659.md` | | RFC text | `rfc/full/rfc4659.txt` | ## Enrolment Enrolled: BGP-MPLS IPv6 VPN / VPNv6 (RFC 4659): 2 MET (labeled VPNv6 NLRI, AFI2/SAFI128 capability negotiation) + 2 single-polarity positive (AFI/SAFI set, 24-octet zero-RD global-IPv6 next-hop) + 3 gap (IPv4-mapped-IPv6 next-hop, 48-octet global+link-local next-hop) + 9 not-applicable (data-plane PE tunneling + multi-AS ASBR) ## What the public ledger says **Status:** Partial **What the ledger says is covered:** 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. **What the ledger says remains:** No IPv4-mapped-IPv6 next-hop for IPv4 transport ([`RFC4659-3.2.1.2-1`](#rfc4659-3.2.1.2-1), 8-4); no 48-octet global+link-local next-hop ([`RFC4659-8-3`](#rfc4659-8-3)). Data-plane PE tunneling (Section 4) and multi-AS ASBR options (Section 8 a/b) are not performed. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 14 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **16** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC4659-3.2-1`](#rfc4659-3.2-1), [`RFC4659-3.4-1`](#rfc4659-3.4-1) **Annotated instead of tested (14):** [`RFC4659-3.2-2`](#rfc4659-3.2-2), [`RFC4659-4-1`](#rfc4659-4-1), [`RFC4659-4-2`](#rfc4659-4-2), [`RFC4659-4-3`](#rfc4659-4-3), [`RFC4659-4-4`](#rfc4659-4-4), [`RFC4659-4-5`](#rfc4659-4-5), [`RFC4659-4-6`](#rfc4659-4-6), [`RFC4659-4-7`](#rfc4659-4-7), [`RFC4659-8-1`](#rfc4659-8-1), [`RFC4659-8-2`](#rfc4659-8-2), [`RFC4659-8-3`](#rfc4659-8-3), [`RFC4659-3.2.1.1-1`](#rfc4659-3.2.1.1-1), [`RFC4659-3.2.1.2-1`](#rfc4659-3.2.1.2-1), [`RFC4659-8-4`](#rfc4659-8-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4659-3.2-1` | PE routers MUST assign and distribute MPLS labels with the IPv6 VPN routes (Section 3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestVPNv6WireRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/vpn/vpn_test.go#L47). **negative:** `unit/verify` [`TestVPNv6RejectsLabellessEncode`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/vpn/vpn_test.go#L81) | | `RFC4659-3.2-2` | AFI and SAFI fields MUST be set to AFI=2, SAFI=128 (Section 3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestUpdateBuilder_BuildVPN_IPv6`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/update_build_test.go#L597). **negative:** no negative test. **{single-polarity}:** the obligation is to SET AFI=2/SAFI=128 when advertising a VPNv6 route, so the only conforming assertion is that the emitted fields equal 2/128 and a MUST-NOT-set-other-values companion is degenerate (internal/component/bgp/message/update_build_vpn.go:221, internal/component/bgp/plugins/nlri/vpn/types.go:37) | | `RFC4659-3.4-1` | Two PEs MUST use BGP Capabilities Negotiation (capability code 1, AFI=2, SAFI=128) to ensure both can process VPN-IPv6 NLRIs (Section 3.4) | MUST | 3.4 | **positive:** `unit/verify` [`TestOpenAdvertisesVPNv6Capability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L171). **negative:** `unit/verify` [`TestNegotiateWith_VPNv6NotActiveWithoutPeerCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L216) | | `RFC4659-4-1` | The ingress PE Router MUST tunnel IPv6 VPN data over the backbone towards the Egress PE router (Section 4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this is an ingress-PE data-plane forwarding behavior; ze is a BGP control-plane speaker with no VPNv6 VRF-to-backbone tunneling path | | `RFC4659-4-2` | When Next Hop is an IPv4-mapped IPv6 address, ingress PE MUST use IPv4 tunneling unless explicitly configured otherwise (Section 4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** a data-plane transport-selection decision for a forwarding PE; ze performs no VPNv6 data-plane forwarding, and no IPv4-mapped detection exists in the BGP path | | `RFC4659-4-3` | When Next Hop is not an IPv4-mapped IPv6 address, ingress PE MUST use IPv6 tunneling (Section 4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** a data-plane transport-selection decision for a forwarding PE, a role ze does not perform | | `RFC4659-4-4` | When tunneling using IPv4, MUST use the IPv4 address encoded in the IPv4-mapped field as the tunnel destination (Section 4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** a data-plane encapsulation behavior; ze installs no VPNv6 IPv4-tunnel forwarding entries | | `RFC4659-4-5` | When tunneling using IPv6, MUST use the IPv6 address from the Next Hop as the tunnel destination (Section 4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** a data-plane encapsulation behavior; ze installs no VPNv6 IPv6-tunnel forwarding entries | | `RFC4659-4-6` | When tunneling using MPLS LSPs, MUST directly push the LSP tunnel label on the label stack (no IP header) (Section 4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** an MPLS data-plane label-imposition behavior for a forwarding PE; ze has no VPNv6 VRF-to-LSP forwarding path | | `RFC4659-4-7` | All systems MUST support tunneling using MPLS LSPs established by LDP (Section 4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** binds the ingress-PE VPN-data-over-LDP-LSP forwarding role; ze has LDP label distribution and an MPLS FIB but performs no VPNv6 customer-data forwarding | | `RFC4659-8-1` | Multi-AS approach (a): Exchange of IPv6 routes MUST be carried out as per RFC 2545 (Section 8) | MUST | 8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the inter-provider option-A back-to-back-VRF ASBR role; ze has no per-VPN VRF inter-AS exchange, and the referenced RFC 2545 IPv6 next-hop wire behavior is enrolled under its own RFC | | `RFC4659-8-2` | Multi-AS approach (b): Exchange of labeled VPN-IPv6 routes MUST be carried out as per RFC 2545 and RFC 3107 (Section 8) | MUST | 8 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the inter-provider option-B ASBR label-swap/redistribution role; ze implements the VPNv6 NLRI and next-hop encodings but performs no inter-AS VPN ASBR redistribution | | `RFC4659-8-3` | Multi-AS approach (b) with IPv6 tunneling: Next Hop Field MUST contain global IPv6 address; when ASBRs share IPv6 subnet, MUST include both global and link-local (Section 8, Section 3.2.1.1) | MUST | 8 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's VPNv6 next-hop encoder emits only the single global-IPv6 24-octet form and never the 48-octet global+link-local next-hop, so the shared-subnet clause is unmet (internal/component/bgp/message/update_build_vpn.go:229; no 48-octet producer exists) | | `RFC4659-3.2.1.1-1` | When requesting IPv6 transport, BGP speaker SHALL advertise a Next Hop containing a VPN-IPv6 address with zero RD and global IPv6 address (Section 3.2.1.1) | SHALL | 3.2.1.1 | **positive:** `unit/verify` [`TestUpdateBuilder_BuildVPN_IPv6_NextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/update_build_test.go#L646). **negative:** no negative test. **{single-polarity}:** the obligation is to EMIT a 24-octet zero-RD + global-IPv6 next-hop, which ze produces for a VPNv6 route with an IPv6 next-hop; the decode side is not RD-aware and is not a gated obligation (internal/component/bgp/message/update_build_vpn.go:246, internal/component/bgp/rib/commit.go:498) | | `RFC4659-3.2.1.2-1` | When requesting IPv4 transport, BGP speaker SHALL advertise a Next Hop containing a VPN-IPv6 address with zero RD and IPv4-mapped IPv6 address (Section 3.2.1.2) | SHALL | 3.2.1.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze constructs no IPv4-mapped-IPv6 next-hop for VPNv6 -- a plain IPv4 next-hop on a VPNv6 route emits a non-conformant 12-octet zero-RD+IPv4 next-hop, and no ::ffff:a.b.c.d mapping exists in the BGP path (internal/component/bgp/message/update_build_vpn.go:229; Is4In6 appears only in ISIS/OSPF) | | `RFC4659-8-4` | Multi-AS approach (b) with IPv4 tunneling: Next Hop Field SHALL contain an IPv4-mapped IPv6 address (Section 8) | SHALL | 8 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the same missing IPv4-mapped-IPv6 next-hop construction as RFC4659-3.2.1.2-1; ze never emits a zero-RD + ::ffff:a.b.c.d VPNv6 next-hop (internal/component/bgp/message/update_build_vpn.go:246; no IPv4-mapped VPNv6 next-hop producer) | | `RFC4659-3.2.1.1-2` | Remove link-local from Next Hop when advertising to internal peer not on a common subnet (Section 3.2.1.1) | SHOULD | 3.2.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4659-1-1` | Same single set of MP-BGP peering relationships and same PE-PE tunnel mesh MAY be used for both IPv4 and IPv6 VPNs (Section 1) | MAY | 1 | **positive:** no positive test. **negative:** no negative test | | `RFC4659-2-1` | Same RD MAY be used for IPv6 and IPv4 addresses from the same site (Section 2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC4659-2-2` | Different RD MAY be used for IPv4 and IPv6 addresses (Section 2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC4659-3.1-1` | For IPv6 VPN route distribution, PEs MAY use iBGP over IPv4 or IPv6 (Section 3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC4659-4-8` | Ingress PE MAY optionally allow IPv6 tunneling when Next Hop is IPv4-mapped via explicit configuration (Section 4) | MAY | 4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4659-4-1`](#rfc4659-4-1) The ingress PE Router MUST tunnel IPv6 VPN data over the backbone towards the Egress PE router (Section 4) | no test | no test carries this requirement id; annotated {not-applicable}: this is an ingress-PE data-plane forwarding behavior; ze is a BGP control-plane speaker with no VPNv6 VRF-to-backbone tunneling path | | [`RFC4659-4-2`](#rfc4659-4-2) When Next Hop is an IPv4-mapped IPv6 address, ingress PE MUST use IPv4 tunneling unless explicitly configured otherwise (Section 4) | no test | no test carries this requirement id; annotated {not-applicable}: a data-plane transport-selection decision for a forwarding PE; ze performs no VPNv6 data-plane forwarding, and no IPv4-mapped detection exists in the BGP path | | [`RFC4659-4-3`](#rfc4659-4-3) When Next Hop is not an IPv4-mapped IPv6 address, ingress PE MUST use IPv6 tunneling (Section 4) | no test | no test carries this requirement id; annotated {not-applicable}: a data-plane transport-selection decision for a forwarding PE, a role ze does not perform | | [`RFC4659-4-4`](#rfc4659-4-4) When tunneling using IPv4, MUST use the IPv4 address encoded in the IPv4-mapped field as the tunnel destination (Section 4) | no test | no test carries this requirement id; annotated {not-applicable}: a data-plane encapsulation behavior; ze installs no VPNv6 IPv4-tunnel forwarding entries | | [`RFC4659-4-5`](#rfc4659-4-5) When tunneling using IPv6, MUST use the IPv6 address from the Next Hop as the tunnel destination (Section 4) | no test | no test carries this requirement id; annotated {not-applicable}: a data-plane encapsulation behavior; ze installs no VPNv6 IPv6-tunnel forwarding entries | | [`RFC4659-4-6`](#rfc4659-4-6) When tunneling using MPLS LSPs, MUST directly push the LSP tunnel label on the label stack (no IP header) (Section 4) | no test | no test carries this requirement id; annotated {not-applicable}: an MPLS data-plane label-imposition behavior for a forwarding PE; ze has no VPNv6 VRF-to-LSP forwarding path | | [`RFC4659-4-7`](#rfc4659-4-7) All systems MUST support tunneling using MPLS LSPs established by LDP (Section 4) | no test | no test carries this requirement id; annotated {not-applicable}: binds the ingress-PE VPN-data-over-LDP-LSP forwarding role; ze has LDP label distribution and an MPLS FIB but performs no VPNv6 customer-data forwarding | | [`RFC4659-8-1`](#rfc4659-8-1) Multi-AS approach (a): Exchange of IPv6 routes MUST be carried out as per RFC 2545 (Section 8) | no test | no test carries this requirement id; annotated {not-applicable}: the inter-provider option-A back-to-back-VRF ASBR role; ze has no per-VPN VRF inter-AS exchange, and the referenced RFC 2545 IPv6 next-hop wire behavior is enrolled under its own RFC | | [`RFC4659-8-2`](#rfc4659-8-2) Multi-AS approach (b): Exchange of labeled VPN-IPv6 routes MUST be carried out as per RFC 2545 and RFC 3107 (Section 8) | no test | no test carries this requirement id; annotated {not-applicable}: the inter-provider option-B ASBR label-swap/redistribution role; ze implements the VPNv6 NLRI and next-hop encodings but performs no inter-AS VPN ASBR redistribution | | [`RFC4659-8-3`](#rfc4659-8-3) Multi-AS approach (b) with IPv6 tunneling: Next Hop Field MUST contain global IPv6 address; when ASBRs share IPv6 subnet, MUST include both global and link-local (Section 8, Section 3.2.1.1) | {gap}, no test | ze's VPNv6 next-hop encoder emits only the single global-IPv6 24-octet form and never the 48-octet global+link-local next-hop, so the shared-subnet clause is unmet (internal/component/bgp/message/update_build_vpn.go:229; no 48-octet producer exists) | | [`RFC4659-3.2.1.2-1`](#rfc4659-3.2.1.2-1) When requesting IPv4 transport, BGP speaker SHALL advertise a Next Hop containing a VPN-IPv6 address with zero RD and IPv4-mapped IPv6 address (Section 3.2.1.2) | {gap}, no test | ze constructs no IPv4-mapped-IPv6 next-hop for VPNv6 -- a plain IPv4 next-hop on a VPNv6 route emits a non-conformant 12-octet zero-RD+IPv4 next-hop, and no ::ffff:a.b.c.d mapping exists in the BGP path (internal/component/bgp/message/update_build_vpn.go:229; Is4In6 appears only in ISIS/OSPF) | | [`RFC4659-8-4`](#rfc4659-8-4) Multi-AS approach (b) with IPv4 tunneling: Next Hop Field SHALL contain an IPv4-mapped IPv6 address (Section 8) | {gap}, no test | the same missing IPv4-mapped-IPv6 next-hop construction as RFC4659-3.2.1.2-1; ze never emits a zero-RD + ::ffff:a.b.c.d VPNv6 next-hop (internal/component/bgp/message/update_build_vpn.go:246; no IPv4-mapped VPNv6 next-hop producer) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4659-3.2-1`](#rfc4659-3.2-1) PE routers MUST assign and distribute MPLS labels with the IPv6 VPN routes (Section 3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestVPNv6RejectsLabellessEncode`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/vpn/vpn_test.go#L81) | unit/verify | unproven | | positive | [`TestVPNv6WireRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/vpn/vpn_test.go#L47) | unit/verify | unproven | ### [`RFC4659-3.2-2`](#rfc4659-3.2-2) AFI and SAFI fields MUST be set to AFI=2, SAFI=128 (Section 3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestUpdateBuilder_BuildVPN_IPv6`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/update_build_test.go#L597) | unit/verify | unproven | ### [`RFC4659-3.4-1`](#rfc4659-3.4-1) Two PEs MUST use BGP Capabilities Negotiation (capability code 1, AFI=2, SAFI=128) to ensure both can process VPN-IPv6 NLRIs (Section 3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegotiateWith_VPNv6NotActiveWithoutPeerCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L216) | unit/verify | unproven | | positive | [`TestOpenAdvertisesVPNv6Capability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_negotiate_test.go#L171) | unit/verify | unproven | ### [`RFC4659-4-1`](#rfc4659-4-1) The ingress PE Router MUST tunnel IPv6 VPN data over the backbone towards the Egress PE router (Section 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-4-1, so no unit is bound to it. ### [`RFC4659-4-2`](#rfc4659-4-2) When Next Hop is an IPv4-mapped IPv6 address, ingress PE MUST use IPv4 tunneling unless explicitly configured otherwise (Section 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-4-2, so no unit is bound to it. ### [`RFC4659-4-3`](#rfc4659-4-3) When Next Hop is not an IPv4-mapped IPv6 address, ingress PE MUST use IPv6 tunneling (Section 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-4-3, so no unit is bound to it. ### [`RFC4659-4-4`](#rfc4659-4-4) When tunneling using IPv4, MUST use the IPv4 address encoded in the IPv4-mapped field as the tunnel destination (Section 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-4-4, so no unit is bound to it. ### [`RFC4659-4-5`](#rfc4659-4-5) When tunneling using IPv6, MUST use the IPv6 address from the Next Hop as the tunnel destination (Section 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-4-5, so no unit is bound to it. ### [`RFC4659-4-6`](#rfc4659-4-6) When tunneling using MPLS LSPs, MUST directly push the LSP tunnel label on the label stack (no IP header) (Section 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-4-6, so no unit is bound to it. ### [`RFC4659-4-7`](#rfc4659-4-7) All systems MUST support tunneling using MPLS LSPs established by LDP (Section 4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-4-7, so no unit is bound to it. ### [`RFC4659-8-1`](#rfc4659-8-1) Multi-AS approach (a): Exchange of IPv6 routes MUST be carried out as per RFC 2545 (Section 8) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-8-1, so no unit is bound to it. ### [`RFC4659-8-2`](#rfc4659-8-2) Multi-AS approach (b): Exchange of labeled VPN-IPv6 routes MUST be carried out as per RFC 2545 and RFC 3107 (Section 8) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-8-2, so no unit is bound to it. ### [`RFC4659-8-3`](#rfc4659-8-3) Multi-AS approach (b) with IPv6 tunneling: Next Hop Field MUST contain global IPv6 address; when ASBRs share IPv6 subnet, MUST include both global and link-local (Section 8, Section 3.2.1.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-8-3, so no unit is bound to it. ### [`RFC4659-3.2.1.1-1`](#rfc4659-3.2.1.1-1) When requesting IPv6 transport, BGP speaker SHALL advertise a Next Hop containing a VPN-IPv6 address with zero RD and global IPv6 address (Section 3.2.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestUpdateBuilder_BuildVPN_IPv6_NextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/update_build_test.go#L646) | unit/verify | unproven | ### [`RFC4659-3.2.1.2-1`](#rfc4659-3.2.1.2-1) When requesting IPv4 transport, BGP speaker SHALL advertise a Next Hop containing a VPN-IPv6 address with zero RD and IPv4-mapped IPv6 address (Section 3.2.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-3.2.1.2-1, so no unit is bound to it. ### [`RFC4659-8-4`](#rfc4659-8-4) Multi-AS approach (b) with IPv4 tunneling: Next Hop Field SHALL contain an IPv4-mapped IPv6 address (Section 8) Audit verdict: not audited: no reader has judged these tests No test carries RFC4659-8-4, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 4659, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4659, so its obligations are stated where they were written. --- ### Page: RFC 4684 - Constrained Route Distribution for Border Gateway Protocol/MultiProtocol Label Switching (BGP/MPLS) Internet Protocol (IP) Virtual Private Networks (VPNs) https://ze-software.net/quality/rfc-compliance/rfc4684/ # RFC 4684 - Constrained Route Distribution for Border Gateway Protocol/MultiProtocol Label Switching (BGP/MPLS) Internet Protocol (IP) Virtual Private Networks (VPNs) Partial. Every requirement this repository extracted from RFC 4684, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 8 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 100.0% | 4 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 8 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 0 | | Declared gaps | 4 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4684.md` | | Requirement shard | `rfc/requirements/rfc4684.md` | | RFC text | `rfc/full/rfc4684.txt` | ## Enrolment Enrolled: Constrained Route Distribution for BGP/MPLS VPNs (Route Target Constraint): four MUST-level requirements, all {gap}. Ze implements RTC as a DECODE-ONLY NLRI codec for display/analysis (internal/component/bgp/plugins/nlri/rtc/rtc.go DecodeNLRIHex/RunDecode) with no encode/origination path and no RT-membership distribution. RFC4684-3.2-1 (Originator/Next-hop when advertising RT membership NLRI) and RFC4684-3.2-2 (best-path/client-path advertisement selection): Ze never advertises RT membership NLRI. RFC4684-3.2-3 (consider all iBGP paths for the outbound route filter): Ze builds no ORF from RT membership. RFC4684-6-1 (bound the End-of-RIB delay for VPN route advertisement): Ze gates no VPN route advertisement on RT-membership state. Disclosed in the docs/features/rfc-status.md RFC 4684 row (Partial). The 6-2/6-3/8-1 SHOULDs and 5-1 MAY are not gated. ## What the public ledger says **Status:** Partial **What the ledger says is covered:** RTC NLRI decode for display/analysis (`ze bgp decode`). Tests bound per requirement in [`rfc/requirements/rfc4684.md`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc4684.md). **What the ledger says remains** Decode-only: no encode/origination path and no RT-membership distribution. Four MUST gaps gated in [`rfc/short/rfc4684.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4684.md): 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). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 4 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (4):** [`RFC4684-3.2-1`](#rfc4684-3.2-1), [`RFC4684-3.2-2`](#rfc4684-3.2-2), [`RFC4684-3.2-3`](#rfc4684-3.2-3), [`RFC4684-6-1`](#rfc4684-6-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4684-3.2-1` | When advertising RT membership NLRI to a route-reflector client, the Originator attribute shall be set to the router-id of the advertiser, and the Next-hop attribute shall be set to the local address for that session (Section 3.2) | MUST | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Ze implements RTC (Route Target Constraint) as a DECODE-ONLY NLRI codec for display/analysis (internal/component/bgp/plugins/nlri/rtc/rtc.go DecodeNLRIHex/RunDecode; there is no encode/origination path). It never advertises RT membership NLRI, so it implements none of the Section 3.2 advertisement Originator/Next-hop procedure. Disclosed in docs/features/rfc-status.md. | | `RFC4684-3.2-2` | When advertising RT membership NLRI to a non-client peer, if best path is from a non-client peer and an alternative client path exists, advertise the client path attributes (Section 3.2) | MUST | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Ze does not advertise RT membership NLRI at all (its RTC support is a decode-only NLRI codec, internal/component/bgp/plugins/nlri/rtc/rtc.go, with no origination path), so it implements none of the Section 3.2 best-path/client-path advertisement selection. Disclosed in docs/features/rfc-status.md. | | `RFC4684-3.2-3` | When processing RT membership NLRIs from internal iBGP peers, consider all available iBGP paths for a given RT prefix for building the outbound route filter, not just the best path (Section 3.2) | MUST | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Ze builds no outbound route filter from RT membership -- its RTC support is a decode-only NLRI codec (internal/component/bgp/plugins/nlri/rtc/rtc.go) with no ORF construction or RT-membership-driven VPN route filtering. Disclosed in docs/features/rfc-status.md. | | `RFC4684-6-1` | If delaying VPN route advertisement until End-of-RIB marker is received, MUST limit that delay to an upper bound (Section 6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Ze does not gate VPN route advertisement on RT-membership state -- it has no RTC-driven VPN route distribution (the RTC codec is decode-only, internal/component/bgp/plugins/nlri/rtc/rtc.go), so there is no such End-of-RIB delay for Ze to bound. Disclosed in docs/features/rfc-status.md. | | `RFC4684-6-2` | Implementations SHOULD generate an End-of-RIB marker for Route Target membership (AFI=1, SAFI=132) regardless of whether graceful-restart is enabled (Section 6) | SHOULD | 6 | **positive:** no positive test. **negative:** no negative test | | `RFC4684-6-3` | A BGP speaker should generate the minimum set of BGP VPN route updates necessary to transition between the previous and current state of the route distribution graph (Section 6) | SHOULD | 6 | **positive:** no positive test. **negative:** no negative test | | `RFC4684-8-1` | Implementations SHOULD provide means to filter RT membership information (Section 8) | SHOULD | 8 | **positive:** no positive test. **negative:** no negative test | | `RFC4684-5-1` | A BGP speaker MAY participate in distribution of Route Target information without using it for VPN NLRI output route filtering (Section 5) | MAY | 5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4684-3.2-1`](#rfc4684-3.2-1) When advertising RT membership NLRI to a route-reflector client, the Originator attribute shall be set to the router-id of the advertiser, and the Next-hop attribute shall be set to the local address for that session (Section 3.2) | {gap}, no test | Ze implements RTC (Route Target Constraint) as a DECODE-ONLY NLRI codec for display/analysis (internal/component/bgp/plugins/nlri/rtc/rtc.go DecodeNLRIHex/RunDecode; there is no encode/origination path). It never advertises RT membership NLRI, so it implements none of the Section 3.2 advertisement Originator/Next-hop procedure. Disclosed in docs/features/rfc-status.md. | | [`RFC4684-3.2-2`](#rfc4684-3.2-2) When advertising RT membership NLRI to a non-client peer, if best path is from a non-client peer and an alternative client path exists, advertise the client path attributes (Section 3.2) | {gap}, no test | Ze does not advertise RT membership NLRI at all (its RTC support is a decode-only NLRI codec, internal/component/bgp/plugins/nlri/rtc/rtc.go, with no origination path), so it implements none of the Section 3.2 best-path/client-path advertisement selection. Disclosed in docs/features/rfc-status.md. | | [`RFC4684-3.2-3`](#rfc4684-3.2-3) When processing RT membership NLRIs from internal iBGP peers, consider all available iBGP paths for a given RT prefix for building the outbound route filter, not just the best path (Section 3.2) | {gap}, no test | Ze builds no outbound route filter from RT membership -- its RTC support is a decode-only NLRI codec (internal/component/bgp/plugins/nlri/rtc/rtc.go) with no ORF construction or RT-membership-driven VPN route filtering. Disclosed in docs/features/rfc-status.md. | | [`RFC4684-6-1`](#rfc4684-6-1) If delaying VPN route advertisement until End-of-RIB marker is received, MUST limit that delay to an upper bound (Section 6) | {gap}, no test | Ze does not gate VPN route advertisement on RT-membership state -- it has no RTC-driven VPN route distribution (the RTC codec is decode-only, internal/component/bgp/plugins/nlri/rtc/rtc.go), so there is no such End-of-RIB delay for Ze to bound. Disclosed in docs/features/rfc-status.md. | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4684-3.2-1`](#rfc4684-3.2-1) When advertising RT membership NLRI to a route-reflector client, the Originator attribute shall be set to the router-id of the advertiser, and the Next-hop attribute shall be set to the local address for that session (Section 3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4684-3.2-1, so no unit is bound to it. ### [`RFC4684-3.2-2`](#rfc4684-3.2-2) When advertising RT membership NLRI to a non-client peer, if best path is from a non-client peer and an alternative client path exists, advertise the client path attributes (Section 3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4684-3.2-2, so no unit is bound to it. ### [`RFC4684-3.2-3`](#rfc4684-3.2-3) When processing RT membership NLRIs from internal iBGP peers, consider all available iBGP paths for a given RT prefix for building the outbound route filter, not just the best path (Section 3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4684-3.2-3, so no unit is bound to it. ### [`RFC4684-6-1`](#rfc4684-6-1) If delaying VPN route advertisement until End-of-RIB marker is received, MUST limit that delay to an upper bound (Section 6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4684-6-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 4684, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4684, so its obligations are stated where they were written. --- ### Page: RFC 4724 - Graceful Restart Mechanism for BGP https://ze-software.net/quality/rfc-compliance/rfc4724/ # RFC 4724 - Graceful Restart Mechanism for BGP Partial. Every requirement this repository extracted from RFC 4724, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 61.5% | 16 of 26 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 3.8% | 1 of 26 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 26 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 43 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 26 | of 31 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 26 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 3.8% | 1 of 26 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 26 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 26 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 30.8% | 8 of 26 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 26 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 31 | | Gated MUST-level | 26 | | Not applicable, so out of scope | 1 | | Declared gaps | 8 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 43 | | Tagged units | 43 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4724.md` | | Requirement shard | `rfc/requirements/rfc4724.md` | | RFC text | `rfc/full/rfc4724.txt` | ## Enrolment Enrolled: Graceful Restart Mechanism for BGP (RFC 4724): 16 MET (GR capability advertise + last-instance negotiate, Restart-State/Forwarding-State bit encoding, End-of-RIB send + detect, mark-stale on non-NOTIFICATION drop, stale deletion on consecutive restart / Restart-Time expiry / F-bit-clear / no-GR-cap re-establish, GR-stale level-1 competes normally in best-path) + 1 single-polarity positive + 8 gap (GR-capability collision-detection override and related receiving-speaker obligations) + 1 not-applicable ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync.go), peer_settings.go). [`test/plugin/initial-sync-barrier-raw.ci`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc4724.md). **What the ledger says remains** Eight MUST gaps annotated in [`rfc/short/rfc4724.md`](https://github.com/ze-software/ze/blob/main/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`](#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`](#rfc4724-4.2-1), 4.2-2). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 16 | one part of the gated population | | Annotated instead of tested | 10 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **26** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (16):** [`RFC4724-3-2`](#rfc4724-3-2), [`RFC4724-3-3`](#rfc4724-3-3), [`RFC4724-3-4`](#rfc4724-3-4), [`RFC4724-4-1`](#rfc4724-4-1), [`RFC4724-4-2`](#rfc4724-4-2), [`RFC4724-4.1-4`](#rfc4724-4.1-4), [`RFC4724-4.1-7`](#rfc4724-4.1-7), [`RFC4724-4.2-3`](#rfc4724-4.2-3), [`RFC4724-4.2-4`](#rfc4724-4.2-4), [`RFC4724-4.2-5`](#rfc4724-4.2-5), [`RFC4724-4.2-6`](#rfc4724-4.2-6), [`RFC4724-4.2-7`](#rfc4724-4.2-7), [`RFC4724-4.2-8`](#rfc4724-4.2-8), [`RFC4724-4.2-9`](#rfc4724-4.2-9), [`RFC4724-4.2-10`](#rfc4724-4.2-10), [`RFC4724-4.2-11`](#rfc4724-4.2-11) **Annotated instead of tested (10):** [`RFC4724-3-1`](#rfc4724-3-1), [`RFC4724-3-5`](#rfc4724-3-5), [`RFC4724-4.1-1`](#rfc4724-4.1-1), [`RFC4724-4.1-2`](#rfc4724-4.1-2), [`RFC4724-4.1-3`](#rfc4724-4.1-3), [`RFC4724-4.1-5`](#rfc4724-4.1-5), [`RFC4724-4.1-6`](#rfc4724-4.1-6), [`RFC4724-4.1-8`](#rfc4724-4.1-8), [`RFC4724-4.2-1`](#rfc4724-4.2-1), [`RFC4724-4.2-2`](#rfc4724-4.2-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4724-3-1` | A BGP speaker MUST NOT include more than one instance of the Graceful Restart Capability in the capability advertisement (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestExtractGRCapabilities_CapabilityDecl`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_test.go#L111). **negative:** no negative test. **{single-polarity}:** ze's GR sender emits exactly one code-64 declaration per peer (internal/component/bgp/plugins/gr/gr.go:703 extractGRCapabilities appends one CapabilityDecl per peer) and the encoder writes a single TLV (internal/core/bgp/capability/capability.go:553 WriteTo); there is no code path that emits two instances, so the more-than-one case cannot be constructed to test negatively | | `RFC4724-3-2` | Reserved bits in Restart Flags MUST be set to zero by the sender (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestGracefulRestartEncodeReservedBits`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L422). **negative:** `unit/verify` [`TestGracefulRestartEncodeReservedBits`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L433) | | `RFC4724-3-3` | Reserved bits in Address Family Flags MUST be set to zero by the sender (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestGracefulRestartEncodeReservedBits`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L428). **negative:** `unit/verify` [`TestGracefulRestartEncodeReservedBits`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L442) | | `RFC4724-3-4` | If more than one instance of the Graceful Restart Capability is received, the receiver MUST ignore all but the last instance (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestNegotiateGracefulRestartLastInstance`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L62). **negative:** `unit/verify` [`TestNegotiateGracefulRestartLastInstance`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L70) | | `RFC4724-3-5` | When R bit is set, peer MUST NOT wait for End-of-RIB marker from the speaker before advertising routing information (Section 3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze advertises its Adj-RIB-Out and per-family End-of-RIB immediately on reaching Established (internal/component/bgp/reactor/peer_initial_sync.go:277,334) and has no receive-side mechanism that gates advertisement on a peer's End-of-RIB; the only End-of-RIB timer (internal/component/bgp/reactor/session_health.go:114 startEORTimer) raises a health warning and never defers advertisement, so there is no wait state for the R bit to override | | `RFC4724-4-1` | The End-of-RIB marker MUST be sent by a BGP speaker to its peer once it completes the initial routing update for an address family (Section 4) | MUST | 4 | **positive:** `unit/verify` [`TestBuildEOR_IPv4Unicast`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L16). **positive:** `unit/verify` [`TestInitialSyncBarrierCreditsOnlyTheProcessesItNames`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L760). **positive:** `unit/verify` [`TestInitialSyncClosesTheQueueGateBeforeItWaitsForRoutePushingPlugins`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L342). **positive:** `unit/verify` [`TestInitialSyncEORReachesTheSilentFamilyToo`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L100). **positive:** `unit/verify` [`TestInitialSyncEORSentWhenNeitherSideDeclaredAFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L726). **positive:** `unit/verify` [`TestRoutePushingBindingsCountBothRails`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L467). **negative:** `unit/verify` [`TestIsEndOfRIBAnyFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L224). **positive:** `interop/nightly` [`checkNoFamilyEndOfRIB`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1196) | | `RFC4724-4-2` | Normal BGP procedures MUST be followed when the TCP session terminates due to sending or receiving a NOTIFICATION message (Section 4) | MUST | 4 | **positive:** `unit/verify` [`TestGRStateManagerNotificationBypass`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L220). **negative:** `unit/verify` [`TestGRStateManagerRouteRetention`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L44) | | `RFC4724-4.1-1` | Restarting Speaker MUST retain, if possible, the forwarding state for BGP routes in Loc-RIB (Section 4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's Restarting Speaker path implements only GR signaling -- it writes a restart marker and sets the R bit (internal/component/bgp/grmarker/grmarker.go, internal/component/bgp/reactor/peer.go:574) -- and does not retain its own in-memory Loc-RIB forwarding state across a process restart within the bgp packages; the Loc-RIB is rebuilt from scratch on restart | | `RFC4724-4.1-2` | Restarting Speaker MUST mark retained forwarding state as stale (Section 4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** because ze does not retain its own Loc-RIB across a restart (see RFC4724-4.1-1), there is no retained own-forwarding state to mark stale; the stale-marking machinery (internal/component/bgp/plugins/rib/rib_commands.go:817 markStaleCommand) applies to routes received from a restarting peer, not to ze's own routes on ze's restart | | `RFC4724-4.1-3` | Restarting Speaker MUST NOT differentiate between stale and other information during forwarding (Section 4.1) | MUST NOT | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** this governs forwarding over ze's own retained stale Loc-RIB, which ze does not build on restart (see RFC4724-4.1-1); the generic non-differentiation of level-1 stale in best-path selection (internal/component/bgp/plugins/rib/bestpath.go:308) is the Receiving Speaker path for peer routes, not ze's own routes as a Restarting Speaker | | `RFC4724-4.1-4` | Restarting Speaker MUST set the "Restart State" (R) bit in the Graceful Restart Capability of the OPEN message (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestSetRBitOnCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/grmarker/grmarker_test.go#L259). **negative:** `unit/verify` [`TestSetRBitTimeGatePattern`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/grmarker/grmarker_test.go#L498) | | `RFC4724-4.1-5` | Restarting Speaker MUST defer route selection per address family until End-of-RIB from all peers or Selection_Deferral_Timer expires (Section 4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze runs best-path selection as updates arrive and has no selection-deferral path keyed on End-of-RIB; the only End-of-RIB timer (internal/component/bgp/reactor/session_health.go:114 startEORTimer) raises a health warning and does not gate route selection | | `RFC4724-4.1-6` | After route selection, forwarding state MUST be updated and stale information MUST be removed (Section 4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** this is the completion step of the deferred-selection cycle ze does not run (see RFC4724-4.1-5); with no own-Loc-RIB stale state (RFC4724-4.1-1) there is no post-selection stale removal on ze's own restart | | `RFC4724-4.1-7` | Once initial update is complete, the End-of-RIB marker MUST be sent (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestBuildEOR_IPv4Unicast`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L20). **negative:** `unit/verify` [`TestIsEndOfRIBAnyFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L228) | | `RFC4724-4.1-8` | An implementation MUST support a configurable Selection_Deferral_Timer (Section 4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze exposes no Selection_Deferral_Timer configuration -- the GR YANG model (internal/component/bgp/plugins/gr/yang/ze-graceful-restart.yang) carries restart-time and long-lived-stale-time only, and no code defers selection (see RFC4724-4.1-5) | | `RFC4724-4.2-1` | Receiving Speaker MUST treat subsequent open connection from peer as termination of old TCP session when GR Capability was received (Section 4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze follows plain RFC 4271 Section 6.8 collision detection -- a new inbound connection while the session is Established is rejected with Cease/Connection Collision (internal/component/bgp/reactor/reactor_connection.go:134-136), with no GR-capability branch that treats the new OPEN as terminating the old session | | `RFC4724-4.2-2` | Previous TCP session MUST be closed, and the new one retained (Section 4.2) | MUST | 4.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the same RFC 4271 collision path closes the NEW connection and keeps the existing Established session (internal/component/bgp/reactor/reactor_connection.go:134-136 rejectConnectionCollisionWithSettings), the opposite of the RFC 4724 Section 4.2 override that closes the previous session and retains the new one | | `RFC4724-4.2-3` | Receiving Speaker MUST retain routes from peer for all address families previously in Graceful Restart Capability and MUST mark them as stale (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestGRStateManagerRouteRetention`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L40). **positive:** `unit/verify` [`TestRFC4724RetentionCoversEveryAdvertisedFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/rfc4724_retention_test.go#L124). **positive:** `unit/verify` [`TestRFC4724SessionDownRetainsAndMarksRoutesStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/rfc4724_retention_test.go#L73). **negative:** `unit/verify` [`TestGRStateManagerNoGRCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L297). **negative:** `unit/verify` [`TestRFC4724SessionDownWithoutCapabilityRetainsNothing`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/rfc4724_retention_test.go#L96) | | `RFC4724-4.2-4` | On consecutive restarts, previously stale routes from the peer MUST be deleted (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestGRConsecutiveRestartClearsPriorStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L272). **negative:** `unit/verify` [`TestGRConsecutiveRestartClearsPriorStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L286) | | `RFC4724-4.2-5` | Receiving Speaker MUST NOT differentiate between stale and other routing information during forwarding (Section 4.2) | MUST NOT | 4.2 | **positive:** `unit/verify` [`TestComparePair_GRStaleCompetesNormally`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L840). **negative:** `unit/verify` [`TestComparePair_LLGRStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L808) | | `RFC4724-4.2-6` | The "Restart State" bit in the Receiving Speaker's OPEN MUST NOT be set unless the Receiving Speaker has itself restarted (Section 4.2) | MUST NOT | 4.2 | **positive:** `unit/verify` [`TestSetRBitTimeGatePattern`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/grmarker/grmarker_test.go#L495). **negative:** `unit/verify` [`TestSetRBitOnCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/grmarker/grmarker_test.go#L262) | | `RFC4724-4.2-7` | If session not re-established within Restart Time, Receiving Speaker MUST delete all stale routes from the peer (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestGRStateManagerTimerExpiry`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L65). **negative:** `unit/verify` [`TestGRStateManagerReconnectWithFBit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L105) | | `RFC4724-4.2-8` | If F bit not set, or AFI/SAFI missing, or no GR capability on re-establishment, Receiving Speaker MUST immediately remove all stale routes for that address family (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestGRStateManagerReconnectFBitZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L143). **negative:** `unit/verify` [`TestGRStateManagerReconnectWithFBit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L102) | | `RFC4724-4.2-9` | Receiving Speaker MUST send End-of-RIB marker once it completes the initial update (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestBuildEOR_IPv4Unicast`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L22). **positive:** `unit/verify` [`TestInitialSyncEORReachesTheSilentFamilyToo`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L105). **negative:** `unit/verify` [`TestIsEndOfRIBAnyFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L229) | | `RFC4724-4.2-10` | Receiving Speaker MUST replace stale routes by routing updates received from the peer (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestFamilyRIB_InsertClearsStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/stale_test.go#L147). **negative:** `unit/verify` [`TestFamilyRIB_InsertNewDuringStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/stale_test.go#L179) | | `RFC4724-4.2-11` | Once End-of-RIB is received from peer, Receiving Speaker MUST immediately remove any routes still marked as stale for that address family (Section 4.2) | MUST | 4.2 | **positive:** `unit/verify` [`TestGRStateManagerEORPurge`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L185). **negative:** `unit/verify` [`TestGRStateManagerEORForNonGRPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L314) | | `RFC4724-2-1` | Sending End-of-RIB upon completion of initial update is recommended even without GR (Section 2) | RECOMMENDED | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC4724-4-3` | Advertising Graceful Restart Capability even without forwarding preservation ability is recommended (Section 4) | RECOMMENDED | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC4724-4-4` | A BGP speaker MAY advertise the Graceful Restart Capability for an address family if it can preserve forwarding state (Section 4) | MAY | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC4724-4.2-12` | Receiving Speaker MAY delete all stale routes if peer's forwarding state is determined non-viable (e.g., via BFD) (Section 4.2) | MAY | 4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4724-4.2-13` | An implementation MAY support a configurable stale route retention timer (Section 4.2) | MAY | 4.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4724-3-5`](#rfc4724-3-5) When R bit is set, peer MUST NOT wait for End-of-RIB marker from the speaker before advertising routing information (Section 3) | no test | no test carries this requirement id; annotated {not-applicable}: ze advertises its Adj-RIB-Out and per-family End-of-RIB immediately on reaching Established (internal/component/bgp/reactor/peer_initial_sync.go:277,334) and has no receive-side mechanism that gates advertisement on a peer's End-of-RIB; the only End-of-RIB timer (internal/component/bgp/reactor/session_health.go:114 startEORTimer) raises a health warning and never defers advertisement, so there is no wait state for the R bit to override | | [`RFC4724-4.1-1`](#rfc4724-4.1-1) Restarting Speaker MUST retain, if possible, the forwarding state for BGP routes in Loc-RIB (Section 4.1) | {gap}, no test | ze's Restarting Speaker path implements only GR signaling -- it writes a restart marker and sets the R bit (internal/component/bgp/grmarker/grmarker.go, internal/component/bgp/reactor/peer.go:574) -- and does not retain its own in-memory Loc-RIB forwarding state across a process restart within the bgp packages; the Loc-RIB is rebuilt from scratch on restart | | [`RFC4724-4.1-2`](#rfc4724-4.1-2) Restarting Speaker MUST mark retained forwarding state as stale (Section 4.1) | {gap}, no test | because ze does not retain its own Loc-RIB across a restart (see RFC4724-4.1-1), there is no retained own-forwarding state to mark stale; the stale-marking machinery (internal/component/bgp/plugins/rib/rib_commands.go:817 markStaleCommand) applies to routes received from a restarting peer, not to ze's own routes on ze's restart | | [`RFC4724-4.1-3`](#rfc4724-4.1-3) Restarting Speaker MUST NOT differentiate between stale and other information during forwarding (Section 4.1) | {gap}, no test | this governs forwarding over ze's own retained stale Loc-RIB, which ze does not build on restart (see RFC4724-4.1-1); the generic non-differentiation of level-1 stale in best-path selection (internal/component/bgp/plugins/rib/bestpath.go:308) is the Receiving Speaker path for peer routes, not ze's own routes as a Restarting Speaker | | [`RFC4724-4.1-5`](#rfc4724-4.1-5) Restarting Speaker MUST defer route selection per address family until End-of-RIB from all peers or Selection_Deferral_Timer expires (Section 4.1) | {gap}, no test | ze runs best-path selection as updates arrive and has no selection-deferral path keyed on End-of-RIB; the only End-of-RIB timer (internal/component/bgp/reactor/session_health.go:114 startEORTimer) raises a health warning and does not gate route selection | | [`RFC4724-4.1-6`](#rfc4724-4.1-6) After route selection, forwarding state MUST be updated and stale information MUST be removed (Section 4.1) | {gap}, no test | this is the completion step of the deferred-selection cycle ze does not run (see RFC4724-4.1-5); with no own-Loc-RIB stale state (RFC4724-4.1-1) there is no post-selection stale removal on ze's own restart | | [`RFC4724-4.1-8`](#rfc4724-4.1-8) An implementation MUST support a configurable Selection_Deferral_Timer (Section 4.1) | {gap}, no test | ze exposes no Selection_Deferral_Timer configuration -- the GR YANG model (internal/component/bgp/plugins/gr/yang/ze-graceful-restart.yang) carries restart-time and long-lived-stale-time only, and no code defers selection (see RFC4724-4.1-5) | | [`RFC4724-4.2-1`](#rfc4724-4.2-1) Receiving Speaker MUST treat subsequent open connection from peer as termination of old TCP session when GR Capability was received (Section 4.2) | {gap}, no test | ze follows plain RFC 4271 Section 6.8 collision detection -- a new inbound connection while the session is Established is rejected with Cease/Connection Collision (internal/component/bgp/reactor/reactor_connection.go:134-136), with no GR-capability branch that treats the new OPEN as terminating the old session | | [`RFC4724-4.2-2`](#rfc4724-4.2-2) Previous TCP session MUST be closed, and the new one retained (Section 4.2) | {gap}, no test | the same RFC 4271 collision path closes the NEW connection and keeps the existing Established session (internal/component/bgp/reactor/reactor_connection.go:134-136 rejectConnectionCollisionWithSettings), the opposite of the RFC 4724 Section 4.2 override that closes the previous session and retains the new one | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4724-3-1`](#rfc4724-3-1) A BGP speaker MUST NOT include more than one instance of the Graceful Restart Capability in the capability advertisement (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestExtractGRCapabilities_CapabilityDecl`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_test.go#L111) | unit/verify | unproven | ### [`RFC4724-3-2`](#rfc4724-3-2) Reserved bits in Restart Flags MUST be set to zero by the sender (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGracefulRestartEncodeReservedBits`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L433) | unit/verify | unproven | | positive | [`TestGracefulRestartEncodeReservedBits`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L422) | unit/verify | unproven | ### [`RFC4724-3-3`](#rfc4724-3-3) Reserved bits in Address Family Flags MUST be set to zero by the sender (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGracefulRestartEncodeReservedBits`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L442) | unit/verify | unproven | | positive | [`TestGracefulRestartEncodeReservedBits`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L428) | unit/verify | unproven | ### [`RFC4724-3-4`](#rfc4724-3-4) If more than one instance of the Graceful Restart Capability is received, the receiver MUST ignore all but the last instance (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegotiateGracefulRestartLastInstance`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L70) | unit/verify | unproven | | positive | [`TestNegotiateGracefulRestartLastInstance`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L62) | unit/verify | unproven | ### [`RFC4724-3-5`](#rfc4724-3-5) When R bit is set, peer MUST NOT wait for End-of-RIB marker from the speaker before advertising routing information (Section 3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-3-5, so no unit is bound to it. ### [`RFC4724-4-1`](#rfc4724-4-1) The End-of-RIB marker MUST be sent by a BGP speaker to its peer once it completes the initial routing update for an address family (Section 4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIsEndOfRIBAnyFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L224) | unit/verify | unproven | | positive | [`TestBuildEOR_IPv4Unicast`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L16) | unit/verify | unproven | | positive | [`TestInitialSyncBarrierCreditsOnlyTheProcessesItNames`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L760) | unit/verify | unproven | | positive | [`TestInitialSyncClosesTheQueueGateBeforeItWaitsForRoutePushingPlugins`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L342) | unit/verify | unproven | | positive | [`TestInitialSyncEORReachesTheSilentFamilyToo`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L100) | unit/verify | unproven | | positive | [`TestInitialSyncEORSentWhenNeitherSideDeclaredAFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L726) | unit/verify | unproven | | positive | [`TestRoutePushingBindingsCountBothRails`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L467) | unit/verify | unproven | | positive | [`checkNoFamilyEndOfRIB`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1196) | interop/nightly | unproven | ### [`RFC4724-4-2`](#rfc4724-4-2) Normal BGP procedures MUST be followed when the TCP session terminates due to sending or receiving a NOTIFICATION message (Section 4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGRStateManagerRouteRetention`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L44) | unit/verify | unproven | | positive | [`TestGRStateManagerNotificationBypass`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L220) | unit/verify | unproven | ### [`RFC4724-4.1-1`](#rfc4724-4.1-1) Restarting Speaker MUST retain, if possible, the forwarding state for BGP routes in Loc-RIB (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-4.1-1, so no unit is bound to it. ### [`RFC4724-4.1-2`](#rfc4724-4.1-2) Restarting Speaker MUST mark retained forwarding state as stale (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-4.1-2, so no unit is bound to it. ### [`RFC4724-4.1-3`](#rfc4724-4.1-3) Restarting Speaker MUST NOT differentiate between stale and other information during forwarding (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-4.1-3, so no unit is bound to it. ### [`RFC4724-4.1-4`](#rfc4724-4.1-4) Restarting Speaker MUST set the "Restart State" (R) bit in the Graceful Restart Capability of the OPEN message (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSetRBitTimeGatePattern`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/grmarker/grmarker_test.go#L498) | unit/verify | unproven | | positive | [`TestSetRBitOnCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/grmarker/grmarker_test.go#L259) | unit/verify | unproven | ### [`RFC4724-4.1-5`](#rfc4724-4.1-5) Restarting Speaker MUST defer route selection per address family until End-of-RIB from all peers or Selection_Deferral_Timer expires (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-4.1-5, so no unit is bound to it. ### [`RFC4724-4.1-6`](#rfc4724-4.1-6) After route selection, forwarding state MUST be updated and stale information MUST be removed (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-4.1-6, so no unit is bound to it. ### [`RFC4724-4.1-7`](#rfc4724-4.1-7) Once initial update is complete, the End-of-RIB marker MUST be sent (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIsEndOfRIBAnyFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L228) | unit/verify | unproven | | positive | [`TestBuildEOR_IPv4Unicast`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L20) | unit/verify | unproven | ### [`RFC4724-4.1-8`](#rfc4724-4.1-8) An implementation MUST support a configurable Selection_Deferral_Timer (Section 4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-4.1-8, so no unit is bound to it. ### [`RFC4724-4.2-1`](#rfc4724-4.2-1) Receiving Speaker MUST treat subsequent open connection from peer as termination of old TCP session when GR Capability was received (Section 4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-4.2-1, so no unit is bound to it. ### [`RFC4724-4.2-2`](#rfc4724-4.2-2) Previous TCP session MUST be closed, and the new one retained (Section 4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4724-4.2-2, so no unit is bound to it. ### [`RFC4724-4.2-3`](#rfc4724-4.2-3) Receiving Speaker MUST retain routes from peer for all address families previously in Graceful Restart Capability and MUST mark them as stale (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGRStateManagerNoGRCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L297) | unit/verify | unproven | | negative | [`TestRFC4724SessionDownWithoutCapabilityRetainsNothing`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/rfc4724_retention_test.go#L96) | unit/verify | unproven | | positive | [`TestGRStateManagerRouteRetention`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L40) | unit/verify | unproven | | positive | [`TestRFC4724RetentionCoversEveryAdvertisedFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/rfc4724_retention_test.go#L124) | unit/verify | unproven | | positive | [`TestRFC4724SessionDownRetainsAndMarksRoutesStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/rfc4724_retention_test.go#L73) | unit/verify | unproven | ### [`RFC4724-4.2-4`](#rfc4724-4.2-4) On consecutive restarts, previously stale routes from the peer MUST be deleted (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGRConsecutiveRestartClearsPriorStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L286) | unit/verify | unproven | | positive | [`TestGRConsecutiveRestartClearsPriorStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L272) | unit/verify | unproven | ### [`RFC4724-4.2-5`](#rfc4724-4.2-5) Receiving Speaker MUST NOT differentiate between stale and other routing information during forwarding (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestComparePair_LLGRStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L808) | unit/verify | unproven | | positive | [`TestComparePair_GRStaleCompetesNormally`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/bestpath_test.go#L840) | unit/verify | unproven | ### [`RFC4724-4.2-6`](#rfc4724-4.2-6) The "Restart State" bit in the Receiving Speaker's OPEN MUST NOT be set unless the Receiving Speaker has itself restarted (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSetRBitOnCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/grmarker/grmarker_test.go#L262) | unit/verify | unproven | | positive | [`TestSetRBitTimeGatePattern`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/grmarker/grmarker_test.go#L495) | unit/verify | unproven | ### [`RFC4724-4.2-7`](#rfc4724-4.2-7) If session not re-established within Restart Time, Receiving Speaker MUST delete all stale routes from the peer (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGRStateManagerReconnectWithFBit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L105) | unit/verify | unproven | | positive | [`TestGRStateManagerTimerExpiry`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L65) | unit/verify | unproven | ### [`RFC4724-4.2-8`](#rfc4724-4.2-8) If F bit not set, or AFI/SAFI missing, or no GR capability on re-establishment, Receiving Speaker MUST immediately remove all stale routes for that address family (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGRStateManagerReconnectWithFBit`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L102) | unit/verify | unproven | | positive | [`TestGRStateManagerReconnectFBitZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L143) | unit/verify | unproven | ### [`RFC4724-4.2-9`](#rfc4724-4.2-9) Receiving Speaker MUST send End-of-RIB marker once it completes the initial update (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIsEndOfRIBAnyFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L229) | unit/verify | unproven | | positive | [`TestBuildEOR_IPv4Unicast`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/eor_test.go#L22) | unit/verify | unproven | | positive | [`TestInitialSyncEORReachesTheSilentFamilyToo`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_initial_sync_test.go#L105) | unit/verify | unproven | ### [`RFC4724-4.2-10`](#rfc4724-4.2-10) Receiving Speaker MUST replace stale routes by routing updates received from the peer (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFamilyRIB_InsertNewDuringStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/stale_test.go#L179) | unit/verify | unproven | | positive | [`TestFamilyRIB_InsertClearsStale`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/storage/stale_test.go#L147) | unit/verify | unproven | ### [`RFC4724-4.2-11`](#rfc4724-4.2-11) Once End-of-RIB is received from peer, Receiving Speaker MUST immediately remove any routes still marked as stale for that address family (Section 4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGRStateManagerEORForNonGRPeer`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L314) | unit/verify | unproven | | positive | [`TestGRStateManagerEORPurge`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/gr/gr_state_test.go#L185) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 4724, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4724, so its obligations are stated where they were written. --- ### Page: RFC 4760 - Multiprotocol Extensions for BGP-4 https://ze-software.net/quality/rfc-compliance/rfc4760/ # RFC 4760 - Multiprotocol Extensions for BGP-4 Supported. Every requirement this repository extracted from RFC 4760, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 66.7% | 4 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 33.3% | 2 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 9.1% | 2 of 22 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 17 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 17 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 22 | | Tagged units | 22 | | Recorded audit verdicts | 0 | | Discrimination records | 2 | | Summary | `rfc/short/rfc4760.md` | | Requirement shard | `rfc/requirements/rfc4760.md` | | RFC text | `rfc/full/rfc4760.txt` | ## Enrolment Enrolled: Multiprotocol Extensions for BGP-4: six MUST-level requirements, all six met: 3-2 (the next-hop length determines the next-hop protocol) carries positive+negative tags; 3-3 (an UPDATE with MP_REACH_NLRI also carries ORIGIN and AS_PATH) and 3-4 (an iBGP UPDATE carrying MP_REACH includes LOCAL_PREF) carry positive+negative tags on new internal/component/bgp/message tests; 3-1 (the MP_REACH Reserved octet is 0) and 8-1 (advertise the Multiprotocol capability) are {single-polarity: positive}. 7-1 (Section 7 bulk per-AFI/SAFI route deletion) carries positive+negative tags on internal/component/bgp/reactor/rfc4760_section7_test.go. The non-applicability annotation that stood here, claiming RFC 7606 superseded the behavior, was voided by the owner on 2026-08-31: RFC 7606 Section 3 clause (j) keeps the obligation, and Ze meets it by session reset, which drops every route from that neighbor and so a superset of that AFI/SAFI's routes. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** AFI/SAFI capability negotiation, MP_REACH_NLRI, MP_UNREACH_NLRI, family-specific UPDATE handling. **What the ledger says remains:** RFC 7606 MP attribute ordering tradeoff is tracked under RFC 7606. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 2 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`RFC4760-3-2`](#rfc4760-3-2), [`RFC4760-3-3`](#rfc4760-3-3), [`RFC4760-3-4`](#rfc4760-3-4), [`RFC4760-7-1`](#rfc4760-7-1) **Annotated instead of tested (2):** [`RFC4760-3-1`](#rfc4760-3-1), [`RFC4760-8-1`](#rfc4760-8-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4760-3-1` | Reserved field in MP_REACH_NLRI MUST be set to 0 (Section 3) | MUST | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** `unit/verify` [`TestMPReachNLRI_WriteTo`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L12). **positive:** `unit/verify` [`TestRFC4760ReservedIsWrittenNotInherited`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4760_reserved_test.go#L37). **positive:** `unit/verify` [`TestRFC4760ReservedSurvivesBufferReuse`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4760_reserved_test.go#L122). **negative:** no negative test. **{single-polarity}:** MPReachNLRI.WriteTo writes the Reserved octet as 0 unconditionally, so there is no non-zero form to reject (internal/core/bgp/attribute/mpnlri.go:182) | | `RFC4760-3-2` | If Next Hop is allowed to be from more than one Network Layer protocol, encoding of the Next Hop MUST provide a way to determine its Network Layer protocol (Section 3) | MUST | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** `unit/verify` [`TestCommitVPNAnnounceCarriesTheRFC4364NextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/rib/commit_nexthop_test.go#L155). **positive:** `unit/verify` [`TestMPReachNLRI_WriteTo`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L16). **positive:** `unit/verify` [`TestMPReachNextHopLengthCountsTheOctetsWritten`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_nexthop_wire_test.go#L39). **positive:** `unit/verify` [`TestParseMPReachNLRI`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L128). **negative:** `unit/verify` [`TestBuildRIBRouteUpdate_RefusesANextHopWithNoWireForm`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_rib_routes_nexthop_test.go#L50). **negative:** `unit/verify` [`TestCommitRefusesAnAnnounceWhoseNextHopHasNoWireForm`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/rib/commit_nexthop_test.go#L69). **negative:** `unit/verify` [`TestMPReachValidateNextHopsRefusesAnAddressWithNoWireForm`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_nexthop_wire_test.go#L124). **negative:** `unit/verify` [`TestParseMPReachNLRI_InvalidNextHopLength`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L498) | | `RFC4760-3-3` | An UPDATE message carrying MP_REACH_NLRI MUST also carry the ORIGIN and AS_PATH attributes (Section 3) | MUST | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** `unit/verify` [`TestRFC4760MPReachRequiresOriginAndASPath`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L85). **negative:** `unit/verify` [`TestRFC4760MPReachRequiresOriginAndASPath`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L88) | | `RFC4760-3-4` | In IBGP exchanges, an UPDATE with MP_REACH_NLRI MUST also carry the LOCAL_PREF attribute (Section 3) | MUST | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** `unit/verify` [`TestRFC4760IBGPMPReachCarriesLocalPref`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L149). **negative:** `unit/verify` [`TestRFC4760IBGPMPReachCarriesLocalPref`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L153) | | `RFC4760-7-1` | On incorrect MP_REACH_NLRI or MP_UNREACH_NLRI, the speaker MUST delete all BGP routes from that neighbor for that AFI/SAFI (Section 7). RFC 7606 does not retire this: Section 3 clause (j) says that when the MP attribute cannot be parsed, "the procedures of [RFC4271] and/or [RFC4760] continue to apply, meaning that the 'session reset' approach (or the 'AFI/SAFI disable' approach) MUST be followed". Ze follows session reset, the stronger of the two, which drops every route from that neighbor and so a superset of that AFI/SAFI's routes. ValidateUpdateRFC7606 (internal/component/bgp/message/rfc7606.go) returns RFC7606ActionSessionReset for an unparseable MP_REACH_NLRI or MP_UNREACH_NLRI, and rfc7606SessionReset (internal/component/bgp/reactor/session_validation.go) sends the NOTIFICATION and closes the connection. | MUST | 7 - Error Handling | **positive:** `unit/verify` [`TestHandleState_PeerDown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_test.go#L298). **positive:** `unit/verify` [`TestRFC4760IncorrectMPAttributeResetsTheSession`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L240). **positive:** `unit/verify` [`TestRFC4760IncorrectMPReachDeletesTheNeighborsRoutes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4760_section7_test.go#L102). **positive:** `unit/verify` [`TestRFC4760IncorrectMPUnreachDeletesTheNeighborsRoutes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4760_section7_test.go#L139). **negative:** `unit/verify` [`TestRFC4760CorrectMPReachKeepsTheNeighborsRoutes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4760_section7_test.go#L171). **negative:** `unit/verify` [`TestRFC4760IncorrectMPAttributeResetsTheSession`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L244) | | `RFC4760-8-1` | For bi-directional exchange of routing information for a particular AFI/SAFI, each speaker MUST advertise the capability to support that AFI/SAFI via Capability Advertisement (Section 8) | MUST | 8 - Use of BGP Capability Advertisement | **positive:** `unit/verify` [`TestParseCapabilities`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L65). **negative:** no negative test. **{single-polarity}:** the obligation is to advertise a well-formed Multiprotocol capability per AFI/SAFI, enforced by the fixed 4-octet AFI/Reserved/SAFI wire form (internal/core/bgp/capability/capability.go:299-318); there is no malformed-advertisement counter-case distinct from RFC 5492 TLV framing | | `RFC4760-3-5` | Reserved field in MP_REACH_NLRI SHOULD be ignored upon receipt (Section 3) | SHOULD | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** no positive test. **negative:** no negative test | | `RFC4760-3-6` | The next hop in MP_REACH_NLRI SHOULD be used as the next hop to listed destinations (Section 3) | SHOULD | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** no positive test. **negative:** no negative test | | `RFC4760-3-7` | An UPDATE carrying no NLRI other than MP_REACH_NLRI SHOULD NOT carry the NEXT_HOP attribute (Section 3) | SHOULD NOT | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** no positive test. **negative:** no negative test | | `RFC4760-3-8` | If such a message contains NEXT_HOP, the receiver SHOULD ignore this attribute (Section 3) | SHOULD | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** no positive test. **negative:** no negative test | | `RFC4760-3-9` | An UPDATE SHOULD NOT include the same address prefix in more than one of WITHDRAWN ROUTES, NLRI, MP_REACH_NLRI, and MP_UNREACH_NLRI fields (Section 3) | SHOULD NOT | 3 - Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | **positive:** no positive test. **negative:** no negative test | | `RFC4760-7-2` | After deleting routes for an incorrect attribute, speaker SHOULD ignore all subsequent routes with that AFI/SAFI for the session (Section 7) | SHOULD | 7 - Error Handling | **positive:** no positive test. **negative:** no negative test | | `RFC4760-7-3` | Session SHOULD be terminated with UPDATE Message Error / Optional Attribute Error on incorrect attribute (Section 7) | SHOULD | 7 - Error Handling | **positive:** no positive test. **negative:** no negative test | | `RFC4760-8-2` | A BGP speaker using Multiprotocol Extensions SHOULD use Capability Advertisement to determine support with a peer (Section 8) | SHOULD | 8 - Use of BGP Capability Advertisement | **positive:** no positive test. **negative:** no negative test | | `RFC4760-8-3` | Reserved field in Multiprotocol Capability SHOULD be set to 0 by sender and ignored by receiver (Section 8) | SHOULD | 8 - Use of BGP Capability Advertisement | **positive:** no positive test. **negative:** no negative test | | `RFC4760-7-4` | The speaker MAY terminate the BGP session on incorrect MP_REACH_NLRI or MP_UNREACH_NLRI (Section 7) | MAY | 7 - Error Handling | **positive:** no positive test. **negative:** no negative test | | `RFC4760-6-1` | An implementation MAY support all, some, or none of the SAFI values defined in this document (Section 6) | MAY | 6 - Subsequent Address Family Identifier | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 4760 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4760-3-1`](#rfc4760-3-1) Reserved field in MP_REACH_NLRI MUST be set to 0 (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestMPReachNLRI_WriteTo`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L12) | unit/verify | unproven | | positive | [`TestRFC4760ReservedIsWrittenNotInherited`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4760_reserved_test.go#L37) | unit/verify | unproven | | positive | [`TestRFC4760ReservedSurvivesBufferReuse`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc4760_reserved_test.go#L122) | unit/verify | unproven | ### [`RFC4760-3-2`](#rfc4760-3-2) If Next Hop is allowed to be from more than one Network Layer protocol, encoding of the Next Hop MUST provide a way to determine its Network Layer protocol (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildRIBRouteUpdate_RefusesANextHopWithNoWireForm`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_rib_routes_nexthop_test.go#L50) | unit/verify | unproven | | negative | [`TestCommitRefusesAnAnnounceWhoseNextHopHasNoWireForm`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/rib/commit_nexthop_test.go#L69) | unit/verify | revert, verified | | negative | [`TestMPReachValidateNextHopsRefusesAnAddressWithNoWireForm`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_nexthop_wire_test.go#L124) | unit/verify | unproven | | negative | [`TestParseMPReachNLRI_InvalidNextHopLength`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L498) | unit/verify | unproven | | positive | [`TestCommitVPNAnnounceCarriesTheRFC4364NextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/rib/commit_nexthop_test.go#L155) | unit/verify | revert, verified | | positive | [`TestMPReachNextHopLengthCountsTheOctetsWritten`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_nexthop_wire_test.go#L39) | unit/verify | unproven | | positive | [`TestMPReachNLRI_WriteTo`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L16) | unit/verify | unproven | | positive | [`TestParseMPReachNLRI`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L128) | unit/verify | unproven | ### [`RFC4760-3-3`](#rfc4760-3-3) An UPDATE message carrying MP_REACH_NLRI MUST also carry the ORIGIN and AS_PATH attributes (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4760MPReachRequiresOriginAndASPath`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L88) | unit/verify | unproven | | positive | [`TestRFC4760MPReachRequiresOriginAndASPath`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L85) | unit/verify | unproven | ### [`RFC4760-3-4`](#rfc4760-3-4) In IBGP exchanges, an UPDATE with MP_REACH_NLRI MUST also carry the LOCAL_PREF attribute (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4760IBGPMPReachCarriesLocalPref`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L153) | unit/verify | unproven | | positive | [`TestRFC4760IBGPMPReachCarriesLocalPref`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L149) | unit/verify | unproven | ### [`RFC4760-7-1`](#rfc4760-7-1) On incorrect MP_REACH_NLRI or MP_UNREACH_NLRI, the speaker MUST delete all BGP routes from that neighbor for that AFI/SAFI (Section 7). RFC 7606 does not retire this: Section 3 clause (j) says that when the MP attribute cannot be parsed, "the procedures of [RFC4271] and/or [RFC4760] continue to apply, meaning that the 'session reset' approach (or the 'AFI/SAFI disable' approach) MUST be followed". Ze follows session reset, the stronger of the two, which drops every route from that neighbor and so a superset of that AFI/SAFI's routes. ValidateUpdateRFC7606 (internal/component/bgp/message/rfc7606.go) returns RFC7606ActionSessionReset for an unparseable MP_REACH_NLRI or MP_UNREACH_NLRI, and rfc7606SessionReset (internal/component/bgp/reactor/session_validation.go) sends the NOTIFICATION and closes the connection. Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC4760IncorrectMPAttributeResetsTheSession`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L244) | unit/verify | unproven | | negative | [`TestRFC4760CorrectMPReachKeepsTheNeighborsRoutes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4760_section7_test.go#L171) | unit/verify | unproven | | positive | [`TestRFC4760IncorrectMPAttributeResetsTheSession`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/message/rfc4760_mp_reach_test.go#L240) | unit/verify | unproven | | positive | [`TestHandleState_PeerDown`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/rib/rib_test.go#L298) | unit/verify | unproven | | positive | [`TestRFC4760IncorrectMPReachDeletesTheNeighborsRoutes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4760_section7_test.go#L102) | unit/verify | unproven | | positive | [`TestRFC4760IncorrectMPUnreachDeletesTheNeighborsRoutes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/rfc4760_section7_test.go#L139) | unit/verify | unproven | ### [`RFC4760-8-1`](#rfc4760-8-1) For bi-directional exchange of routing information for a particular AFI/SAFI, each speaker MUST advertise the capability to support that AFI/SAFI via Capability Advertisement (Section 8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestParseCapabilities`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L65) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-fixit-rfc-drain-quota-never-armed WP-1 | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc4760.txt | | Source fingerprint | 7b28975d269770a5 | | Record | rfc/extraction/rfc4760.json | | Mapped sentences | 6 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of This Memo, Copyright Notice and Abstract. The Abstract states that the document extends BGP-4 to carry routing information for multiple Network Layer protocols and that the extensions are backward compatible. It binds no speaker. | | `1` | Introduction | 0 | walked | Introduction. Indicative prose naming the three IPv4-specific pieces of BGP-4 (NEXT_HOP, AGGREGATOR and NLRI) and the two attributes this document adds, both optional and non-transitive so a speaker without the extension ignores them. Its only modal is the lowercase 'should' of 'the advertisement of reachable destinations should be grouped with ... the next hop', which describes the design rationale for MP_REACH_NLRI rather than directing a speaker. Every obligation it foreshadows is stated normatively in sections 3, 7 and 8. | | `2` | not stated | 0 | walked | Specification of Requirements: the RFC 2119 key-words paragraph. It tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. | | `3` | Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) | 5 | walked | Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14). The wire format, the per-field semantics, and the attribute-companion rules. Five derived sites: 3:1 and 3:2 are the same Next Hop encoding sentence repeated under the AFI and SAFI field headings, 3:3 is the Reserved octet, and 3:4 and 3:5 are the ORIGIN/AS_PATH and LOCAL_PREF companions. The section's five advisory rows are listed below: each is a SHOULD or SHOULD NOT sentence the MUST-level site inventory cannot see, and RFC4760-3-5 shares its sentence with site 3:3. | | `4` | not stated | 2 | walked | Multiprotocol Unreachable NLRI - MP_UNREACH_NLRI (Type Code 15). The attribute carries AFI, SAFI and Withdrawn Routes only. Both derived sites are the AFI and SAFI field paragraphs copied from section 3, Next Hop sentence included, which is the copy verified errata 1573 reports: MP_UNREACH_NLRI has no Next Hop field. The section adds one sentence of its own, 'An UPDATE message that contains the MP_UNREACH_NLRI is not required to carry any other path attributes', which relaxes section 3 rather than obliging anyone. The summary declares no id from this section. | | `5` | NLRI Encoding | 0 | walked | NLRI Encoding. Defines the <length, prefix> 2-tuple, that a zero length matches all addresses of the family, and that the value of the trailing bits is irrelevant. Descriptive throughout, with no modal of any case, so the summary reads no requirement from it. | | `6` | Subsequent Address Family Identifier | 0 | walked | Subsequent Address Family Identifier. Assigns SAFI 1 to unicast forwarding and SAFI 2 to multicast forwarding, then states one MAY. A value assignment carries no obligation, and the MAY is advisory so the MUST-level inventory cannot see it. | | `7` | Error Handling | 1 | walked | Error Handling. Its one MUST is site 7:1. The other three sentences are the SHOULD to ignore subsequent routes of that AFI/SAFI, the MAY to terminate the session, and the SHOULD that fixes the Notification code/subcode when it is terminated. All three are advisory and are listed below. | | `8` | Use of BGP Capability Advertisement | 1 | walked | Use of BGP Capability Advertisement. Its one MUST is site 8:1. The opening SHOULD to use Capability Advertisement and the Res. field's SHOULD are advisory and are listed below. The Capability Code 1 and Capability Length 4 sentences are value assignments, and 'A speaker that supports multiple <AFI, SAFI> tuples includes them as multiple Capabilities' is indicative prose the summary captures as wire format rather than as a requirement row. | | `9` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Defines the SAFI name space: 1 and 2 assigned, 3 deprecated, the allocation policy for 5-63 and 67-127, and the reserved and private-use ranges. Binds IANA, not a speaker. | | `10` | Comparison with RFC 2858 | 0 | walked | Comparison with RFC 2858. A change log against the obsoleted document: next hop use made consistent with NEXT_HOP, SAFI 3 deprecated, the SAFI partitioning changed, and Number of SNPAs renamed to Reserved. Its one lowercase 'should be considered reserved' restates the IANA action of section 9 and binds IANA. | | `11` | Comparison with RFC 2283 | 0 | walked | Comparison with RFC 2283. A change log: one instance per attribute, the no-NLRI clarification, the error-handling clarification, and the addition of Capability Advertisement. Every item it names is stated normatively in sections 3, 7 or 8 and is captured there. | | `12` | Security Considerations | 0 | walked | Security Considerations. One sentence: the extension does not change the security issues inherent in existing BGP. No countermeasure is directed at a speaker. | | `13` | Acknowledgements: the IDR Working Group | 0 | skipped (acknowledgements) | Acknowledgements: the IDR Working Group. | | `14` | not stated | 0 | skipped (references) | Normative References: RFC 3392, RFC 4271, IANA Address Family Numbers, RFC 2119, RFC 2434, RFC 4020. The section also absorbs the Authors' Addresses block, the Full Copyright Statement and the Intellectual Property notice, none of which binds a speaker. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `3:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The identical sentence, repeated word for word under the Subsequent Address Family Identifier field heading because AFI and SAFI are described as a pair. Site 3:1 maps RFC4760-3-2; this is the same obligation stated once more, not a second one. | If the Next Hop is allowed to be from more than one Network Layer protocol, the encoding of the Next Hop MUST provide a way to determine its Network Layer protocol. | | `4:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The section 3 AFI paragraph copied into MP_UNREACH_NLRI, Next Hop sentence included. Verified errata 1573 records that this text was copied from MP_REACH_NLRI without adjustment and that MP_UNREACH_NLRI carries no Next Hop field, so the sentence states no obligation here beyond the one site 3:1 maps as RFC4760-3-2. | If the Next Hop is allowed to be from more than one Network Layer protocol, the encoding of the Next Hop MUST provide a way to determine its Network Layer protocol. | | `4:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The same copy again, under the SAFI field heading of MP_UNREACH_NLRI. Fourth occurrence of one sentence; site 3:1 maps it as RFC4760-3-2. | If the Next Hop is allowed to be from more than one Network Layer protocol, the encoding of the Next Hop MUST provide a way to determine its Network Layer protocol. | ## Superseded No document obsoletes RFC 4760, so its obligations are stated where they were written. --- ### Page: RFC 4761 - Virtual Private LAN Service (VPLS) Using BGP for Auto-Discovery and Signaling https://ze-software.net/quality/rfc-compliance/rfc4761/ # RFC 4761 - Virtual Private LAN Service (VPLS) Using BGP for Auto-Discovery and Signaling Partial. Every requirement this repository extracted from RFC 4761, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 18 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 18 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 18 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 18 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 18 | of 27 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 18 | of 18 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 18 of 18 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 18 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 18 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 18 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 27 | | Gated MUST-level | 18 | | Not applicable, so out of scope | 18 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4761.md` | | Requirement shard | `rfc/requirements/rfc4761.md` | | RFC text | `rfc/full/rfc4761.txt` | ## Enrolment Enrolled: Virtual Private LAN Service / VPLS Using BGP (RFC 4761): all 18 gated MUSTs not-applicable. ze is not a VPLS PE -- its RFC 4761 support is a decode-mode L2VPN/VPLS (AFI 25, SAFI 65) NLRI codec (internal/component/bgp/plugins/nlri/vpls/) plus an operator-driven Layer2 Info ext-community passthrough; no VSI, no pseudowire signaling, no MAC FIB, received L2VPN routes stored as opaque raw NLRI blobs (adj_rib_in/rib.go). The gated MUSTs are all VPLS-PE control-plane signaling and data-plane forwarding duties ze does not perform. Same delegation pattern as enrolled RFC 4364 ## What the public ledger says **Status:** Partial **What the ledger says is covered:** L2VPN VPLS family registration, encode, decode, and route config. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 18 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **18** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (18):** [`RFC4761-2.3-1`](#rfc4761-2.3-1), [`RFC4761-3.2.4-1`](#rfc4761-3.2.4-1), [`RFC4761-3.2.4-2`](#rfc4761-3.2.4-2), [`RFC4761-3.2.4-3`](#rfc4761-3.2.4-3), [`RFC4761-3.2.4-4`](#rfc4761-3.2.4-4), [`RFC4761-3.2.4-5`](#rfc4761-3.2.4-5), [`RFC4761-3.2.4-6`](#rfc4761-3.2.4-6), [`RFC4761-3.2.3-1`](#rfc4761-3.2.3-1), [`RFC4761-3.2.3-2`](#rfc4761-3.2.3-2), [`RFC4761-3.3-1`](#rfc4761-3.3-1), [`RFC4761-3.4.2-1`](#rfc4761-3.4.2-1), [`RFC4761-3.5-1`](#rfc4761-3.5-1), [`RFC4761-3.5-2`](#rfc4761-3.5-2), [`RFC4761-4.2.1-1`](#rfc4761-4.2.1-1), [`RFC4761-4.2.2-1`](#rfc4761-4.2.2-1), [`RFC4761-4.2.5-1`](#rfc4761-4.2.5-1), [`RFC4761-6-1`](#rfc4761-6-1), [`RFC4761-6-2`](#rfc4761-6-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4761-2.3-1` | An implementation MUST maintain a separate routing storage for each service (Section 2.3) | MUST | 2.3 - Interactions | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** maintaining a separate routing/forwarding store per VPLS service is a VPLS-PE VSI duty; ze is not a VPLS PE: its RFC 4761 support is a control-plane L2VPN/VPLS (AFI 25, SAFI 65) NLRI codec (internal/component/bgp/plugins/nlri/vpls/vpls.go:45 Mode decode; types.go:82 ParseVPLS, types.go:163 WriteTo) plus route-injection encoders, with no VPLS instance (VSI), no pseudowire signaling, no MAC FIB, and no VPLS forwarding data path, so there is no per-service VSI store to maintain | | `RFC4761-3.2.4-1` | MBZ bits in Control Flags MUST be set to zero when sending (Section 3.2.4) | MUST | 3.2.4 - Signaling PE Capabilities | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** signalling the Layer2 Info Extended Community (0x800A) Control Flags is a duty of a VPLS PE originating auto-discovery; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no PW signaling), and the only l2info code is an operator-driven raw ext-community encoder that packs the supplied control byte verbatim (internal/component/bgp/config/routeattr_community.go:283-305 parseL2InfoExtCommunity), not a PE Control-Flags sender, so ze originates no VPLS Control Flags whose MBZ bits it must zero | | `RFC4761-3.2.4-2` | MBZ bits in Control Flags MUST be ignored when receiving (Section 3.2.4) | MUST | 3.2.4 - Signaling PE Capabilities | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ignoring MBZ bits on receipt of the Layer2 Info Control Flags is a VPLS-PE receive duty; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no PW state) and has no Layer2 Info decoder, so no MBZ-on-receive processing exists to exercise | | `RFC4761-3.2.4-3` | When C flag is 1, a Control Word MUST be present when sending VPLS packets to that PE (Section 3.2.4) | MUST | 3.2.4 - Signaling PE Capabilities | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** inserting a Control Word into VPLS packets when the C flag is 1 is a pseudowire data-plane encapsulation choice; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no PW signaling, no VPLS forwarding data path), so it sends no VPLS packets and the C-flag data-plane obligation has no host | | `RFC4761-3.2.4-4` | When C flag is 0, a Control Word MUST NOT be present when sending VPLS packets to that PE (Section 3.2.4) | MUST NOT | 3.2.4 - Signaling PE Capabilities | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** omitting the Control Word from VPLS packets when the C flag is 0 is a pseudowire data-plane choice; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it sends no VPLS packets to constrain | | `RFC4761-3.2.4-5` | When S flag is 1, sequenced delivery of frames MUST be used when sending VPLS packets to that PE (Section 3.2.4) | MUST | 3.2.4 - Signaling PE Capabilities | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** using sequenced delivery for VPLS packets when the S flag is 1 is a pseudowire data-plane behavior; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it delivers no VPLS packets | | `RFC4761-3.2.4-6` | When S flag is 0, sequenced delivery MUST NOT be used when sending VPLS packets to that PE (Section 3.2.4) | MUST NOT | 3.2.4 - Signaling PE Capabilities | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** refraining from sequenced delivery when the S flag is 0 is a pseudowire data-plane behavior; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it delivers no VPLS packets | | `RFC4761-3.2.3-1` | If announcing PE's VE ID is not covered by any remote VE set that PE-b announced, PE-b MUST make a new announcement covering it (Section 3.2.3) | MUST | 3.2.3 - PW Setup and Teardown | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** computing and announcing a new label block to cover a peer's VE ID is VPLS-PE auto-discovery signaling; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI), and runs no label-block allocation or pseudowire-setup algorithm | | `RFC4761-3.2.3-2` | If Y withdraws an NLRI for V that X was using, X MUST tear down its ends of the pseudowire between X and Y (Section 3.2.3) | MUST | 3.2.3 - PW Setup and Teardown | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** tearing down pseudowire ends when a peer withdraws its NLRI is a VPLS-PE data-plane action; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it establishes and tears down no pseudowires | | `RFC4761-3.3-1` | PE-a MUST withdraw all its announcements for VPLS foo that contain VE ID V when V is removed from configuration (Section 3.3) | MUST | 3.3 - BGP VPLS Operation | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** withdrawing a PE's own VPLS announcements when a VE ID leaves the VSI configuration is a VPLS-PE lifecycle duty; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45), and has no VSI/VE configuration model that originates or withdraws such announcements | | `RFC4761-3.4.2-1` | Inter-AS Method (b): Length, Route Distinguisher, VE ID, VE Block Offset, and VE Block Size MUST be the same when ASBR re-advertises (Section 3.4.2) | MUST | 3.4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** re-advertising a VPLS NLRI with unchanged Length/RD/VE-ID/VE-Block-Offset/VE-Block-Size while swapping labels is a VPLS ASBR duty (Inter-AS option b); ze is not a VPLS PE or ASBR (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), and performs no VPLS ASBR label-swap re-advertisement | | `RFC4761-3.5-1` | When receiving equivalent NLRIs from multiple PEs, MUST pick only one via BGP path selection (Section 3.5) | MUST | 3.5 - Multi-homing and Path Selection | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** selecting exactly one among equivalent VPLS NLRIs (same RD, VE ID, VE Block Offset) for pseudowire setup is a VPLS-PE control duty; ze is not a VPLS PE: received L2VPN routes are stored as opaque raw NLRI blobs (internal/component/bgp/plugins/adj_rib_in/rib.go:850-851), with no VPLS-specific equivalence or best-path over the (RD, VE ID, VBO) tuple, and no VSI to install a chosen pseudowire | | `RFC4761-3.5-2` | If two PEs are assigned the same VE ID in a given VPLS, they MUST use the same Route Distinguisher (Section 3.5) | MUST | 3.5 - Multi-homing and Path Selection | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the same-VE-ID-implies-same-RD rule binds VPLS-PE provisioning; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI), provisions no VPLS VE identities, and holds no VE-ID-to-RD assignment to constrain | | `RFC4761-4.2.1-1` | When a VE learns a source MAC address S on port P and later sees S on a different port P', the VE MUST update its FIB to reflect the new port (Section 4.2.1) | MUST | 4.2.1 - MAC Address Learning | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** updating the FIB when a source MAC moves to a new port is VPLS-PE data-plane bridging; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no MAC FIB), so it performs no MAC learning or L2 forwarding | | `RFC4761-4.2.2-1` | If the age of source MAC S exceeds aging time T, S MUST be flushed from the FIB (Section 4.2.2) | MUST | 4.2.2 - Aging | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** flushing an aged-out source MAC from the FIB is VPLS-PE data-plane bridging; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45), and maintains no MAC FIB to age | | `RFC4761-4.2.5-1` | For split horizon: PE MUST NOT send frames received from another PE to other PEs (Section 4.2.5) | MUST NOT | 4.2.5 - 'Split Horizon' Forwarding | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** split-horizon flooding (never sending a PE-sourced frame back to other PEs) is VPLS-PE data-plane forwarding; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VPLS forwarding data path), so it floods no VPLS frames | | `RFC4761-6-1` | Any implementation using MPLS-in-IP/GRE tunnels for VPLS MUST contain an IPsec implementation (Section 6) | MUST | 6 - Security Considerations | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it tunnels no VPLS packets over MPLS-in-IP/GRE and the must-contain-IPsec trigger never fires (the IKE/IPsec engine in internal/component/ike is unrelated to VPLS tunneling) | | `RFC4761-6-2` | If not using IPsec, implementation MUST allow egress PE to validate the IP source address of any tunneled packet (Section 6) | MUST | 6 - Security Considerations | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** validating the IP source address of a tunneled VPLS packet is an egress-PE data-plane check; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VPLS forwarding data path), so there is no MPLS-in-IP/GRE VPLS data path at an egress PE to validate a packet's source | | `RFC4761-3.3-2` | When all CE links go down, PE SHOULD either withdraw all NLRIs or signal unavailability (Section 3.3) | SHOULD | 3.3 - BGP VPLS Operation | **positive:** no positive test. **negative:** no negative test | | `RFC4761-3.5-3` | Multi-homed PEs with same VE ID SHOULD announce the same VE Block Size for a given VE Offset (Section 3.5) | SHOULD | 3.5 - Multi-homing and Path Selection | **positive:** no positive test. **negative:** no negative test | | `RFC4761-4.2.2-2` | VPLS PEs SHOULD have an aging mechanism to remove MAC addresses (Section 4.2.2) | SHOULD | 4.2.2 - Aging | **positive:** no positive test. **negative:** no negative test | | `RFC4761-4.2.2-3` | An implementation SHOULD provide a configurable knob to set the aging time T on a per-VPLS basis (Section 4.2.2) | SHOULD | 4.2.2 - Aging | **positive:** no positive test. **negative:** no negative test | | `RFC4761-4.2.7-1` | An implementation SHOULD allow the 802.1p to EXP mapping function to be different for each VPLS (Section 4.2.7) | SHOULD | 4.2.7 - Class of Service | **positive:** no positive test. **negative:** no negative test | | `RFC4761-4.2.1-2` | A VE MAY implement a mechanism to damp flapping of source ports for a given MAC address (Section 4.2.1) | MAY | 4.2.1 - MAC Address Learning | **positive:** no positive test. **negative:** no negative test | | `RFC4761-4.2.2-4` | An implementation MAY accelerate aging of all MAC addresses on topology change (Section 4.2.2) | MAY | 4.2.2 - Aging | **positive:** no positive test. **negative:** no negative test | | `RFC4761-4.2.4-1` | A VE MAY use techniques to restrict multicast frame transmission to a smaller set of receivers (Section 4.2.4) | MAY | 4.2.4 - Broadcast and Multicast | **positive:** no positive test. **negative:** no negative test | | `RFC4761-4.2.7-2` | An implementation MAY choose to map 802.1p bits to EXP bits for Class of Service (Section 4.2.7) | MAY | 4.2.7 - Class of Service | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4761-2.3-1`](#rfc4761-2.3-1) An implementation MUST maintain a separate routing storage for each service (Section 2.3) | no test | no test carries this requirement id; annotated {not-applicable}: maintaining a separate routing/forwarding store per VPLS service is a VPLS-PE VSI duty; ze is not a VPLS PE: its RFC 4761 support is a control-plane L2VPN/VPLS (AFI 25, SAFI 65) NLRI codec (internal/component/bgp/plugins/nlri/vpls/vpls.go:45 Mode decode; types.go:82 ParseVPLS, types.go:163 WriteTo) plus route-injection encoders, with no VPLS instance (VSI), no pseudowire signaling, no MAC FIB, and no VPLS forwarding data path, so there is no per-service VSI store to maintain | | [`RFC4761-3.2.4-1`](#rfc4761-3.2.4-1) MBZ bits in Control Flags MUST be set to zero when sending (Section 3.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: signalling the Layer2 Info Extended Community (0x800A) Control Flags is a duty of a VPLS PE originating auto-discovery; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no PW signaling), and the only l2info code is an operator-driven raw ext-community encoder that packs the supplied control byte verbatim (internal/component/bgp/config/routeattr_community.go:283-305 parseL2InfoExtCommunity), not a PE Control-Flags sender, so ze originates no VPLS Control Flags whose MBZ bits it must zero | | [`RFC4761-3.2.4-2`](#rfc4761-3.2.4-2) MBZ bits in Control Flags MUST be ignored when receiving (Section 3.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: ignoring MBZ bits on receipt of the Layer2 Info Control Flags is a VPLS-PE receive duty; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no PW state) and has no Layer2 Info decoder, so no MBZ-on-receive processing exists to exercise | | [`RFC4761-3.2.4-3`](#rfc4761-3.2.4-3) When C flag is 1, a Control Word MUST be present when sending VPLS packets to that PE (Section 3.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: inserting a Control Word into VPLS packets when the C flag is 1 is a pseudowire data-plane encapsulation choice; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no PW signaling, no VPLS forwarding data path), so it sends no VPLS packets and the C-flag data-plane obligation has no host | | [`RFC4761-3.2.4-4`](#rfc4761-3.2.4-4) When C flag is 0, a Control Word MUST NOT be present when sending VPLS packets to that PE (Section 3.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: omitting the Control Word from VPLS packets when the C flag is 0 is a pseudowire data-plane choice; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it sends no VPLS packets to constrain | | [`RFC4761-3.2.4-5`](#rfc4761-3.2.4-5) When S flag is 1, sequenced delivery of frames MUST be used when sending VPLS packets to that PE (Section 3.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: using sequenced delivery for VPLS packets when the S flag is 1 is a pseudowire data-plane behavior; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it delivers no VPLS packets | | [`RFC4761-3.2.4-6`](#rfc4761-3.2.4-6) When S flag is 0, sequenced delivery MUST NOT be used when sending VPLS packets to that PE (Section 3.2.4) | no test | no test carries this requirement id; annotated {not-applicable}: refraining from sequenced delivery when the S flag is 0 is a pseudowire data-plane behavior; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it delivers no VPLS packets | | [`RFC4761-3.2.3-1`](#rfc4761-3.2.3-1) If announcing PE's VE ID is not covered by any remote VE set that PE-b announced, PE-b MUST make a new announcement covering it (Section 3.2.3) | no test | no test carries this requirement id; annotated {not-applicable}: computing and announcing a new label block to cover a peer's VE ID is VPLS-PE auto-discovery signaling; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI), and runs no label-block allocation or pseudowire-setup algorithm | | [`RFC4761-3.2.3-2`](#rfc4761-3.2.3-2) If Y withdraws an NLRI for V that X was using, X MUST tear down its ends of the pseudowire between X and Y (Section 3.2.3) | no test | no test carries this requirement id; annotated {not-applicable}: tearing down pseudowire ends when a peer withdraws its NLRI is a VPLS-PE data-plane action; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it establishes and tears down no pseudowires | | [`RFC4761-3.3-1`](#rfc4761-3.3-1) PE-a MUST withdraw all its announcements for VPLS foo that contain VE ID V when V is removed from configuration (Section 3.3) | no test | no test carries this requirement id; annotated {not-applicable}: withdrawing a PE's own VPLS announcements when a VE ID leaves the VSI configuration is a VPLS-PE lifecycle duty; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45), and has no VSI/VE configuration model that originates or withdraws such announcements | | [`RFC4761-3.4.2-1`](#rfc4761-3.4.2-1) Inter-AS Method (b): Length, Route Distinguisher, VE ID, VE Block Offset, and VE Block Size MUST be the same when ASBR re-advertises (Section 3.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: re-advertising a VPLS NLRI with unchanged Length/RD/VE-ID/VE-Block-Offset/VE-Block-Size while swapping labels is a VPLS ASBR duty (Inter-AS option b); ze is not a VPLS PE or ASBR (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), and performs no VPLS ASBR label-swap re-advertisement | | [`RFC4761-3.5-1`](#rfc4761-3.5-1) When receiving equivalent NLRIs from multiple PEs, MUST pick only one via BGP path selection (Section 3.5) | no test | no test carries this requirement id; annotated {not-applicable}: selecting exactly one among equivalent VPLS NLRIs (same RD, VE ID, VE Block Offset) for pseudowire setup is a VPLS-PE control duty; ze is not a VPLS PE: received L2VPN routes are stored as opaque raw NLRI blobs (internal/component/bgp/plugins/adj_rib_in/rib.go:850-851), with no VPLS-specific equivalence or best-path over the (RD, VE ID, VBO) tuple, and no VSI to install a chosen pseudowire | | [`RFC4761-3.5-2`](#rfc4761-3.5-2) If two PEs are assigned the same VE ID in a given VPLS, they MUST use the same Route Distinguisher (Section 3.5) | no test | no test carries this requirement id; annotated {not-applicable}: the same-VE-ID-implies-same-RD rule binds VPLS-PE provisioning; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI), provisions no VPLS VE identities, and holds no VE-ID-to-RD assignment to constrain | | [`RFC4761-4.2.1-1`](#rfc4761-4.2.1-1) When a VE learns a source MAC address S on port P and later sees S on a different port P', the VE MUST update its FIB to reflect the new port (Section 4.2.1) | no test | no test carries this requirement id; annotated {not-applicable}: updating the FIB when a source MAC moves to a new port is VPLS-PE data-plane bridging; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no MAC FIB), so it performs no MAC learning or L2 forwarding | | [`RFC4761-4.2.2-1`](#rfc4761-4.2.2-1) If the age of source MAC S exceeds aging time T, S MUST be flushed from the FIB (Section 4.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: flushing an aged-out source MAC from the FIB is VPLS-PE data-plane bridging; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45), and maintains no MAC FIB to age | | [`RFC4761-4.2.5-1`](#rfc4761-4.2.5-1) For split horizon: PE MUST NOT send frames received from another PE to other PEs (Section 4.2.5) | no test | no test carries this requirement id; annotated {not-applicable}: split-horizon flooding (never sending a PE-sourced frame back to other PEs) is VPLS-PE data-plane forwarding; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VPLS forwarding data path), so it floods no VPLS frames | | [`RFC4761-6-1`](#rfc4761-6-1) Any implementation using MPLS-in-IP/GRE tunnels for VPLS MUST contain an IPsec implementation (Section 6) | no test | no test carries this requirement id; annotated {not-applicable}: ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VSI, no VPLS forwarding data path), so it tunnels no VPLS packets over MPLS-in-IP/GRE and the must-contain-IPsec trigger never fires (the IKE/IPsec engine in internal/component/ike is unrelated to VPLS tunneling) | | [`RFC4761-6-2`](#rfc4761-6-2) If not using IPsec, implementation MUST allow egress PE to validate the IP source address of any tunneled packet (Section 6) | no test | no test carries this requirement id; annotated {not-applicable}: validating the IP source address of a tunneled VPLS packet is an egress-PE data-plane check; ze is not a VPLS PE (control-plane VPLS NLRI codec at internal/component/bgp/plugins/nlri/vpls/vpls.go:45, no VPLS forwarding data path), so there is no MPLS-in-IP/GRE VPLS data path at an egress PE to validate a packet's source | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4761-2.3-1`](#rfc4761-2.3-1) An implementation MUST maintain a separate routing storage for each service (Section 2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-2.3-1, so no unit is bound to it. ### [`RFC4761-3.2.4-1`](#rfc4761-3.2.4-1) MBZ bits in Control Flags MUST be set to zero when sending (Section 3.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.2.4-1, so no unit is bound to it. ### [`RFC4761-3.2.4-2`](#rfc4761-3.2.4-2) MBZ bits in Control Flags MUST be ignored when receiving (Section 3.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.2.4-2, so no unit is bound to it. ### [`RFC4761-3.2.4-3`](#rfc4761-3.2.4-3) When C flag is 1, a Control Word MUST be present when sending VPLS packets to that PE (Section 3.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.2.4-3, so no unit is bound to it. ### [`RFC4761-3.2.4-4`](#rfc4761-3.2.4-4) When C flag is 0, a Control Word MUST NOT be present when sending VPLS packets to that PE (Section 3.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.2.4-4, so no unit is bound to it. ### [`RFC4761-3.2.4-5`](#rfc4761-3.2.4-5) When S flag is 1, sequenced delivery of frames MUST be used when sending VPLS packets to that PE (Section 3.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.2.4-5, so no unit is bound to it. ### [`RFC4761-3.2.4-6`](#rfc4761-3.2.4-6) When S flag is 0, sequenced delivery MUST NOT be used when sending VPLS packets to that PE (Section 3.2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.2.4-6, so no unit is bound to it. ### [`RFC4761-3.2.3-1`](#rfc4761-3.2.3-1) If announcing PE's VE ID is not covered by any remote VE set that PE-b announced, PE-b MUST make a new announcement covering it (Section 3.2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.2.3-1, so no unit is bound to it. ### [`RFC4761-3.2.3-2`](#rfc4761-3.2.3-2) If Y withdraws an NLRI for V that X was using, X MUST tear down its ends of the pseudowire between X and Y (Section 3.2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.2.3-2, so no unit is bound to it. ### [`RFC4761-3.3-1`](#rfc4761-3.3-1) PE-a MUST withdraw all its announcements for VPLS foo that contain VE ID V when V is removed from configuration (Section 3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.3-1, so no unit is bound to it. ### [`RFC4761-3.4.2-1`](#rfc4761-3.4.2-1) Inter-AS Method (b): Length, Route Distinguisher, VE ID, VE Block Offset, and VE Block Size MUST be the same when ASBR re-advertises (Section 3.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.4.2-1, so no unit is bound to it. ### [`RFC4761-3.5-1`](#rfc4761-3.5-1) When receiving equivalent NLRIs from multiple PEs, MUST pick only one via BGP path selection (Section 3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.5-1, so no unit is bound to it. ### [`RFC4761-3.5-2`](#rfc4761-3.5-2) If two PEs are assigned the same VE ID in a given VPLS, they MUST use the same Route Distinguisher (Section 3.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-3.5-2, so no unit is bound to it. ### [`RFC4761-4.2.1-1`](#rfc4761-4.2.1-1) When a VE learns a source MAC address S on port P and later sees S on a different port P', the VE MUST update its FIB to reflect the new port (Section 4.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-4.2.1-1, so no unit is bound to it. ### [`RFC4761-4.2.2-1`](#rfc4761-4.2.2-1) If the age of source MAC S exceeds aging time T, S MUST be flushed from the FIB (Section 4.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-4.2.2-1, so no unit is bound to it. ### [`RFC4761-4.2.5-1`](#rfc4761-4.2.5-1) For split horizon: PE MUST NOT send frames received from another PE to other PEs (Section 4.2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-4.2.5-1, so no unit is bound to it. ### [`RFC4761-6-1`](#rfc4761-6-1) Any implementation using MPLS-in-IP/GRE tunnels for VPLS MUST contain an IPsec implementation (Section 6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-6-1, so no unit is bound to it. ### [`RFC4761-6-2`](#rfc4761-6-2) If not using IPsec, implementation MUST allow egress PE to validate the IP source address of any tunneled packet (Section 6) Audit verdict: not audited: no reader has judged these tests No test carries RFC4761-6-2, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6, rfc4761 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc4761.txt | | Source fingerprint | 26c76afd3dd9f253 | | Record | rfc/extraction/rfc4761.json | | Mapped sentences | 16 | | Declined as scope | 26 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 1 | walked | Title block, Status of This Memo, Copyright Notice, IESG Note, Abstract and Table of Contents. Walked rather than skipped because the site scan attributes one site here, the Abstract's statement of what the document describes, excluded below. The IESG Note records that RFC 4762 and this document both perform VPLS in different, incompatible manners. | | `1` | Introduction | 0 | walked | Introduction. Indicative prose: what a VPLS is, that it glues individual LANs across a packet switched network by MAC learning, flooding and forwarding over pseudowires, what this document adds (auto-discovery and signaling over BGP, and transport of VPLS frames over tunnels), and which alternatives exist, RFC 4762's LDP-signaled VPLS among them. No directive and no site. | | `1.1` | Scope of This Document | 1 | walked | Scope of This Document. A roadmap: the functional model in section 2, the BGP control plane in section 3, the forwarding plane in section 4, the deployment options in section 5. Its one site is the roadmap sentence pointing at section 4 and is excluded below. | | `1.2` | Conventions Used in This Document | 0 | walked | Conventions Used in This Document. The RFC 2119 key-words paragraph. It tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. | | `2` | Functional Model | 0 | walked | Functional Model. One sentence introducing Figure 1, the example VPLS with CEs, PEs, a u-PE and five customer sites. No directive and no site. | | `2.1` | Terminology | 0 | walked | Terminology. Defines SP, P, PE, CE, u-PE, VE and demultiplexor, records that PE and u-PE devices are VPLS-aware while a CE is not, and that the demultiplexor in this document is an MPLS label. Definitions, not directives, and no site. | | `2.2` | Assumptions | 0 | walked | Assumptions. States the assumptions the rest of the document rests on: a packet switched SP network, PEs logically fully meshed with tunnels established outside this document, flooding and learning private to each SP device, and a bidirectional pseudowire between every pair of PEs in a VPLS. Indicative throughout and no site. | | `2.3` | Interactions | 1 | walked | Interactions. States what makes VPLS a LAN service, private and virtual, that PE interactions are control-driven rather than data-driven, and that a PE can run VPLS and IP VPNs at once because the two use different AFI/SAFI pairs. Its one capitalised MUST-level site is the separate-routing-storage obligation that follows from that, mapped below to RFC4761-2.3-1. | | `3` | Control Plane | 0 | walked | Control Plane. One paragraph naming the two control-plane functions, auto-discovery and signaling, and pointing at sections 3.1 to 3.5. No directive and no site. | | `3.1` | Auto-Discovery | 2 | walked | Auto-Discovery. Contrasts configuring every PE by hand with discovering them by protocol. Both sites belong to the description of the manual alternative and are excluded below; the paragraph on auto-discovery that follows states what BGP buys and directs nobody. | | `3.1.1` | Functions | 3 | walked | Functions. States what the discovery function has to provide: a PE must be able to tell the others it is a member of a VPLS, to declare that it no longer participates, and so to have a means of identifying a VPLS and of communicating with all other PEs. All three sites bind the VPLS PE performing auto-discovery and are excluded below. | | `3.1.2` | Protocol Specification | 0 | walked | Protocol Specification. States that the mechanism uses the Route Target extended community of RFC 4360 to identify VPLS members with RFC 4364's semantics, that one RT suffices for a fully meshed VPLS, and how a PE announces membership by annotating its NLRIs with that RT and withdraws it by withdrawing them. Indicative throughout and no site. | | `3.2` | Signaling | 1 | walked | Signaling. States what signaling is (each pair of PEs establishing and tearing down pseudowires, that is exchanging and withdrawing demultiplexors), what a demultiplexor carries, and why one common Update carrying demultiplexors for every remote PE beats N individual messages. Its one site is the pseudowire-establishment sentence, which binds the VPLS PE and is excluded below. | | `3.2.1` | Label Blocks | 0 | walked | Label Blocks. Defines a label block as the contiguous set {LB, ..., LB+VBS-1}, extends it with the VE block offset so a block is {LB+VBO, ..., LB+VBO+VBS-1}, and explains how each receiving PE infers its own demultiplexor by adding its VE ID to the label base. Definitions, not directives, and no site. | | `3.2.2` | VPLS BGP NLRI | 1 | walked | VPLS BGP NLRI. The document's wire-format section: AFI 25, SAFI 65, and the NLRI's Length, Route Distinguisher, VE ID, VE Block Offset, VE Block Size and 3-octet Label Base, with the one-to-one correspondence between the remote VE set and the label block. The layout is carried by the Wire Formats tables of rfc/short/rfc4761.md, and it is the part of this RFC ze does implement: internal/component/bgp/plugins/nlri/vpls.ParseVPLS decodes exactly these fields and EncodeRoute builds them. Its one site is the VE ID provisioning sentence, which binds the VPLS PE and is excluded below. | | `3.2.3` | PW Setup and Teardown | 2 | walked | PW Setup and Teardown. The four-step procedure PE-b runs on an announcement from PE-a, and the teardown rule when an NLRI in use is withdrawn. Its two capitalised MUST-level sites are mapped below to RFC4761-3.2.3-1 and RFC4761-3.2.3-2; steps 1, 2 and 4 are stated in the indicative and the site scan sees nothing in them. | | `3.2.4` | Signaling PE Capabilities | 4 | walked | Signaling PE Capabilities. Defines the Layer2 Info Extended Community (0x800A) carrying Encaps Type, Control Flags and Layer-2 MTU, with Encaps Type 19 for VPLS, and the C and S Control Flags. Three of its four sites carry the section's obligations and are mapped below to RFC4761-3.2.4-1, -3 and -5. The other half of each of those three sentences is a second declared row, listed here as an unsourced id, because the splitter keeps one sentence as one site: RFC4761-3.2.4-2 is 'MUST be ignored when receiving this community', RFC4761-3.2.4-4 is the C=0 'MUST NOT be present' half, and RFC4761-3.2.4-6 is the S=0 'MUST NOT be used' half. Site 3.2.4:1 is the Figure 4 bit vector and its legend and is excluded below. | | `3.3` | BGP VPLS Operation | 4 | walked | BGP VPLS Operation. Walks the whole life of a BGP VPLS announcement: the administrator picks an RT, the PE is configured with a VE ID and possibly an RD, it generates a label block and a remote VE set, builds the NLRI, attaches the Layer2 Info community and the RT, sets itself as Next Hop and announces. Site 3.3:4 carries the withdrawal obligation and is mapped below to RFC4761-3.3-1; sites 3.3:1, 3.3:2 and 3.3:3 are excluded. The SHOULD listed as an unsourced id is the CE-links-down rule: 'If all of PE-a's links to its CEs in VPLS foo go down, then PE-a SHOULD either withdraw all its NLRIs for VPLS foo or let other PEs in the VPLS foo know in some way that PE-a is no longer connected to its CEs.' | | `3.4` | Multi-AS VPLS | 0 | walked | Multi-AS VPLS. States the problem when the sites of a VPLS attach to PEs in different ASes, introduces Figure 5 and the three methods of sections 3.4.1 to 3.4.3 in order of increasing scalability. No directive and no site. | | `3.4.1` | Method (a): VPLS-to-VPLS Connections at the ASBRs | 1 | walked | Method (a): VPLS-to-VPLS Connections at the ASBRs. Each ASBR acts as a PE for the VPLSs spanning the two ASes and views the other ASBR as a CE, which requires Ethernet on the interconnect and full PE operation on the ASBR. Its one site is the loop-prevention rule for redundant inter-AS connections and is excluded below. | | `3.4.2` | not stated | 3 | walked | Method (b): EBGP Redistribution of VPLS Information between ASBRs. The ASBRs re-advertise the VPLS NLRI with new labels and themselves as next hop, and swap labels in the forwarding path. Site 3.4.2:1 carries the obligation that everything but the Label Base stay identical and is mapped below to RFC4761-3.4.2-1; sites 3.4.2:2 and 3.4.2:3 are the ASBR forwarding-path installations and are excluded. | | `3.4.3` | not stated | 0 | walked | Method (c): Multi-Hop EBGP Redistribution of VPLS Information between ASes. A multi-hop E-BGP peering between the PEs or their route reflectors, with a tunnel LSP from PE1 to PE2 and no VPLS state on the ASBRs. Indicative throughout and no site. | | `3.4.4` | Allocation of VE IDs across Multiple ASes | 0 | walked | Allocation of VE IDs across Multiple ASes. Suggests allocating a VE ID range per AS to keep VE IDs unique while minimising the number of NLRIs, with no overlap between ranges except for multi-homing. Advisory prose with no site. | | `3.5` | Multi-homing and Path Selection | 2 | walked | Multi-homing and Path Selection. When the PEs attached to one site carry the same VE ID, BGP path selection builds the loop-free topology, and two VPLS NLRIs are equivalent when their Route Distinguisher, VE ID and VE Block Offset match. Its two capitalised MUST-level sites are mapped below to RFC4761-3.5-1 and RFC4761-3.5-2. The unsourced id is the SHOULD that closes the second of those sentences, 'they SHOULD announce the same VE Block Size for a given VE Offset', which the splitter keeps in site 3.5:2. | | `3.6` | Hierarchical BGP VPLS | 1 | walked | Hierarchical BGP VPLS. How BGP route reflectors scale the VPLS control plane, that RRs introduce no data plane state, that no MAC addresses are exchanged over BGP, and when BGP processing for VPLS happens. Its one site states that something is NOT required and is excluded below. | | `4` | Data Plane | 0 | walked | Data Plane. One sentence naming the two aspects sections 4.1 and 4.2 cover, encapsulation and forwarding. No directive and no site. | | `4.1` | Encapsulation | 0 | walked | Encapsulation. One sentence: Ethernet frames from CE devices are encapsulated for transmission over the packet switched network as in RFC 4448. No obligation of its own and no site. | | `4.2` | Forwarding | 0 | walked | Forwarding. States that a VPLS packet is classified into a service instance by the interface it arrived on, and forwarded within that instance on the destination MAC address. Indicative and no site. | | `4.2.1` | MAC Address Learning | 2 | walked | MAC Address Learning. The SP network appears as one logical learning bridge per VPLS, learning associates a source MAC with the logical port it arrived on, and that association is the FIB. Site 4.2.1:2 carries the MAC-move obligation and is mapped below to RFC4761-4.2.1-1; site 4.2.1:1 is the learning-bridge analogy and is excluded. The unsourced id is the MAY that closes the section, 'A VE MAY implement a mechanism to damp flapping of source ports for a given MAC address.' | | `4.2.2` | Aging | 2 | walked | Aging. Site 4.2.2:2 carries the capitalised MUST, mapped below to RFC4761-4.2.2-1, and site 4.2.2:1 is the rationale sentence inside the section's opening SHOULD and is excluded. Three declared rows are read from prose here and are the unsourced ids: RFC4761-4.2.2-2 is 'VPLS PEs SHOULD have an aging mechanism to remove a MAC address associated with a logical port', RFC4761-4.2.2-3 is 'An implementation SHOULD provide a configurable knob to set the aging time T on a per-VPLS basis', and RFC4761-4.2.2-4 is the MAY to accelerate aging of every MAC address in a VPLS on a Spanning Tree topology change. | | `4.2.3` | Flooding | 0 | walked | Flooding. Describes flooding a frame whose destination is not in the FIB to every other VE in the VPLS, and works the Figure 1 example through PE2, PE1 and a u-PE, including a PE that announces whether it is capable of flooding. Indicative throughout and no site. | | `4.2.4` | Broadcast and Multicast | 1 | walked | Broadcast and Multicast. Its one site is the broadcast-delivery rule and is excluded below. The unsourced id is the MAY that follows, 'a VE MAY also use certain techniques to restrict transmission of multicast frames to a smaller set of receivers', whose discussion the section puts out of scope. | | `4.2.5` | 'Split Horizon' Forwarding | 4 | walked | 'Split Horizon' Forwarding. Four sites: three describe what a flooding PE sends where, and the fourth is the capitalised split-horizon prohibition mapped below to RFC4761-4.2.5-1. The other three bind the VPLS PE forwarding path and are excluded. The closing sentence extends the rule to broadcast and multicast packets as well as unknown MAC addresses. | | `4.2.6` | Qualified and Unqualified Learning | 0 | walked | Qualified and Unqualified Learning. Defines the two learning keys, the MAC address alone or the customer VLAN tag with it, and weighs one global broadcast domain against one per VLAN. Definitions and trade-offs, no directive and no site. | | `4.2.7` | Class of Service | 1 | walked | Class of Service. Its one site is the SHOULD that the mapping function be per-VPLS, mapped below to RFC4761-4.2.7-1. The unsourced id is the MAY that opens the section, 'an implementation MAY choose to map 802.1p bits in a customer Ethernet frame with a VLAN tag to an appropriate setting of EXP bits in the pseudowire and/or tunnel label'. | | `5` | Deployment Options | 1 | walked | Deployment Options. Defines 'decoupled' operation: the VE closest to the customer can be the PE itself or a u-PE that does the Layer 2 functions and a limited set of Layer 3 functions, and PEs of both kinds mix in one network because discovery and signaling are unchanged. Its one site is the SP's deployment decision and is excluded below. | | `6` | Security Considerations | 3 | walked | Security Considerations. States that VPLS offers no confidentiality, integrity or authentication, that both the control plane and the forwarding path have to be protected, that RFC 2385 authenticates the BGP exchanges, and that VPLS labels must be accepted only from valid interfaces. It closes on MPLS-in-IP and MPLS-in-GRE tunneling per RFC 4023: sites 6:2 and 6:3 carry the two capitalised MUST-level obligations and are mapped below to RFC4761-6-1 and RFC4761-6-2, and site 6:1 points at RFC 4023's own security considerations and is excluded. | | `7` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records the allocated AFI 25 for L2VPN information and the allocated extended community value 0x800A for the Layer2 Info Extended Community. Binds IANA, not a speaker. | | `8` | References | 0 | skipped (references) | References. The heading for sections 8.1 and 8.2. | | `8.1` | not stated | 0 | skipped (references) | Normative References: RFC 2119, RFC 2385, RFC 4023, RFC 4760, RFC 4360, RFC 4364 and RFC 4448. | | `8.2` | not stated | 0 | skipped (references) | Informative References: RFC 4456, RFC 4664, RFC 4762, the VR-based Layer-3 VPN and Constrained VPN Route Distribution drafts, RFC 4447, the Layer 2 VPNs Over Tunnels draft and IEEE 802.1D. | | `A` | Appendix A, Contributors | 0 | skipped (acknowledgements) | Appendix A, Contributors. The list of people who contributed to the document. | | `B` | Appendix B, Acknowledgements | 1 | walked | Appendix B, Acknowledgements. Because no numbered heading follows it, the derived span also carries the Editors' Addresses, the Full Copyright Statement, the Intellectual Property boilerplate and the RFC Editor funding note. Walked rather than skipped because the prose scan attributes one site here; that site is IPR boilerplate and is excluded below. Nothing in the span states an obligation on an implementation. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `front:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the sentence is the Abstract's statement of the document's CONTENTS, 'This document describes the functions required to offer VPLS, a mechanism for signaling a VPLS, and rules for forwarding VPLS frames across a packet switched network.' The case-insensitive prose scan sees 'required' inside the noun phrase 'the functions required to offer VPLS', which names what the document describes rather than directing a speaker. | This document describes the functions required to offer VPLS, a mechanism for signaling a VPLS, and rules for forwarding VPLS frames across a packet switched network. | | `1.1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: a roadmap sentence in Scope of This Document. Its subject is where the material is written down -- 'The forwarding plane and the actions that a participating Provider Edge (PE) router offering the VPLS service must take is described in Section 4' -- so the lowercase 'must' sits in a relative clause naming what section 4 covers. The obligations themselves are in section 4 and are classified there. | The forwarding plane and the actions that a participating Provider Edge (PE) router offering the VPLS service must take is described in Section 4. | | `3.1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: a description of the OTHER approach, the one this document replaces. The paragraph characterises configuring each PE by hand ('The former approach is fairly configuration-intensive'), and the lowercase 'is required' states why that approach costs what it does, namely that the PEs of a VPLS are fully meshed with pseudowires. It directs no speaker, and the auto-discovery paragraph that follows is the mechanism this document specifies. | The former approach is fairly configuration-intensive, especially since it is required that the PEs participating in a given VPLS are fully meshed (i.e., that every PE in a given VPLS establish pseudowires to every other PE in that VPLS). | | `3.1:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the second half of the same characterisation of the manual approach. It states a CONSEQUENCE of not auto-discovering, that a topology change forces the VPLS configuration on every PE to change, and the paragraph that follows says auto-discovery removes it. It directs no speaker. | Furthermore, when the topology of a VPLS changes (i.e., a PE is added to, or removed from, the VPLS), the VPLS configuration on all PEs in that VPLS must be changed. | | `3.1.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the VPLS PE performing auto-discovery: the sentence says a PE participating in VPLS instance V must be able to tell every other PE in V that it is a member. ze is not a VPLS PE: its RFC 4761 surface is the control-plane L2VPN/VPLS (AFI 25, SAFI 65) NLRI codec of internal/component/bgp/plugins/nlri/vpls (ParseVPLS decodes the section 3.2.2 NLRI, EncodeRoute and EncodeNLRIHex build one from operator-supplied fields, and the plugin registers the family in decode mode) plus the operator-driven Layer2 Info extended community encoder internal/component/bgp/config.parseL2InfoExtCommunity. There is no VSI, no pseudowire signaling, no MAC FIB and no VPLS forwarding path, and a received L2VPN route is stored as an opaque raw NLRI blob. It joins no VPLS and announces no membership. | A PE that participates in a given VPLS instance V must be able to tell all other PEs in VPLS V that it is also a member of V. | | `3.1.1:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same VPLS PE role: it must have a means of declaring that it no longer participates in a VPLS. Ze participates in no VPLS to leave. It decodes and encodes the VPLS NLRI (internal/component/bgp/plugins/nlri/vpls.ParseVPLS and EncodeRoute) and holds no VSI whose membership it could withdraw. | A PE must also have a means of declaring that it no longer participates in a VPLS. | | `3.1.1:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same VPLS PE role, stating what the two preceding sentences need: a means of identifying a VPLS and a means of communicating with every other PE. Ze plays no VPLS PE role, so it needs neither. The producer that would act as it if ze did is the whole of ze's RFC 4761 code, the VPLS NLRI codec `internal/component/bgp/plugins/nlri/vpls/vpls.go`, which codes the SAFI 65 route and nothing else: the tree holds no VPLS forwarding path, no MAC learning and no flooding for the role to inhabit. | To do both of these, the PE must have a means of identifying a VPLS and a means by which to communicate to all other PEs. | | `3.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the VPLS PE performing signaling: each PAIR of PEs in a VPLS must be able to establish and tear down pseudowires to each other, exchanging and withdrawing demultiplexors. ze is not a VPLS PE: its RFC 4761 surface is the control-plane L2VPN/VPLS (AFI 25, SAFI 65) NLRI codec of internal/component/bgp/plugins/nlri/vpls (ParseVPLS decodes the section 3.2.2 NLRI, EncodeRoute and EncodeNLRIHex build one from operator-supplied fields, and the plugin registers the family in decode mode) plus the operator-driven Layer2 Info extended community encoder internal/component/bgp/config.parseL2InfoExtCommunity. There is no VSI, no pseudowire signaling, no MAC FIB and no VPLS forwarding path, and a received L2VPN route is stored as an opaque raw NLRI blob. It signals no pseudowire and holds no demultiplexor state. | Once discovery is done, each pair of PEs in a VPLS must be able to establish (and tear down) pseudowires to each other, i.e., exchange (and withdraw) demultiplexors. | | `3.2.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds VPLS PE provisioning: a PE participating in a VPLS must have at least one VE ID, one per attached u-PE and possibly one for itself. Ze provisions no VE identity. Its VE ID handling is codec-level only: internal/component/bgp/plugins/nlri/vpls.ParseVPLS reads the 2-octet field off the wire and EncodeRoute writes the value the operator supplied. | A PE participating in a VPLS must have at least one VE ID. | | `3.2.4:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the site is Figure 4 itself, the Control Flags bit vector, and the keyword is in the figure's LEGEND expanding a field name -- '(MBZ = MUST Be Zero)'. The obligation the abbreviation stands for is stated in the sentence site 3.2.4:2 quotes, which is mapped to RFC4761-3.2.4-1. | 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ \| MBZ \|C\|S\| (MBZ = MUST Be Zero) +-+-+-+-+-+-+-+-+ | | `3.3:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the network administrator provisioning the service: to create a VPLS, an administrator must pick a Route Target for it, which every PE serving that VPLS then uses. It is a provisioning act by a person, not behavior of a protocol speaker, and ze offers no VPLS service to provision. The producer that would act as it if ze did is the whole of ze's RFC 4761 code, the VPLS NLRI codec `internal/component/bgp/plugins/nlri/vpls/vpls.go`, which codes the SAFI 65 route and nothing else: the tree holds no VPLS forwarding path, no MAC learning and no flooding for the role to inhabit. | To create a new VPLS, say VPLS foo, a network administrator must pick an RT for VPLS foo, say RT-foo. | | `3.3:2` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 4760, which this document cites as [4] and which the sentence names explicitly: 'this association is required by [4], Section 5'. RFC 4760 section 5 is what binds the Network Layer protocol of the Next Hop address for an <AFI, SAFI> pair, and rfc/short/rfc4760.md carries it. This sentence records the association for <AFI=L2VPN, SAFI=VPLS> and adds no obligation of its own. | The Network Layer protocol associated with the Network Address of the Next Hop for the combination <AFI=L2VPN AFI, SAFI=VPLS SAFI> is IP; this association is required by [4], Section 5. | | `3.3:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the VPLS PE that has discovered a peer: PE-b must set up its part of the VPLS pseudowire between itself and PE-a. ze is not a VPLS PE: its RFC 4761 surface is the control-plane L2VPN/VPLS (AFI 25, SAFI 65) NLRI codec of internal/component/bgp/plugins/nlri/vpls (ParseVPLS decodes the section 3.2.2 NLRI, EncodeRoute and EncodeNLRIHex build one from operator-supplied fields, and the plugin registers the family in decode mode) plus the operator-driven Layer2 Info extended community encoder internal/component/bgp/config.parseL2InfoExtCommunity. There is no VSI, no pseudowire signaling, no MAC FIB and no VPLS forwarding path, and a received L2VPN route is stored as an opaque raw NLRI blob. It sets up no pseudowire. | Similarly, PE-b will have discovered that PE-a is in the same VPLS, and PE-b must set up its part of the VPLS pseudowire. | | `3.4.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the inter-AS VPLS ASBR and the PEs of method (a), which have to run the Spanning Tree Protocol or another loop-detection mechanism per VPLS when several links join the two ASes; the section itself puts how that is achieved out of scope. Ze runs no Spanning Tree Protocol and acts as neither a VPLS PE nor a VPLS ASBR. The producer that would act as it if ze did is the whole of ze's RFC 4761 code, the VPLS NLRI codec `internal/component/bgp/plugins/nlri/vpls/vpls.go`, which codes the SAFI 65 route and nothing else: the tree holds no VPLS forwarding path, no MAC learning and no flooding for the role to inhabit. | In this case, the Spanning Tree Protocol (STP) [15], or some other means of loop detection and prevention, must be run on each VPLS that spans these ASes, so that a loop-free topology can be constructed in each VPLS. | | `3.4.2:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the inter-AS VPLS ASBR of method (b) in its FORWARDING path: ASBR1 installs a swap of each label of its own block for the corresponding label of PE1's block, with the tunnel label pushed. Ze is not a VPLS ASBR and installs no VPLS label swap. The MPLS entries it does program are RSVP-TE and LDP transport labels, through fibkernel.handleMPLSEntry (internal/plugins/fib/kernel/mpls.go), with no VPLS label block behind them. | Furthermore, ASBR1 must also update its forwarding path as follows: if the Label Base sent by PE1 is L1, the Label-block Size is N, the Label Base sent by ASBR1 is L2, and the tunnel label from ASBR1 to PE1 is T, then ASBR1 must install the following in the forwarding path: swap L2 with L1 and push T, | | `3.4.2:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the second inter-AS VPLS ASBR of method (b), which acts as ASBR1 does except that a direct connection removes the need for a tunnel label. Ze plays no VPLS ASBR role. The producer that would act as it if ze did is the whole of ze's RFC 4761 code, the VPLS NLRI codec `internal/component/bgp/plugins/nlri/vpls/vpls.go`, which codes the SAFI 65 route and nothing else: the tree holds no VPLS forwarding path, no MAC learning and no flooding for the role to inhabit. | ASBR2 must act similarly, except that it may not need a tunnel label if it is directly connected with ASBR1. | | `3.6:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: the sentence states that something is NOT required. It records a consequence of route reflection, that no single set of RRs has to handle every BGP message and no single RR every message from a given PE, and the rest of the paragraph gives examples of partitioning RRs by service. It directs nobody. | Another consequence of this approach is that it is not required that one set of RRs handles all BGP messages, or that a particular RR handle all messages from a given PE. | | `4.2.1:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the VPLS PE bridging data path: the SP bridge must learn MAC addresses at its VEs, as a learning bridge learns them on its ports. ze is not a VPLS PE: its RFC 4761 surface is the control-plane L2VPN/VPLS (AFI 25, SAFI 65) NLRI codec of internal/component/bgp/plugins/nlri/vpls (ParseVPLS decodes the section 3.2.2 NLRI, EncodeRoute and EncodeNLRIHex build one from operator-supplied fields, and the plugin registers the family in decode mode) plus the operator-driven Layer2 Info extended community encoder internal/component/bgp/config.parseL2InfoExtCommunity. There is no VSI, no pseudowire signaling, no MAC FIB and no VPLS forwarding path, and a received L2VPN route is stored as an opaque raw NLRI blob. It learns no MAC address and holds no FIB. | Just as a learning bridge learns MAC addresses on its ports, the SP bridge must learn MAC addresses at its VEs. | | `4.2.2:1` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The enclosing construction is a SHOULD: 'VPLS PEs SHOULD have an aging mechanism to remove a MAC address associated with a logical port, much the same as learning bridges do. This is required so that a MAC address can be relearned if it "moves" from a logical port to another logical port ...'. The site is that second sentence, which gives the RATIONALE for the SHOULD rather than stating an obligation of its own, so its 'is required' asserts no level. rfc/short/rfc4761.md declares the enclosing SHOULD as RFC4761-4.2.2-2, listed as an unsourced id on this section. | This is required so that a MAC address can be relearned if it "moves" from a logical port to another logical port, either because the station to which that MAC address belongs really has moved or because of a topology change in the LAN that causes this MAC address to arrive on a new port. | | `4.2.4:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the VPLS PE forwarding path: a frame addressed to the broadcast MAC address must reach all stations in that VPLS, by the same means used for flooding. Ze forwards no VPLS frame and floods nothing; it holds no VSI and no MAC FIB, only the control-plane NLRI codec of internal/component/bgp/plugins/nlri/vpls. | An Ethernet frame whose destination MAC address is the broadcast MAC address must be sent to all stations in that VPLS. | | `4.2.5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the flooding-capable VPLS PE: on a broadcast frame or one with an unknown destination MAC, it must flood. Ze is not a VPLS PE and floods no VPLS frame. The capitalised split-horizon prohibition that closes this section is site 4.2.5:4, mapped below because rfc/short/rfc4761.md declares it. The producer that would act as it if ze did is the whole of ze's RFC 4761 code, the VPLS NLRI codec `internal/component/bgp/plugins/nlri/vpls/vpls.go`, which codes the SAFI 65 route and nothing else: the tree holds no VPLS forwarding path, no MAC learning and no flooding for the role to inhabit. | When a PE capable of flooding (say PEx) receives a broadcast Ethernet frame, or one with an unknown destination MAC address, it must flood the frame. | | `4.2.5:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same flooding-capable VPLS PE for a frame arriving from an attached CE: a copy goes to every other attached CE and to every other PE in the VPLS. Ze has no attached CE, no VPLS peer set and no VPLS forwarding path. The producer that would act as it if ze did is the whole of ze's RFC 4761 code, the VPLS NLRI codec `internal/component/bgp/plugins/nlri/vpls/vpls.go`, which codes the SAFI 65 route and nothing else: the tree holds no VPLS forwarding path, no MAC learning and no flooding for the role to inhabit. | If the frame arrived from an attached CE, PEx must send a copy of the frame to every other attached CE, as well as to all other PEs participating in the VPLS. | | `4.2.5:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the same PE for a frame arriving from another PE: the copy goes only to attached CEs. Ze forwards no VPLS frame from any source. The producer that would act as it if ze did is the whole of ze's RFC 4761 code, the VPLS NLRI codec `internal/component/bgp/plugins/nlri/vpls/vpls.go`, which codes the SAFI 65 route and nothing else: the tree holds no VPLS forwarding path, no MAC learning and no flooding for the role to inhabit. | If, on the other hand, the frame arrived from another PE (say PEy), PEx must send a copy of the packet only to attached CEs. | | `5:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the Service Provider deploying the network: the SP decides which functions the VPLS-aware device closest to the customer supports, that is whether the VE is the PE itself or a u-PE. It is a deployment decision by an operator, and ze deploys no VPLS service. The producer that would act as it if ze did is the whole of ze's RFC 4761 code, the VPLS NLRI codec `internal/component/bgp/plugins/nlri/vpls/vpls.go`, which codes the SAFI 65 route and nothing else: the tree holds no VPLS forwarding path, no MAC learning and no flooding for the role to inhabit. | In deploying a network that supports VPLS, the SP must decide what functions the VPLS-aware device closest to the customer (the VE) supports. | | `6:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 4023, 'Encapsulating MPLS in IP or Generic Routing Encapsulation (GRE)', which this document cites as [3] and which the sentence points at by section: 'the security considerations described in Section 8 of that document must be fully understood'. The sentence directs a reader to another document's analysis and states no requirement of its own. The two obligations this section does state about those tunnels are sites 6:2 and 6:3, mapped below. | If it is desired to use such tunnels to carry VPLS packets, then the security considerations described in Section 8 of that document must be fully understood. | | `B:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Non-normative use: this is the IETF's standard Intellectual Property boilerplate, which the derived section span carries into Appendix B after the Acknowledgements. The sentence invites interested parties to disclose patent rights to the IETF, and its 'may be required to implement this standard' describes what a patent might cover rather than requiring anything of an implementation. | The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. | ## Superseded No document obsoletes RFC 4761, so its obligations are stated where they were written. --- ### Page: RFC 4762 - Virtual Private LAN Service (VPLS) Using Label Distribution Protocol (LDP) Signaling https://ze-software.net/quality/rfc-compliance/rfc4762/ # RFC 4762 - Virtual Private LAN Service (VPLS) Using Label Distribution Protocol (LDP) Signaling Unsupported. Every requirement this repository extracted from RFC 4762, the tests bound to it, and what a reader has verified about them. This summary is not enrolled. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 0 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 0 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 0 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 0 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | | Audit verdicts | 0 | of 0 gated MUSTs judged | 0 weak, wrong or unimplemented, 0 no longer current. Each is named below under its own requirement id | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | MUSTs declared | 0 | of 0 this summary declares | MUST-level requirements this summary DECLARES. The gate holds none of them, because this RFC is not enrolled (blocked), so every share below reads what the summary records rather than what the gate enforces | | Out of scope | 0 | of 0 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 0 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 0 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 0 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | No card above is a share of a population, so there is nothing to add up. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | MUSTs declared | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | ok | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Unsupported | | Enrolment | Not enrolled (blocked) | | Requirements | 0 | | Gated MUST-level | 0 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4762.md` | | Requirement shard | no requirement declared, so no shard is generated | | RFC text | this checkout does not carry the RFC's own text | ## Enrolment Not enrolled (blocked, something outside the summary stops the extraction, and it is named in the reason): VPLS using LDP signaling. Written 2026-09-01 so the public row this RFC has always carried is declared by a summary rather than authored on a page nobody could tie back to a document. It is not enrolled because there is no source text at rfc/full/rfc4762.txt: fetch https://www.rfc-editor.org/rfc/rfc4762.txt, then extract. Ze speaks the BGP-signalled VPLS of RFC 4761 and not this one, so the row claims Unsupported and no obligation here is gated. ## What the public ledger says **Status:** Unsupported **What the ledger says is covered** 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. **What the ledger says remains:** 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. ## Coverage RFC 4762 declares no MUST-level requirement, so the gate counts nothing here. ## Requirements RFC 4762 declares no requirement, so this summary generates no shard. ## Gaps and untested MUSTs RFC 4762 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state RFC 4762 carries no gated, tagged or audited requirement, so there is no proof state to state. ## Extraction sign-off No extraction sign-off exists for RFC 4762, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4762, so its obligations are stated where they were written. --- ### Page: RFC 4862 - IPv6 Stateless Address Autoconfiguration https://ze-software.net/quality/rfc-compliance/rfc4862/ # RFC 4862 - IPv6 Stateless Address Autoconfiguration No row in the public ledger. Every requirement this repository extracted from RFC 4862, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 16 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 16 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 16 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 16 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 16 | of 30 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 16 | of 16 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 16 of 16 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 16 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 16 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 16 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 30 | | Gated MUST-level | 16 | | Not applicable, so out of scope | 16 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc4862.md` | | Requirement shard | `rfc/requirements/rfc4862.md` | | RFC text | `rfc/full/rfc4862.txt` | ## Enrolment Enrolled: IPv6 Stateless Address Autoconfiguration (RFC 4862): all 16 gated MUST/MUST-NOTs not-applicable -- host SLAAC (address formation, RA/PIO, DAD, deprecated/invalid source selection) is kernel addrconf; ze enables it via sysctls and classifies kernel-assigned addresses ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 4862. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 16 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **16** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (16):** [`RFC4862-5.1-1`](#rfc4862-5.1-1), [`RFC4862-5.4-1`](#rfc4862-5.4-1), [`RFC4862-5.4-2`](#rfc4862-5.4-2), [`RFC4862-5.4-5`](#rfc4862-5.4-5), [`RFC4862-5.4.1-1`](#rfc4862-5.4.1-1), [`RFC4862-5.4.2-1`](#rfc4862-5.4.2-1), [`RFC4862-5.4.2-4`](#rfc4862-5.4.2-4), [`RFC4862-5.4.3-1`](#rfc4862-5.4.3-1), [`RFC4862-5.4.5-1`](#rfc4862-5.4.5-1), [`RFC4862-5.5-2`](#rfc4862-5.5-2), [`RFC4862-5.5.3-2`](#rfc4862-5.5.3-2), [`RFC4862-5.5.4-3`](#rfc4862-5.5.4-3), [`RFC4862-5.5.4-5`](#rfc4862-5.5.4-5), [`RFC4862-5.5.4-6`](#rfc4862-5.5.4-6), [`RFC4862-5.5.4-7`](#rfc4862-5.5.4-7), [`RFC4862-5.5.4-8`](#rfc4862-5.5.4-8) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC4862-5.1-1` | A node MUST allow the DupAddrDetectTransmits autoconfiguration variable to be configured by system management for each multicast-capable interface (§5.1) | MUST | 5.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** DAD is run by the kernel, so DupAddrDetectTransmits is the kernel's net.ipv6.conf.<if>.dad_transmits sysctl; ze configures kernel IPv6 conf keys and owns no DAD variable of its own (internal/component/iface/config_sysctl.go:73-79) | | `RFC4862-5.4-1` | Duplicate Address Detection MUST be performed on all unicast addresses prior to assigning them to an interface, regardless of whether they are obtained through stateless autoconfiguration, DHCPv6, or manual configuration (§5.4) | MUST | 5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** DAD is executed by the kernel addrconf engine; ze only observes the kernel's tentative/assigned state via netlink and never performs DAD (internal/plugins/iface/netlink/show_linux.go:201) | | `RFC4862-5.4-2` | Duplicate Address Detection MUST NOT be performed on anycast addresses (§5.4) | MUST NOT | 5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no DAD at all, so the anycast exclusion is enforced by the kernel that performs DAD | | `RFC4862-5.4-5` | New implementations MUST NOT skip DAD for a global address that reuses the link-local interface identifier (the DAD "optimization") (§5.4) | MUST NOT | 5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the kernel decides which addresses undergo DAD; ze forms no addresses and applies no DAD optimization | | `RFC4862-5.4.1-1` | A node MUST silently discard any Neighbor Solicitation or Advertisement message that does not pass the validity checks specified in [RFC4861] (§5.4.1) | MUST | 5.4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** Neighbor Discovery message validation is performed by the kernel neighbor subsystem; ze sends and parses no NS/NA on the SLAAC path | | `RFC4862-5.4.2-1` | Before sending a Neighbor Solicitation, an interface MUST join the all-nodes multicast address and the solicited-node multicast address of the tentative address (§5.4.2) | MUST | 5.4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** multicast group membership for DAD and the tentative NS are issued by the kernel; ze joins no such groups for autoconfiguration | | `RFC4862-5.4.2-4` | An interface MUST receive and process datagrams sent to the all-nodes multicast address or solicited-node multicast address of the tentative address during the delay period (§5.4.2) | MUST | 5.4.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** receiving DAD-window multicast datagrams is a kernel IPv6 input action; ze has no per-packet SLAAC receive path | | `RFC4862-5.4.3-1` | In all cases, a node MUST NOT respond to a Neighbor Solicitation for a tentative address (§5.4.3) | MUST NOT | 5.4.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** NS response suppression for tentative addresses is enforced by the kernel neighbor subsystem that owns the tentative state; ze answers no NS | | `RFC4862-5.4.5-1` | A tentative address that is determined to be a duplicate MUST NOT be assigned to an interface (§5.4.5) | MUST NOT | 5.4.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the kernel decides DAD outcome and refuses assignment on collision; ze only observes the resulting non-tentative address via netlink (internal/plugins/iface/netlink/slaac_linux.go:30) | | `RFC4862-5.5-2` | The global-address creation processing described in this section MUST be enabled by default (§5.5) | MUST | 5.5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** global-address formation from RAs is the kernel's addrconf, on by default via net.ipv6.conf.*.autoconf; ze forwards this knob to the kernel and forms no addresses itself (internal/component/iface/config_sysctl.go:74-75) | | `RFC4862-5.5.3-2` | If the sum of the prefix length and interface identifier length does not equal 128 bits, the Prefix Information option MUST be ignored (§5.5.3) | MUST | 5.5.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** PIO parsing and the 128-bit consistency check happen inside the kernel RA processor; ze parses no Prefix Information options | | `RFC4862-5.5.4-3` | IP and higher layers (e.g., TCP, UDP) MUST continue to accept and process datagrams destined to a deprecated address as normal (§5.5.4) | MUST | 5.5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** datagram acceptance for a deprecated address is a kernel IP-stack behavior; ze runs no IP forwarding/receive datapath | | `RFC4862-5.5.4-5` | If an implementation prevents new communication from using a deprecated address, system management MUST have the ability to disable that facility (§5.5.4) | MUST | 5.5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze implements no facility that prevents new communication on a deprecated address; deprecated-address source selection is the kernel's, configured via kernel sysctls ze passes through (internal/component/iface/config_sysctl.go:73-79) | | `RFC4862-5.5.4-6` | The facility preventing new communication on a deprecated address MUST be disabled by default (§5.5.4) | MUST | 5.5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** any such default lives in the kernel source-address-selection logic; ze provides no deprecated-address-blocking facility to default off | | `RFC4862-5.5.4-7` | An invalid address MUST NOT be used as a source address in outgoing communications (§5.5.4) | MUST NOT | 5.5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** source-address selection excluding invalid (expired) addresses is a kernel function; ze originates no traffic bound by SLAAC source selection and only observes lifetimes via netlink (internal/plugins/iface/netlink/slaac_linux.go:46) | | `RFC4862-5.5.4-8` | An invalid address MUST NOT be recognized as a destination on a receiving interface (§5.5.4) | MUST NOT | 5.5.4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** destination-address acceptance on input is a kernel IP-stack decision; ze has no IPv6 receive datapath that recognizes destinations | | `RFC4862-5.4-3` | Each individual unicast address SHOULD be tested for uniqueness (§5.4) | SHOULD | 5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.4-4` | Performing DAD only for the link-local address and skipping the global address that reuses its interface identifier is NOT RECOMMENDED (§5.4) | NOT RECOMMENDED | 5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.4.2-2` | If the Neighbor Solicitation is the first message sent after interface (re)initialization, the node SHOULD delay joining the solicited-node multicast address by a random delay between 0 and MAX_RTR_SOLICITATION_DELAY (§5.4.2) | SHOULD | 5.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.4.2-3` | Even when not the first message, the node SHOULD delay joining the solicited-node multicast address by a random delay between 0 and MAX_RTR_SOLICITATION_DELAY if the address being checked is configured by a multicasted router advertisement (§5.4.2) | SHOULD | 5.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.4.5-2` | On DAD failure, the node SHOULD log a system management error (§5.4.5) | SHOULD | 5.4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.4.5-3` | If the duplicate link-local address is formed from a hardware-based interface identifier (e.g., EUI-64), IP operation on the interface SHOULD be disabled (§5.4.5) | SHOULD | 5.4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.5-1` | Creation of global addresses as described in this section SHOULD be locally configurable (§5.5) | SHOULD | 5.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.5.4-1` | A deprecated address SHOULD continue to be used as a source address in existing communications (§5.5.4) | SHOULD | 5.5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.5.4-2` | A deprecated address SHOULD NOT be used to initiate new communications if an alternate (non-deprecated) address of sufficient scope can easily be used instead (§5.5.4) | SHOULD NOT | 5.5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.6-1` | If there is no security difference, the most recently obtained values SHOULD have precedence over information learned earlier (§5.6) | SHOULD | 5.6 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.4.5-4` | If the duplicate link-local address is not formed from a hardware-based interface identifier, IP operation on the interface MAY be continued (§5.4.5) | MAY | 5.4.5 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.5.3-1` | A node MAY wish to log a system management error when the preferred lifetime is greater than the valid lifetime (§5.5.3) | MAY | 5.5.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.5.3-3` | An implementation MAY wish to log a system management error when the prefix length plus interface identifier length is not 128 bits (§5.5.3) | MAY | 5.5.3 | **positive:** no positive test. **negative:** no negative test | | `RFC4862-5.5.4-4` | An implementation MAY prevent any new communication from using a deprecated address (§5.5.4) | MAY | 5.5.4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC4862-5.1-1`](#rfc4862-5.1-1) A node MUST allow the DupAddrDetectTransmits autoconfiguration variable to be configured by system management for each multicast-capable interface (§5.1) | no test | no test carries this requirement id; annotated {not-applicable}: DAD is run by the kernel, so DupAddrDetectTransmits is the kernel's net.ipv6.conf.<if>.dad_transmits sysctl; ze configures kernel IPv6 conf keys and owns no DAD variable of its own (internal/component/iface/config_sysctl.go:73-79) | | [`RFC4862-5.4-1`](#rfc4862-5.4-1) Duplicate Address Detection MUST be performed on all unicast addresses prior to assigning them to an interface, regardless of whether they are obtained through stateless autoconfiguration, DHCPv6, or manual configuration (§5.4) | no test | no test carries this requirement id; annotated {not-applicable}: DAD is executed by the kernel addrconf engine; ze only observes the kernel's tentative/assigned state via netlink and never performs DAD (internal/plugins/iface/netlink/show_linux.go:201) | | [`RFC4862-5.4-2`](#rfc4862-5.4-2) Duplicate Address Detection MUST NOT be performed on anycast addresses (§5.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no DAD at all, so the anycast exclusion is enforced by the kernel that performs DAD | | [`RFC4862-5.4-5`](#rfc4862-5.4-5) New implementations MUST NOT skip DAD for a global address that reuses the link-local interface identifier (the DAD "optimization") (§5.4) | no test | no test carries this requirement id; annotated {not-applicable}: the kernel decides which addresses undergo DAD; ze forms no addresses and applies no DAD optimization | | [`RFC4862-5.4.1-1`](#rfc4862-5.4.1-1) A node MUST silently discard any Neighbor Solicitation or Advertisement message that does not pass the validity checks specified in [RFC4861] (§5.4.1) | no test | no test carries this requirement id; annotated {not-applicable}: Neighbor Discovery message validation is performed by the kernel neighbor subsystem; ze sends and parses no NS/NA on the SLAAC path | | [`RFC4862-5.4.2-1`](#rfc4862-5.4.2-1) Before sending a Neighbor Solicitation, an interface MUST join the all-nodes multicast address and the solicited-node multicast address of the tentative address (§5.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: multicast group membership for DAD and the tentative NS are issued by the kernel; ze joins no such groups for autoconfiguration | | [`RFC4862-5.4.2-4`](#rfc4862-5.4.2-4) An interface MUST receive and process datagrams sent to the all-nodes multicast address or solicited-node multicast address of the tentative address during the delay period (§5.4.2) | no test | no test carries this requirement id; annotated {not-applicable}: receiving DAD-window multicast datagrams is a kernel IPv6 input action; ze has no per-packet SLAAC receive path | | [`RFC4862-5.4.3-1`](#rfc4862-5.4.3-1) In all cases, a node MUST NOT respond to a Neighbor Solicitation for a tentative address (§5.4.3) | no test | no test carries this requirement id; annotated {not-applicable}: NS response suppression for tentative addresses is enforced by the kernel neighbor subsystem that owns the tentative state; ze answers no NS | | [`RFC4862-5.4.5-1`](#rfc4862-5.4.5-1) A tentative address that is determined to be a duplicate MUST NOT be assigned to an interface (§5.4.5) | no test | no test carries this requirement id; annotated {not-applicable}: the kernel decides DAD outcome and refuses assignment on collision; ze only observes the resulting non-tentative address via netlink (internal/plugins/iface/netlink/slaac_linux.go:30) | | [`RFC4862-5.5-2`](#rfc4862-5.5-2) The global-address creation processing described in this section MUST be enabled by default (§5.5) | no test | no test carries this requirement id; annotated {not-applicable}: global-address formation from RAs is the kernel's addrconf, on by default via net.ipv6.conf.*.autoconf; ze forwards this knob to the kernel and forms no addresses itself (internal/component/iface/config_sysctl.go:74-75) | | [`RFC4862-5.5.3-2`](#rfc4862-5.5.3-2) If the sum of the prefix length and interface identifier length does not equal 128 bits, the Prefix Information option MUST be ignored (§5.5.3) | no test | no test carries this requirement id; annotated {not-applicable}: PIO parsing and the 128-bit consistency check happen inside the kernel RA processor; ze parses no Prefix Information options | | [`RFC4862-5.5.4-3`](#rfc4862-5.5.4-3) IP and higher layers (e.g., TCP, UDP) MUST continue to accept and process datagrams destined to a deprecated address as normal (§5.5.4) | no test | no test carries this requirement id; annotated {not-applicable}: datagram acceptance for a deprecated address is a kernel IP-stack behavior; ze runs no IP forwarding/receive datapath | | [`RFC4862-5.5.4-5`](#rfc4862-5.5.4-5) If an implementation prevents new communication from using a deprecated address, system management MUST have the ability to disable that facility (§5.5.4) | no test | no test carries this requirement id; annotated {not-applicable}: ze implements no facility that prevents new communication on a deprecated address; deprecated-address source selection is the kernel's, configured via kernel sysctls ze passes through (internal/component/iface/config_sysctl.go:73-79) | | [`RFC4862-5.5.4-6`](#rfc4862-5.5.4-6) The facility preventing new communication on a deprecated address MUST be disabled by default (§5.5.4) | no test | no test carries this requirement id; annotated {not-applicable}: any such default lives in the kernel source-address-selection logic; ze provides no deprecated-address-blocking facility to default off | | [`RFC4862-5.5.4-7`](#rfc4862-5.5.4-7) An invalid address MUST NOT be used as a source address in outgoing communications (§5.5.4) | no test | no test carries this requirement id; annotated {not-applicable}: source-address selection excluding invalid (expired) addresses is a kernel function; ze originates no traffic bound by SLAAC source selection and only observes lifetimes via netlink (internal/plugins/iface/netlink/slaac_linux.go:46) | | [`RFC4862-5.5.4-8`](#rfc4862-5.5.4-8) An invalid address MUST NOT be recognized as a destination on a receiving interface (§5.5.4) | no test | no test carries this requirement id; annotated {not-applicable}: destination-address acceptance on input is a kernel IP-stack decision; ze has no IPv6 receive datapath that recognizes destinations | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC4862-5.1-1`](#rfc4862-5.1-1) A node MUST allow the DupAddrDetectTransmits autoconfiguration variable to be configured by system management for each multicast-capable interface (§5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.1-1, so no unit is bound to it. ### [`RFC4862-5.4-1`](#rfc4862-5.4-1) Duplicate Address Detection MUST be performed on all unicast addresses prior to assigning them to an interface, regardless of whether they are obtained through stateless autoconfiguration, DHCPv6, or manual configuration (§5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.4-1, so no unit is bound to it. ### [`RFC4862-5.4-2`](#rfc4862-5.4-2) Duplicate Address Detection MUST NOT be performed on anycast addresses (§5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.4-2, so no unit is bound to it. ### [`RFC4862-5.4-5`](#rfc4862-5.4-5) New implementations MUST NOT skip DAD for a global address that reuses the link-local interface identifier (the DAD "optimization") (§5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.4-5, so no unit is bound to it. ### [`RFC4862-5.4.1-1`](#rfc4862-5.4.1-1) A node MUST silently discard any Neighbor Solicitation or Advertisement message that does not pass the validity checks specified in [RFC4861] (§5.4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.4.1-1, so no unit is bound to it. ### [`RFC4862-5.4.2-1`](#rfc4862-5.4.2-1) Before sending a Neighbor Solicitation, an interface MUST join the all-nodes multicast address and the solicited-node multicast address of the tentative address (§5.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.4.2-1, so no unit is bound to it. ### [`RFC4862-5.4.2-4`](#rfc4862-5.4.2-4) An interface MUST receive and process datagrams sent to the all-nodes multicast address or solicited-node multicast address of the tentative address during the delay period (§5.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.4.2-4, so no unit is bound to it. ### [`RFC4862-5.4.3-1`](#rfc4862-5.4.3-1) In all cases, a node MUST NOT respond to a Neighbor Solicitation for a tentative address (§5.4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.4.3-1, so no unit is bound to it. ### [`RFC4862-5.4.5-1`](#rfc4862-5.4.5-1) A tentative address that is determined to be a duplicate MUST NOT be assigned to an interface (§5.4.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.4.5-1, so no unit is bound to it. ### [`RFC4862-5.5-2`](#rfc4862-5.5-2) The global-address creation processing described in this section MUST be enabled by default (§5.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.5-2, so no unit is bound to it. ### [`RFC4862-5.5.3-2`](#rfc4862-5.5.3-2) If the sum of the prefix length and interface identifier length does not equal 128 bits, the Prefix Information option MUST be ignored (§5.5.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.5.3-2, so no unit is bound to it. ### [`RFC4862-5.5.4-3`](#rfc4862-5.5.4-3) IP and higher layers (e.g., TCP, UDP) MUST continue to accept and process datagrams destined to a deprecated address as normal (§5.5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.5.4-3, so no unit is bound to it. ### [`RFC4862-5.5.4-5`](#rfc4862-5.5.4-5) If an implementation prevents new communication from using a deprecated address, system management MUST have the ability to disable that facility (§5.5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.5.4-5, so no unit is bound to it. ### [`RFC4862-5.5.4-6`](#rfc4862-5.5.4-6) The facility preventing new communication on a deprecated address MUST be disabled by default (§5.5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.5.4-6, so no unit is bound to it. ### [`RFC4862-5.5.4-7`](#rfc4862-5.5.4-7) An invalid address MUST NOT be used as a source address in outgoing communications (§5.5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.5.4-7, so no unit is bound to it. ### [`RFC4862-5.5.4-8`](#rfc4862-5.5.4-8) An invalid address MUST NOT be recognized as a destination on a receiving interface (§5.5.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC4862-5.5.4-8, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 4862, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 4862, so its obligations are stated where they were written. --- ### Page: RFC 5036 - LDP Specification https://ze-software.net/quality/rfc-compliance/rfc5036/ # RFC 5036 - LDP Specification Experimental. Every requirement this repository extracted from RFC 5036, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 50.0% | 7 of 14 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 14 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 14 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 14 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 14 | of 18 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 14 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 14 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 14 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 14 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 50.0% | 7 of 14 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 14 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 18 | | Gated MUST-level | 14 | | Not applicable, so out of scope | 0 | | Declared gaps | 7 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 14 | | Tagged units | 14 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5036.md` | | Requirement shard | `rfc/requirements/rfc5036.md` | | RFC text | `rfc/full/rfc5036.txt` | ## Enrolment Enrolled: LDP Specification ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** Basic Discovery, active-only TCP session FSM with Initialization/KeepAlive negotiation, label information base, downstream-unsolicited Label Mapping advertisement and reception, kernel MPLS integration. **What the ledger says remains** Seven MUST gaps gated in [`rfc/short/rfc5036.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5036.md). - **Messages ze never emits:** [`RFC5036-2.5.3-2`](#rfc5036-2.5.3-2) and [`RFC5036-3.5.1-1`](#rfc5036-3.5.1-1) (no Notification encoder in wire.go, so a fatal error closes the session silently), [`RFC5036-2.6.1.3-1`](#rfc5036-2.6.1.3-1) (no Label Release on a received withdraw), [`RFC5036-2.6.1.2-1`](#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`](#rfc5036-2.5.1-4) (handleInit at session.go overwrites the expected peer LSR ID instead of comparing it), [`RFC5036-2.5.1-3`](#rfc5036-2.5.1-3) (the establishment KeepAlive at register.go is unconditional, not a response to an accepted Initialization). - **Forwarding:** [`RFC5036-2.7-1`](#rfc5036-2.7-1) (ProgramPush at fib.go imposes labels without checking that MPLS forwarding is enabled on the interface). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 7 | one part of the gated population | | Annotated instead of tested | 7 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **14** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (7):** [`RFC5036-x-1`](#rfc5036-x-1), [`RFC5036-x-2`](#rfc5036-x-2), [`RFC5036-x-3`](#rfc5036-x-3), [`RFC5036-2.5.1-1`](#rfc5036-2.5.1-1), [`RFC5036-2.5.3-1`](#rfc5036-2.5.3-1), [`RFC5036-2.5.1-2`](#rfc5036-2.5.1-2), [`RFC5036-3.5.2-1`](#rfc5036-3.5.2-1) **Annotated instead of tested (7):** [`RFC5036-2.6.1.2-1`](#rfc5036-2.6.1.2-1), [`RFC5036-2.6.1.3-1`](#rfc5036-2.6.1.3-1), [`RFC5036-2.5.1-3`](#rfc5036-2.5.1-3), [`RFC5036-2.5.1-4`](#rfc5036-2.5.1-4), [`RFC5036-2.5.3-2`](#rfc5036-2.5.3-2), [`RFC5036-2.7-1`](#rfc5036-2.7-1), [`RFC5036-3.5.1-1`](#rfc5036-3.5.1-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5036-x-1` | Version field in PDU header must be 1 (Wire Format) | MUST | x | **positive:** `unit/verify` [`TestRFC5036PDUVersionOneAccepted`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L123). **negative:** `unit/verify` [`TestRFC5036PDUVersionOtherRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L150) | | `RFC5036-x-2` | Reserved bits in Common Hello Parameters TLV must be zero (Discovery) | MUST | x | **positive:** `unit/verify` [`TestRFC5036HelloReservedBitsZeroOnTransmit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L175). **negative:** `unit/verify` [`TestRFC5036HelloReservedBitsIgnoredOnReceipt`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L197) | | `RFC5036-x-3` | Protocol Version in Common Session Parameters must be 1 (Sessions) | MUST | x | **positive:** `unit/verify` [`TestRFC5036InitProtocolVersionOne`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L220). **negative:** `unit/verify` [`TestRFC5036InitProtocolVersionOtherRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L254) | | `RFC5036-2.5.1-1` | An LSR MUST send the Initialization message to start a session (§2.5.1) | MUST | 2.5.1 | **positive:** `unit/verify` [`TestRFC5036SessionSendsInitializationFirst`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L279). **negative:** `unit/verify` [`TestRFC5036SessionNotOperationalWithoutOwnInit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L302) | | `RFC5036-2.5.3-1` | An LSR MUST periodically send KeepAlive messages on established sessions (§2.5.3) | MUST | 2.5.3 | **positive:** `unit/verify` [`TestRFC5036KeepalivesSentPeriodically`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L390). **negative:** `unit/verify` [`TestRFC5036KeepalivesNotSentContinuously`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L416) | | `RFC5036-2.6.1.2-1` | An LSR MUST send a Label Withdraw message when a previously advertised binding is no longer valid (§2.6.1.2) | MUST | 2.6.1.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the encoder and sender exist (internal/plugins/ldp/session.go:326 SendLabelWithdraw, internal/plugins/ldp/wire.go:499 EncodeLabelWithdraw) but nothing invokes them -- a local binding is created once in OnStarted (internal/plugins/ldp/register.go:312) and released only by RemovePop at engine exit (internal/plugins/ldp/register.go:380), so no Label Withdraw ever reaches the wire | | `RFC5036-2.6.1.3-1` | An LSR MUST send a Label Release message when it no longer needs a label (§2.6.1.3) | MUST | 2.6.1.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the withdraw handler drops the binding and reconciles forwarding without replying (internal/plugins/ldp/register.go:821 the onWithdraw callback, internal/plugins/ldp/fib.go:48 withdrawRemoteBinding); MsgTypeLabelRelease has no encoder and an inbound one is discarded at internal/plugins/ldp/session.go:463 | | `RFC5036-2.5.1-2` | An LSR MUST accept the lower of the two proposed KeepAlive Timer values during negotiation (§2.5.1) | MUST | 2.5.1 | **positive:** `unit/verify` [`TestRFC5036KeepaliveNegotiationAdoptsLower`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L335). **negative:** `unit/verify` [`TestRFC5036KeepaliveNegotiationRefusesHigher`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L351) | | `RFC5036-2.5.1-3` | An LSR MUST respond to a received Initialization with a KeepAlive message if parameters are acceptable (§2.5.1) | MUST | 2.5.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** handleInit (internal/plugins/ldp/session.go:504) applies the negotiated parameters and advances the FSM without emitting anything; the only establishment KeepAlive is the unconditional one at internal/plugins/ldp/register.go:758, sent right after ze's own Initialization and before the peer's arrives, so no KeepAlive is conditioned on receiving and accepting an Initialization | | `RFC5036-3.5.2-1` | A Common Hello Parameters Hold Time of 0 means use the default hold time -- 15 seconds for Link Hellos, 45 seconds for Targeted Hellos -- and the adjacency is kept, not removed (§3.5.2) | MUST | 3.5.2 | **positive:** `unit/verify` [`TestRFC5036HelloHoldTimeZeroUsesDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L455). **negative:** `unit/verify` [`TestRFC5036HelloHoldTimeNonZeroNotDefaulted`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L495) | | `RFC5036-2.5.1-4` | The LSR MUST check that the LSR ID matches what was expected in the Initialization (§2.5.1) | MUST | 2.5.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** handleInit overwrites the expected peer LSR ID with whatever the PDU header carried (internal/plugins/ldp/session.go:508 `s.peerLSRID = peerLSRID`) instead of comparing it to the value learned from the Hello, and the Receiver LSR ID decoded from the Common Session Parameters TLV (internal/plugins/ldp/wire.go:338) is never compared to the local LSR ID | | `RFC5036-2.5.3-2` | An LSR MUST send a Notification message for fatal errors that require session teardown (§2.5.3) | MUST | 2.5.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the fatal-error path returns the error and closes the connection with no Notification (internal/plugins/ldp/session.go:371 keepalive expiry, internal/plugins/ldp/session.go:391 decode failure, internal/plugins/ldp/register.go:859 the session-ended log); wire.go has no Notification or Status TLV encoder | | `RFC5036-2.7-1` | An LSR MUST NOT send labeled packets on a link until MPLS forwarding has been enabled on that interface (§2.7) | MUST NOT | 2.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ProgramPush (internal/plugins/ldp/fib.go:128) emits the label-imposition entry for every accepted binding with no check that MPLS forwarding is enabled on the outgoing interface; enabling it is operator config carried by iface (internal/component/iface/config_sysctl.go:71 net.mpls.conf.<iface>.input) and LDP reads no such state | | `RFC5036-3.5.1-1` | An LSR MUST NOT process any further messages after sending a fatal Notification (§3.5.1) | MUST NOT | 3.5.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the halt half holds -- processMessages returns on the first decode failure (internal/plugins/ldp/session.go:397) and ReadLoop propagates it (internal/plugins/ldp/session.go:391) -- but ze sends no fatal Notification to halt after, so the obligation's trigger has no producer; it is unmet for the same reason as RFC5036-2.5.3-2 | | `RFC5036-2.9-1` | An LSR SHOULD use TCP MD5 Authentication for session protection (§2.9) | SHOULD | 2.9 | **positive:** no positive test. **negative:** no negative test | | `RFC5036-2.6.1.1-1` | An LSR SHOULD advertise labels for all FECs in downstream unsolicited mode (§2.6.1.1) | SHOULD | 2.6.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5036-2.8-1` | An LSR MAY use loop detection mechanisms (hop count, path vector) (§2.8) | MAY | 2.8 | **positive:** no positive test. **negative:** no negative test | | `RFC5036-2.4.1-1` | An LSR MAY use Extended Discovery for non-adjacent peers (§2.4.1) | MAY | 2.4.1 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5036-2.6.1.2-1`](#rfc5036-2.6.1.2-1) An LSR MUST send a Label Withdraw message when a previously advertised binding is no longer valid (§2.6.1.2) | {gap}, no test | the encoder and sender exist (internal/plugins/ldp/session.go:326 SendLabelWithdraw, internal/plugins/ldp/wire.go:499 EncodeLabelWithdraw) but nothing invokes them -- a local binding is created once in OnStarted (internal/plugins/ldp/register.go:312) and released only by RemovePop at engine exit (internal/plugins/ldp/register.go:380), so no Label Withdraw ever reaches the wire | | [`RFC5036-2.6.1.3-1`](#rfc5036-2.6.1.3-1) An LSR MUST send a Label Release message when it no longer needs a label (§2.6.1.3) | {gap}, no test | the withdraw handler drops the binding and reconciles forwarding without replying (internal/plugins/ldp/register.go:821 the onWithdraw callback, internal/plugins/ldp/fib.go:48 withdrawRemoteBinding); MsgTypeLabelRelease has no encoder and an inbound one is discarded at internal/plugins/ldp/session.go:463 | | [`RFC5036-2.5.1-3`](#rfc5036-2.5.1-3) An LSR MUST respond to a received Initialization with a KeepAlive message if parameters are acceptable (§2.5.1) | {gap}, no test | handleInit (internal/plugins/ldp/session.go:504) applies the negotiated parameters and advances the FSM without emitting anything; the only establishment KeepAlive is the unconditional one at internal/plugins/ldp/register.go:758, sent right after ze's own Initialization and before the peer's arrives, so no KeepAlive is conditioned on receiving and accepting an Initialization | | [`RFC5036-2.5.1-4`](#rfc5036-2.5.1-4) The LSR MUST check that the LSR ID matches what was expected in the Initialization (§2.5.1) | {gap}, no test | handleInit overwrites the expected peer LSR ID with whatever the PDU header carried (internal/plugins/ldp/session.go:508 `s.peerLSRID = peerLSRID`) instead of comparing it to the value learned from the Hello, and the Receiver LSR ID decoded from the Common Session Parameters TLV (internal/plugins/ldp/wire.go:338) is never compared to the local LSR ID | | [`RFC5036-2.5.3-2`](#rfc5036-2.5.3-2) An LSR MUST send a Notification message for fatal errors that require session teardown (§2.5.3) | {gap}, no test | the fatal-error path returns the error and closes the connection with no Notification (internal/plugins/ldp/session.go:371 keepalive expiry, internal/plugins/ldp/session.go:391 decode failure, internal/plugins/ldp/register.go:859 the session-ended log); wire.go has no Notification or Status TLV encoder | | [`RFC5036-2.7-1`](#rfc5036-2.7-1) An LSR MUST NOT send labeled packets on a link until MPLS forwarding has been enabled on that interface (§2.7) | {gap}, no test | ProgramPush (internal/plugins/ldp/fib.go:128) emits the label-imposition entry for every accepted binding with no check that MPLS forwarding is enabled on the outgoing interface; enabling it is operator config carried by iface (internal/component/iface/config_sysctl.go:71 net.mpls.conf.<iface>.input) and LDP reads no such state | | [`RFC5036-3.5.1-1`](#rfc5036-3.5.1-1) An LSR MUST NOT process any further messages after sending a fatal Notification (§3.5.1) | {gap}, no test | the halt half holds -- processMessages returns on the first decode failure (internal/plugins/ldp/session.go:397) and ReadLoop propagates it (internal/plugins/ldp/session.go:391) -- but ze sends no fatal Notification to halt after, so the obligation's trigger has no producer; it is unmet for the same reason as RFC5036-2.5.3-2 | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5036-x-1`](#rfc5036-x-1) Version field in PDU header must be 1 (Wire Format) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5036PDUVersionOtherRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L150) | unit/verify | unproven | | positive | [`TestRFC5036PDUVersionOneAccepted`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L123) | unit/verify | unproven | ### [`RFC5036-x-2`](#rfc5036-x-2) Reserved bits in Common Hello Parameters TLV must be zero (Discovery) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5036HelloReservedBitsIgnoredOnReceipt`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L197) | unit/verify | unproven | | positive | [`TestRFC5036HelloReservedBitsZeroOnTransmit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L175) | unit/verify | unproven | ### [`RFC5036-x-3`](#rfc5036-x-3) Protocol Version in Common Session Parameters must be 1 (Sessions) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5036InitProtocolVersionOtherRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L254) | unit/verify | unproven | | positive | [`TestRFC5036InitProtocolVersionOne`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L220) | unit/verify | unproven | ### [`RFC5036-2.5.1-1`](#rfc5036-2.5.1-1) An LSR MUST send the Initialization message to start a session (§2.5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5036SessionNotOperationalWithoutOwnInit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L302) | unit/verify | unproven | | positive | [`TestRFC5036SessionSendsInitializationFirst`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L279) | unit/verify | unproven | ### [`RFC5036-2.5.3-1`](#rfc5036-2.5.3-1) An LSR MUST periodically send KeepAlive messages on established sessions (§2.5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5036KeepalivesNotSentContinuously`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L416) | unit/verify | unproven | | positive | [`TestRFC5036KeepalivesSentPeriodically`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L390) | unit/verify | unproven | ### [`RFC5036-2.6.1.2-1`](#rfc5036-2.6.1.2-1) An LSR MUST send a Label Withdraw message when a previously advertised binding is no longer valid (§2.6.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5036-2.6.1.2-1, so no unit is bound to it. ### [`RFC5036-2.6.1.3-1`](#rfc5036-2.6.1.3-1) An LSR MUST send a Label Release message when it no longer needs a label (§2.6.1.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5036-2.6.1.3-1, so no unit is bound to it. ### [`RFC5036-2.5.1-2`](#rfc5036-2.5.1-2) An LSR MUST accept the lower of the two proposed KeepAlive Timer values during negotiation (§2.5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5036KeepaliveNegotiationRefusesHigher`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L351) | unit/verify | unproven | | positive | [`TestRFC5036KeepaliveNegotiationAdoptsLower`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L335) | unit/verify | unproven | ### [`RFC5036-2.5.1-3`](#rfc5036-2.5.1-3) An LSR MUST respond to a received Initialization with a KeepAlive message if parameters are acceptable (§2.5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5036-2.5.1-3, so no unit is bound to it. ### [`RFC5036-3.5.2-1`](#rfc5036-3.5.2-1) A Common Hello Parameters Hold Time of 0 means use the default hold time -- 15 seconds for Link Hellos, 45 seconds for Targeted Hellos -- and the adjacency is kept, not removed (§3.5.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5036HelloHoldTimeNonZeroNotDefaulted`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L495) | unit/verify | unproven | | positive | [`TestRFC5036HelloHoldTimeZeroUsesDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ldp/rfc5036_test.go#L455) | unit/verify | unproven | ### [`RFC5036-2.5.1-4`](#rfc5036-2.5.1-4) The LSR MUST check that the LSR ID matches what was expected in the Initialization (§2.5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5036-2.5.1-4, so no unit is bound to it. ### [`RFC5036-2.5.3-2`](#rfc5036-2.5.3-2) An LSR MUST send a Notification message for fatal errors that require session teardown (§2.5.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5036-2.5.3-2, so no unit is bound to it. ### [`RFC5036-2.7-1`](#rfc5036-2.7-1) An LSR MUST NOT send labeled packets on a link until MPLS forwarding has been enabled on that interface (§2.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5036-2.7-1, so no unit is bound to it. ### [`RFC5036-3.5.1-1`](#rfc5036-3.5.1-1) An LSR MUST NOT process any further messages after sending a fatal Notification (§3.5.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5036-3.5.1-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 5036, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5036, so its obligations are stated where they were written. --- ### Page: RFC 5065 - Autonomous System Confederations for BGP https://ze-software.net/quality/rfc-compliance/rfc5065/ # RFC 5065 - Autonomous System Confederations for BGP Unsupported. Every requirement this repository extracted from RFC 5065, the tests bound to it, and what a reader has verified about them. This summary is not enrolled. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 0 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 0 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 0 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 0 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | | Audit verdicts | 0 | of 0 gated MUSTs judged | 0 weak, wrong or unimplemented, 0 no longer current. Each is named below under its own requirement id | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | MUSTs declared | 0 | of 0 this summary declares | MUST-level requirements this summary DECLARES. The gate holds none of them, because this RFC is not enrolled (blocked), so every share below reads what the summary records rather than what the gate enforces | | Out of scope | 0 | of 0 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 0 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 0 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 0 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | No card above is a share of a population, so there is nothing to add up. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | MUSTs declared | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | ok | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Unsupported | | Enrolment | Not enrolled (blocked) | | Requirements | 0 | | Gated MUST-level | 0 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5065.md` | | Requirement shard | no requirement declared, so no shard is generated | | RFC text | `rfc/full/rfc5065.txt` | ## Enrolment Not enrolled (blocked, something outside the summary stops the extraction, and it is named in the reason): BGP confederations. Written 2026-09-01 for the same reason as the other six rows that had no summary: the public claim now lives in the document it is about. It is not enrolled because there is no source text at rfc/full/rfc5065.txt: fetch https://www.rfc-editor.org/rfc/rfc5065.txt, then extract. The row claims Unsupported, so no obligation here is gated. ## What the public ledger says **Status:** Unsupported **What the ledger says is covered:** None claimed. **What the ledger says remains:** Explicitly unsupported. ## Coverage RFC 5065 declares no MUST-level requirement, so the gate counts nothing here. ## Requirements RFC 5065 declares no requirement, so this summary generates no shard. ## Gaps and untested MUSTs RFC 5065 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state RFC 5065 carries no gated, tagged or audited requirement, so there is no proof state to state. ## Extraction sign-off No extraction sign-off exists for RFC 5065, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5065, so its obligations are stated where they were written. --- ### Page: RFC 5072 - IP Version 6 over PPP https://ze-software.net/quality/rfc-compliance/rfc5072/ # RFC 5072 - IP Version 6 over PPP Partial. Every requirement this repository extracted from RFC 5072, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 37.5% | 6 of 16 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 37.5% | 6 of 16 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 16 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 26.3% | 5 of 19 tagged units, 0 escaped and 2 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 16 | of 28 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 16 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 6.2% | 1 of 16 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 16 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 16 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 18.8% | 3 of 16 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 16 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 28 | | Gated MUST-level | 16 | | Not applicable, so out of scope | 1 | | Declared gaps | 3 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 19 | | Tagged units | 19 | | Recorded audit verdicts | 0 | | Discrimination records | 7 | | Summary | `rfc/short/rfc5072.md` | | Requirement shard | `rfc/requirements/rfc5072.md` | | RFC text | `rfc/full/rfc5072.txt` | ## Enrolment Enrolled: IPv6 over PPP / IPV6CP (RFC 5072): 7 MET (no IPV6CP before network phase, unique interface ID, different-non-zero->Ack, Nak->resend CR, valid Reject->teardown, exactly-one IID option enforced on receive, both-zero->Reject) + 5 single-polarity positive (no IPv6 before Opened, no double-Nak on a missing IID option, collision Nak suggests a fresh different identifier, the suggestion differs from ze's last-sent identifier, the suggestion's u/l bit is zero) + 3 gap (tentative identifier's u/l bit not zeroed, no 1280 MTU floor, oscillation break) + 1 not-applicable (EUI-derived source) ## What the public ledger says **Status:** Partial **What the ledger says is covered:** Interface-Identifier NCP: independent FSM, generation, Configure-Req/Ack/Nak/Reject, RA/DHCPv6-PD after Opened. **What the ledger says remains** Gaps in [`rfc/short/rfc5072.md`](https://github.com/ze-software/ze/blob/main/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). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 6 | one part of the gated population | | Annotated instead of tested | 10 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **16** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (6):** [`RFC5072-3-1`](#rfc5072-3-1), [`RFC5072-4.1-1`](#rfc5072-4.1-1), [`RFC5072-4.1-2`](#rfc5072-4.1-2), [`RFC5072-4.1-3`](#rfc5072-4.1-3), [`RFC5072-4.1-7`](#rfc5072-4.1-7), [`RFC5072-4.1-12`](#rfc5072-4.1-12) **Annotated instead of tested (10):** [`RFC5072-2-1`](#rfc5072-2-1), [`RFC5072-2-2`](#rfc5072-2-2), [`RFC5072-4.1-4`](#rfc5072-4.1-4), [`RFC5072-4.1-5`](#rfc5072-4.1-5), [`RFC5072-4.1-6`](#rfc5072-4.1-6), [`RFC5072-4.1-8`](#rfc5072-4.1-8), [`RFC5072-4.1-9`](#rfc5072-4.1-9), [`RFC5072-4.1-10`](#rfc5072-4.1-10), [`RFC5072-4.1-11`](#rfc5072-4.1-11), [`RFC5072-4.1-19`](#rfc5072-4.1-19) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5072-3-1` | IPV6CP packets MUST NOT be exchanged until PPP has reached the network-layer protocol phase (§3) | MUST | 3 | **positive:** `unit/verify` [`TestIPv6CPOpenedEmitsAssigned`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L401). **negative:** `unit/verify` [`TestIPv6CPNoResponseBeforeNetworkPhase`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L673) | | `RFC5072-2-1` | PPP MUST reach the network-layer protocol phase, and IPv6 Control Protocol MUST reach the Opened state before any IPv6 packet is sent (§2) | MUST | 2 | **positive:** `unit/verify` [`TestIPv6ServiceStartsOnlyAfterIPv6CPOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L447). **negative:** no negative test. **{single-polarity}:** ze starts its IPv6 service (Router Advertisements) only after IPV6CP reaches Opened, and there is no ze code path that emits an IPv6 packet before Opened to negatively exercise (internal/component/l2tp/ppp/session_run.go:482) | | `RFC5072-2-2` | PPP links supporting IPv6 MUST allow the information field to be at least as large as the minimum link MTU size required for IPv6 (§2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the MTU floor applied to an IPv6-enabled session is minIPMTU=68, not 1280, so a session whose negotiated MRU is below 1284 gets a sub-1280 MTU; no 1280 clamp exists (internal/component/l2tp/ppp/session_run.go:42, :472) | | `RFC5072-4.1-1` | A Configure-Request MUST contain exactly one instance of the interface-identifier option (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestIPv6CPProposesInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ipv6cp_test.go#L123). **negative:** `unit/verify` [`TestIPv6CPDuplicateIdentifierOptionIsNotAcked`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1101). **negative:** `unit/verify` [`TestIPv6CPRequestWithoutIdentifierIsNotAcked`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1033) | | `RFC5072-4.1-2` | The interface identifier MUST be unique within the PPP link (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestIPv6CPInterfaceIDsDiffer`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L715). **negative:** `unit/verify` [`TestIPv6CPNaksCollidingInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L764) | | `RFC5072-4.1-3` | If the two interface identifiers are different and the received identifier is not zero, it MUST be acknowledged with Configure-Ack (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestIPv6CPAcksDifferentNonZeroInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L801). **negative:** `unit/verify` [`TestIPv6CPNaksZeroInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L840) | | `RFC5072-4.1-4` | If the two interface identifiers are equal and non-zero, Configure-Nak MUST be sent specifying a different non-zero interface-identifier (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestIPv6CPNakOnEqualNonZeroIdentifiers`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1260). **negative:** no negative test. **{single-polarity}:** the Nak's suggestion is drawn by suggestIPv6CPInterfaceID (ipv6cp.go) and rejects a redraw equal to s.localInterfaceID, so the value ze sends is never the identifier ze holds; the negative shape (an Ack, or a Nak echoing the collision) is what evalIPv6CPRequest's own unacceptable verdict already forecloses, both-polarity proven under RFC5072-4.1-2's negative test (TestIPv6CPNaksCollidingInterfaceID) | | `RFC5072-4.1-5` | The suggested interface identifier MUST be different from the interface identifier of the last Configure-Request sent to the peer (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestIPv6CPSuggestionDiffersFromLocalIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1304). **negative:** no negative test. **{single-polarity}:** suggestIPv6CPInterfaceID (ipv6cp.go) rejects and redraws any value equal to s.localInterfaceID, which is the identifier of ze's own last-sent (or about-to-resend) Configure-Request (see the doc comment on pppSession.localInterfaceID, session.go); TestIPv6CPSuggestionDiffersFromLocalIdentifier exhausts the draw loop rather than exercising a negative case there is no code path to produce | | `RFC5072-4.1-6` | If both interface identifiers are zero, negotiation MUST be terminated by transmitting Configure-Reject with IID=0 (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestIPv6CPBothZeroIsRejected`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1173). **negative:** no negative test. **{single-polarity}:** the negative case -- a zero received identifier that does NOT equal ze's own zero-if-forced-so local one -- is the ordinary differing-and-zero fork, already both-polarity proven under RFC5072-4.1-3's negative test (TestIPv6CPNaksZeroInterfaceID), which shows Nak fires rather than Reject; a second test here would re-exercise that same fork rather than a distinct one | | `RFC5072-4.1-7` | On receiving Configure-Nak, a new Configure-Request MUST be sent with the suggested identifier value (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestIPv6CPResendsCRWithNakSuggestedID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L874). **negative:** `unit/verify` [`TestIPv6CPNakInvalidSuggestionNotAdopted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L910) | | `RFC5072-4.1-8` | If the received interface identifier equals the one sent in the last Configure-Nak, a new interface identifier MUST be chosen (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** absorbIPv6CPNak adopts the peer's suggestion unconditionally; ze keeps no record of the IID it suggested in its own last Nak and never regenerates on oscillation (internal/component/l2tp/ppp/ncp.go:479-487) | | `RFC5072-4.1-9` | The "u" bit of the suggested identifier MUST be set to zero unless a globally unique EUI-48/EUI-64 derived identifier is provided for the peer's exclusive use (§4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestIPv6CPSuggestionHasUniversalBitClear`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ipv6cp_test.go#L187). **negative:** no negative test. **{single-polarity}:** suggestIPv6CPInterfaceID (ipv6cp.go) clears octet 0's bit 0x02 (canonical bit 6) on every draw; ze never derives a suggestion from a globally unique EUI-48/EUI-64 identifier loaned to the peer, so the exception this MUST carves out is never taken and there is no code path to negatively exercise. Distinct from RFC5072-4.1-11, which is the u bit of ze's own TENTATIVE identifier (requestIPv6CPInterfaceID's call to generateIPv6CPInterfaceID) rather than the SUGGESTED one this row and suggestIPv6CPInterfaceID govern -- that row's gap stands, unchanged by this phase | | `RFC5072-4.1-10` | When uniqueness source is link-layer addresses or serial numbers, the "u" bit MUST be set to zero (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze's sole interface-identifier source is crypto/rand; it never derives an identifier from a link-layer address or serial number, so this source-specific clause binds a code path ze does not have (internal/component/l2tp/ppp/ipv6cp.go:133) | | `RFC5072-4.1-11` | When a random number is generated, the "u" bit MUST be set to zero (§4.1) | MUST | 4.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze's identifier is randomly generated and the generator does not force the u bit to zero, the exact case this MUST governs (internal/component/l2tp/ppp/ipv6cp.go:133-144) | | `RFC5072-4.1-12` | A new Configure-Request MUST NOT contain the interface-identifier option if a valid Configure-Reject is received (§4.1) | MUST NOT | 4.1 | **positive:** `unit/verify` [`TestIPv6CPInterfaceIDRejectIsFatal`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L955). **negative:** `unit/verify` [`TestIPv6CPUnknownOptionRejectNotFatal`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L988) | | `RFC5072-3-2` | Codes other than 1-7 should result in Code-Rejects (§3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-3-3` | IPV6CP packets received before network-layer phase should be silently discarded (§3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-4.1-13` | The non-zero tentative interface identifier SHOULD be unique to the link and preferably consistently reproducible across initializations (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-4.1-14` | A new Configure-Request SHOULD NOT be sent until normal processing would cause it (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-4.1-15` | A new Configure-Request SHOULD be sent with the new tentative interface identifier when peer's Nak proposed local value back (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-4.1-16` | If negotiation is required and peer did not provide the option, it SHOULD be appended to a Configure-Nak (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-4.1-17` | An implementation SHOULD attempt to negotiate the interface identifier for its end of the PPP connection (§4.1) | SHOULD | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-5-1` | The interface identifier of IPv6 unicast addresses of a PPP interface SHOULD be negotiated in the IPV6CP phase (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-5-2` | It SHOULD NOT be assumed that the same interface identifier is used for global unicast addresses via SLAAC (§5) | SHOULD NOT | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-5-3` | Default DupAddrDetectTransmits SHOULD be zero when IPV6CP negotiated unique identifiers on an exclusive-prefix PPP link (§5) | RECOMMENDED | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-5-4` | The PPP peer MAY generate interface identifiers using RFC 4941 methods to autoconfigure global unicast addresses (§5) | MAY | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-4.1-18` | If no usable identifier can be produced, it MAY send zero to request the peer to supply one (§4.1) | MAY | 4.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5072-4.1-19` | Having Configure-Naked a Configure-Request that omitted the interface-identifier option once, an implementation MUST NOT Configure-Nak a further Configure-Request that also omits it (§4.1) | MUST NOT | 4.1 | **positive:** `unit/verify` [`TestIPv6CPMissingOptionIsNakedOnce`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1064). **negative:** no negative test. **{single-polarity}:** the violating shape (Naking the option-less request a second time) is exactly the pre-fix defect this MUST NOT closes, and TestIPv6CPMissingOptionIsNakedOnce proves both halves of the single-session sequence -- Nak the first time, Ack rather than Nak the second -- inside one test rather than a separate negative one | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5072-2-2`](#rfc5072-2-2) PPP links supporting IPv6 MUST allow the information field to be at least as large as the minimum link MTU size required for IPv6 (§2) | {gap}, no test | the MTU floor applied to an IPv6-enabled session is minIPMTU=68, not 1280, so a session whose negotiated MRU is below 1284 gets a sub-1280 MTU; no 1280 clamp exists (internal/component/l2tp/ppp/session_run.go:42, :472) | | [`RFC5072-4.1-8`](#rfc5072-4.1-8) If the received interface identifier equals the one sent in the last Configure-Nak, a new interface identifier MUST be chosen (§4.1) | {gap}, no test | absorbIPv6CPNak adopts the peer's suggestion unconditionally; ze keeps no record of the IID it suggested in its own last Nak and never regenerates on oscillation (internal/component/l2tp/ppp/ncp.go:479-487) | | [`RFC5072-4.1-10`](#rfc5072-4.1-10) When uniqueness source is link-layer addresses or serial numbers, the "u" bit MUST be set to zero (§4.1) | no test | no test carries this requirement id; annotated {not-applicable}: ze's sole interface-identifier source is crypto/rand; it never derives an identifier from a link-layer address or serial number, so this source-specific clause binds a code path ze does not have (internal/component/l2tp/ppp/ipv6cp.go:133) | | [`RFC5072-4.1-11`](#rfc5072-4.1-11) When a random number is generated, the "u" bit MUST be set to zero (§4.1) | {gap}, no test | ze's identifier is randomly generated and the generator does not force the u bit to zero, the exact case this MUST governs (internal/component/l2tp/ppp/ipv6cp.go:133-144) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5072-3-1`](#rfc5072-3-1) IPV6CP packets MUST NOT be exchanged until PPP has reached the network-layer protocol phase (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPv6CPNoResponseBeforeNetworkPhase`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L673) | unit/verify | unproven | | positive | [`TestIPv6CPOpenedEmitsAssigned`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L401) | unit/verify | unproven | ### [`RFC5072-2-1`](#rfc5072-2-1) PPP MUST reach the network-layer protocol phase, and IPv6 Control Protocol MUST reach the Opened state before any IPv6 packet is sent (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPv6ServiceStartsOnlyAfterIPv6CPOpened`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L447) | unit/verify | unproven | ### [`RFC5072-2-2`](#rfc5072-2-2) PPP links supporting IPv6 MUST allow the information field to be at least as large as the minimum link MTU size required for IPv6 (§2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5072-2-2, so no unit is bound to it. ### [`RFC5072-4.1-1`](#rfc5072-4.1-1) A Configure-Request MUST contain exactly one instance of the interface-identifier option (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPv6CPDuplicateIdentifierOptionIsNotAcked`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1101) | unit/verify | revert, verified | | negative | [`TestIPv6CPRequestWithoutIdentifierIsNotAcked`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1033) | unit/verify | revert, verified | | positive | [`TestIPv6CPProposesInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ipv6cp_test.go#L123) | unit/verify | unproven | ### [`RFC5072-4.1-2`](#rfc5072-4.1-2) The interface identifier MUST be unique within the PPP link (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPv6CPNaksCollidingInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L764) | unit/verify | unproven | | positive | [`TestIPv6CPInterfaceIDsDiffer`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L715) | unit/verify | unproven | ### [`RFC5072-4.1-3`](#rfc5072-4.1-3) If the two interface identifiers are different and the received identifier is not zero, it MUST be acknowledged with Configure-Ack (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPv6CPNaksZeroInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L840) | unit/verify | unproven | | positive | [`TestIPv6CPAcksDifferentNonZeroInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L801) | unit/verify | unproven | ### [`RFC5072-4.1-4`](#rfc5072-4.1-4) If the two interface identifiers are equal and non-zero, Configure-Nak MUST be sent specifying a different non-zero interface-identifier (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPv6CPNakOnEqualNonZeroIdentifiers`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1260) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | ### [`RFC5072-4.1-5`](#rfc5072-4.1-5) The suggested interface identifier MUST be different from the interface identifier of the last Configure-Request sent to the peer (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPv6CPSuggestionDiffersFromLocalIdentifier`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1304) | unit/verify | revert, verified | ### [`RFC5072-4.1-6`](#rfc5072-4.1-6) If both interface identifiers are zero, negotiation MUST be terminated by transmitting Configure-Reject with IID=0 (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPv6CPBothZeroIsRejected`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1173) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | ### [`RFC5072-4.1-7`](#rfc5072-4.1-7) On receiving Configure-Nak, a new Configure-Request MUST be sent with the suggested identifier value (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPv6CPNakInvalidSuggestionNotAdopted`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L910) | unit/verify | unproven | | positive | [`TestIPv6CPResendsCRWithNakSuggestedID`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L874) | unit/verify | unproven | ### [`RFC5072-4.1-8`](#rfc5072-4.1-8) If the received interface identifier equals the one sent in the last Configure-Nak, a new interface identifier MUST be chosen (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5072-4.1-8, so no unit is bound to it. ### [`RFC5072-4.1-9`](#rfc5072-4.1-9) The "u" bit of the suggested identifier MUST be set to zero unless a globally unique EUI-48/EUI-64 derived identifier is provided for the peer's exclusive use (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPv6CPSuggestionHasUniversalBitClear`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ipv6cp_test.go#L187) | unit/verify | revert, verified | ### [`RFC5072-4.1-10`](#rfc5072-4.1-10) When uniqueness source is link-layer addresses or serial numbers, the "u" bit MUST be set to zero (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5072-4.1-10, so no unit is bound to it. ### [`RFC5072-4.1-11`](#rfc5072-4.1-11) When a random number is generated, the "u" bit MUST be set to zero (§4.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5072-4.1-11, so no unit is bound to it. ### [`RFC5072-4.1-12`](#rfc5072-4.1-12) A new Configure-Request MUST NOT contain the interface-identifier option if a valid Configure-Reject is received (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestIPv6CPUnknownOptionRejectNotFatal`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L988) | unit/verify | unproven | | positive | [`TestIPv6CPInterfaceIDRejectIsFatal`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L955) | unit/verify | unproven | ### [`RFC5072-4.1-19`](#rfc5072-4.1-19) Having Configure-Naked a Configure-Request that omitted the interface-identifier option once, an implementation MUST NOT Configure-Nak a further Configure-Request that also omits it (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPv6CPMissingOptionIsNakedOnce`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/ppp/ncp_test.go#L1064) | unit/verify | revert, verified | ## Extraction sign-off No extraction sign-off exists for RFC 5072, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5072, so its obligations are stated where they were written. --- ### Page: RFC 5082 - The Generalized TTL Security Mechanism (GTSM) https://ze-software.net/quality/rfc-compliance/rfc5082/ # RFC 5082 - The Generalized TTL Security Mechanism (GTSM) Supported on Linux. Every requirement this repository extracted from RFC 5082, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 75.0% | 3 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 50.0% | 3 of 6 tagged units, 0 escaped and 3 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 10 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 25.0% | 1 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported on Linux | | Enrolment | Enrolled | | Requirements | 10 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 1 | | Nightly-only evidence | 0 | | Test tags | 6 | | Tagged units | 6 | | Recorded audit verdicts | 0 | | Discrimination records | 6 | | Summary | `rfc/short/rfc5082.md` | | Requirement shard | `rfc/requirements/rfc5082.md` | | RFC text | `rfc/full/rfc5082.txt` | ## Enrolment Enrolled: Generalized TTL Security Mechanism (GTSM): four MUST-level requirements. Ze installs the socket options that make the Linux stack perform the check, so conformance is judged on the whole stack. RFC5082-3-1 (transmit TTL 255) is produced by network.setIPTTL (IP_TTL / IPV6_UNICAST_HOPS, internal/core/network/ttl_linux.go), driven for BGP by reactor.parseTTLSettings (`ttl max N` derives OutTTL=255) and reactor.tuneTCPConnectionForSettings, for the listen socket by Reactor.listenTTLForListener plus network.setListenIPTTL, and for BFD by transport.applySocketOptions / applySocketOptionsV6 (IP_TTL=255, IPV6_UNICAST_HOPS=255). RFC5082-3-3 (no decrement) holds because Linux does not decrement locally originated packets; the observable proof is a peer socket carrying IP_MINTTL=255 accepting the connection. RFC5082-3-4 (never drop Trusted or Unknown) holds because network.setIPMinTTL is applied only when a peer configures a floor, so a packet no GTSM session claims meets no TTL gate, and bfd/engine.passesTTLGate admits TTL >= MinTTL. RFC5082-3-2 (the same TTL 255 rule for the related ICMP error messages) is produced by no layer: Linux emits ICMP errors from the IP stack with net.ipv4.ip_default_ttl, not with the socket IP_TTL, and no socket option gates the TTL of an inbound ICMP error. RFC5082-3-1, RFC5082-3-3 and RFC5082-3-4 are proven in both polarities by internal/core/network/ttl_gtsm_linux_test.go, each with a verified discrimination record in rfc/discrimination/rfc5082.json. RFC5082-3-2 carries no test: the behavior does not exist to test. ## What the public ledger says **Status:** Supported on Linux **What the ledger says is covered** 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`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_connection.go), [`internal/core/network/ttl_linux.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/transport/udp_linux.go), `passesTTLGate` in [`internal/component/bfd/engine/loop.go`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/loop.go)). VRRP sends and requires TTL 255 ([`internal/plugins/vrrp/packet/validate.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate.go)). Requirements bound per requirement in [`rfc/requirements/rfc5082.md`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc5082.md). **What the ledger says remains** 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`](https://github.com/ze-software/ze/blob/main/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 [`rfc/extraction/rfc5082.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/rfc5082.json). That absent feature is an implementation gap a later scope decision can revisit, and not a conformance gap. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 3 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 1 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (3):** [`RFC5082-3-1`](#rfc5082-3-1), [`RFC5082-3-3`](#rfc5082-3-3), [`RFC5082-3-4`](#rfc5082-3-4) **No test and no annotation (1):** [`RFC5082-3-2`](#rfc5082-3-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5082-3-1` | The TTL field in all IP packets used for transmission of messages associated with GTSM-enabled protocol sessions MUST be set to 255 (§3) | MUST | 3 - GTSM Procedure | **positive:** `unit/verify` [`TestGTSMDialerSetsOutgoingTTLTo255`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L40). **negative:** `unit/verify` [`TestGTSMDialerWithoutOutTTLLeavesTheDefault`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L59) | | `RFC5082-3-2` | The TTL 255 transmit and verify rule also applies to the related ICMP error handling messages of a GTSM-enabled session (§3, restated §6.1) | MUST | 3 - GTSM Procedure | **positive:** no positive test. **negative:** no negative test | | `RFC5082-3-3` | The TTL of GTSM-enabled sessions MUST NOT be decremented by the forwarding plane (§3) | MUST NOT | 3 - GTSM Procedure | **positive:** `unit/verify` [`TestGTSMTransmittedTTLArrivesUndecremented`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L83). **negative:** `unit/verify` [`TestGTSMTransmittedTTLReportsTheValueSet`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L111) | | `RFC5082-3-4` | Implementations MUST NOT drop, as part of GTSM processing, packets classified as Trusted or Unknown (§3) | MUST NOT | 3 - GTSM Procedure | **positive:** `unit/verify` [`TestGTSMFloorDeliversATrustedPacket`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L138). **negative:** `unit/verify` [`TestGTSMNoFloorDeliversAnUnknownPacket`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L165) | | `RFC5082-3-5` | GTSM added to a protocol as an additional feature SHOULD NOT be enabled by default (§3) | SHOULD NOT | 3 - GTSM Procedure | **positive:** no positive test. **negative:** no negative test | | `RFC5082-3-6` | Implementations SHOULD ensure that packets classified as Dangerous do not compete for resources with packets classified as Trusted or Unknown (§3) | SHOULD | 3 - GTSM Procedure | **positive:** no positive test. **negative:** no negative test | | `RFC5082-3-7` | Implementations MAY drop packets classified as Dangerous (§3) | MAY | 3 - GTSM Procedure | **positive:** no positive test. **negative:** no negative test | | `RFC5082-3-8` | A protocol peer MAY suggest the use of GTSM when the protocol defines a built-in dynamic capability negotiation for it, provided GTSM is enabled only if both peers agree (§3) | MAY | 3 - GTSM Procedure | **positive:** no positive test. **negative:** no negative test | | `RFC5082-2-1` | Use of GTSM is OPTIONAL and can be configured on a per-peer (group) basis (§2) | OPTIONAL | 2 - Assumptions Underlying GTSM | **positive:** no positive test. **negative:** no negative test | | `RFC5082-5.4-1` | GTSM-protected protocols are highly RECOMMENDED to avoid fragmentation and reassembly by manual MTU tuning or Path MTU Discovery (§5.4) | RECOMMENDED | 5.4 - Fragmentation Considerations | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5082-3-2`](#rfc5082-3-2) The TTL 255 transmit and verify rule also applies to the related ICMP error handling messages of a GTSM-enabled session (§3, restated §6.1) | no test | no test carries this requirement id | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5082-3-1`](#rfc5082-3-1) The TTL field in all IP packets used for transmission of messages associated with GTSM-enabled protocol sessions MUST be set to 255 (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGTSMDialerWithoutOutTTLLeavesTheDefault`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L59) | unit/verify | revert, unit-changed (the tagged unit's behavior changed since the red was observed) | | positive | [`TestGTSMDialerSetsOutgoingTTLTo255`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L40) | unit/verify | revert, unit-changed (the tagged unit's behavior changed since the red was observed) | ### [`RFC5082-3-2`](#rfc5082-3-2) The TTL 255 transmit and verify rule also applies to the related ICMP error handling messages of a GTSM-enabled session (§3, restated §6.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5082-3-2, so no unit is bound to it. ### [`RFC5082-3-3`](#rfc5082-3-3) The TTL of GTSM-enabled sessions MUST NOT be decremented by the forwarding plane (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGTSMTransmittedTTLReportsTheValueSet`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L111) | unit/verify | revert, verified | | positive | [`TestGTSMTransmittedTTLArrivesUndecremented`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L83) | unit/verify | revert, verified | ### [`RFC5082-3-4`](#rfc5082-3-4) Implementations MUST NOT drop, as part of GTSM processing, packets classified as Trusted or Unknown (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGTSMNoFloorDeliversAnUnknownPacket`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L165) | unit/verify | revert, unit-changed (the tagged unit's behavior changed since the red was observed) | | positive | [`TestGTSMFloorDeliversATrustedPacket`](https://github.com/ze-software/ze/blob/main/internal/core/network/ttl_gtsm_linux_test.go#L138) | unit/verify | revert, verified | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase, rfc5082 | | Signed off | 2026-09-01 | | Register | rfc2119 | | Source | rfc/full/rfc5082.txt | | Source fingerprint | b863c08d35ee141c | | Record | rfc/extraction/rfc5082.json | | Mapped sentences | 3 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of This Memo, Abstract and Table of Contents. The Abstract says the document generalizes the use of a packet's TTL or Hop Limit to verify that the packet came from an adjacent node, and that it obsoletes RFC 3682. It directs no implementation. | | `1` | Introduction | 0 | walked | Introduction. States what GTSM protects against, the assumption that most protocol peerings are between adjacent routers, and that GTSM is not a substitute for authentication. It also fixes the term 'TTL' to mean both the IPv4 TTL and the IPv6 Hop Limit, which rfc/short/rfc5082.md carries in its TTL Semantics table. The section closes with the RFC 2119 key-words paragraph, which binds nobody and which the site derivation excludes. No site. | | `2` | Assumptions Underlying GTSM | 0 | walked | Assumptions Underlying GTSM. Five numbered assumptions, all indicative. Assumption 3, 'Use of GTSM is OPTIONAL, and can be configured on a per-peer (group) basis', is the only one carrying an RFC 2119 keyword; OPTIONAL is advisory, so the derivation raises no MUST-level site for it and the summary records it as RFC5082-2-1. The closing paragraphs state that the document does not prescribe what a router does with non-matching packets and does not choose a resource-separation mechanism. | | `2.1` | GTSM Negotiation | 1 | walked | GTSM Negotiation. One site, 2.1:1, excluded below as feature-out-of-scope: it is conditional on dynamic GTSM negotiation, which this document neither assumes nor defines. The section's other sentences are indicative: that GTSM is manually configured between peers, and that a new protocol designed with built-in GTSM support is recommended to always run the send and validate procedures. | | `2.2` | Assumptions on Attack Sophistication | 0 | walked | Assumptions on Attack Sophistication. States the attacker model: control traffic that looks valid, every router on the path decrementing TTL properly, ingress filtering applied before the scarce resource, and four alternative assumptions about tunnels. It closes with the sentence the whole mechanism rests on, that a receiver can set TTL 255 on transmit and reject packets from configured peers whose inbound TTL is not 255. All indicative. No site. | | `3` | GTSM Procedure | 3 | walked | GTSM Procedure. The document's only normative section. Three sites, mapped below to RFC5082-3-1, RFC5082-3-3 and RFC5082-3-4. The sentence that extends the transmit rule to the related ICMP error messages, 'This also applies to the related ICMP error handling messages', carries no RFC 2119 keyword, so the site scan cannot see it; Section 6.1 restates it as 'This specification mandates setting and verifying TTL=255 of those as well as the main protocol packets', and Appendix B lists it as a change since RFC 3682. It is declared unsourced here as RFC5082-3-2. The section's advisory sentences also carry no MUST-level keyword and are the remaining unsourced ids: the SHOULD NOT against enabling GTSM by default when it is added to an existing protocol (RFC5082-3-5), the SHOULD that Dangerous packets not compete for resources with Trusted or Unknown ones (RFC5082-3-6), the MAY to drop Dangerous packets (RFC5082-3-7), and the MAY for a peer to suggest GTSM where the protocol defines a built-in dynamic capability negotiation (RFC5082-3-8). The three trustworthiness categories, Unknown, Trusted and Dangerous, are definitions and rfc/short/rfc5082.md carries them in its TTL Semantics table. | | `4` | Acknowledgments | 0 | skipped (acknowledgements) | Acknowledgments. | | `5` | Security Considerations, opening | 0 | walked | Security Considerations, opening. States that GTSM protects single-hop protocol sessions except where the peer is compromised, and that it does not protect against on-the-wire attacks. No site. | | `5.1` | TTL (Hop Limit) Spoofing | 0 | walked | TTL (Hop Limit) Spoofing. Explains why 255 is the value chosen: the TTL is decremented once per router, so a value of 255 cannot be engineered from a location that is not directly connected. Indicative throughout. No site. | | `5.2` | Tunneled Packets | 0 | walked | Tunneled Packets. States that a tunnel that is not integrity-protected is the exception to the observation that TTL 255 is hard to spoof, and describes what GTSM still buys over a tunnel. Indicative. No site. | | `5.2.1` | IP Tunneled over IP | 2 | walked | IP Tunneled over IP. Two sites, 5.2.1:1 and 5.2.1:2, both excluded below as cross-document: each is a block quotation of another RFC's decapsulator rule, RFC 2003 and RFC 2784 respectively, cited by number in the sentence that introduces it. The section's own text is an analysis of what the inner TTL can be at the protocol peer in each of the two tunnel topologies. It binds no GTSM implementation. | | `5.2.2` | IP Tunneled over MPLS | 0 | walked | IP Tunneled over MPLS. Analyses TTL handling under the RFC 3443 Uniform, Pipe and Short Pipe models, and concludes that a GTSM check is possible over Pipe model LSPs and not over Uniform model LSPs of more than one hop. Every quoted rule is RFC 3443's and none carries an RFC 2119 keyword here. No site. | | `5.3` | Onlink Attackers | 0 | walked | Onlink Attackers. Restates Section 2.2: an attacker on a directly connected interface can disturb a GTSM-protected session unless ingress filtering is applied, so such interfaces have to be trusted. Indicative. No site. | | `5.4` | Fragmentation Considerations | 0 | walked | Fragmentation Considerations. Explains that a non-initial fragment carries no Layer 4 information, so it classifies as Unknown, and that a reassembled packet inherits that. Its one RFC 2119 keyword is the advisory 'it is highly RECOMMENDED for GTSM-protected protocols to avoid fragmentation and reassembly', which is not MUST-level, so the derivation raises no site; the summary records it as RFC5082-5.4-1. | | `5.5` | Multi-Hop Protocol Sessions | 0 | walked | Multi-Hop Protocol Sessions. States that the document describes only the single-hop case, and that the protection multi-hop GTSM offers is difficult to quantify. No obligation. No site. | | `6` | Applicability Statement | 0 | walked | Applicability Statement. Limits GTSM to environments with inherently limited topologies and to directly connected peers, and states that GTSM does not protect against an attacker as close as the legitimate peer. Its modals are lowercase 'should'. No site. | | `6.1` | Backwards Compatibility | 0 | walked | Backwards Compatibility. Records what changed against RFC 3682: this specification mandates setting and verifying TTL=255 on related ICMP error messages as well as on the main protocol packets. That is the restatement of the Section 3 sentence declared unsourced above as RFC5082-3-2, so no id is allocated here. The rest weighs the interoperability cost against RFC 3682 senders that emit related messages with TTL 64. No site. | | `7` | References, the container heading | 0 | skipped (references) | References, the container heading. | | `7.1` | Normative References | 0 | skipped (references) | Normative References. RFC 791, RFC 2003, RFC 2119, RFC 2461, RFC 2784, RFC 3392, RFC 3443, RFC 4213, RFC 4271 and RFC 4301. | | `7.2` | Informative References, and the BITW mailing-list thread | 0 | skipped (references) | Informative References, and the BITW mailing-list thread. | | `A` | Appendix A, Multi-Hop GTSM | 0 | skipped (appendix-non-normative) | Appendix A, Multi-Hop GTSM. Its first line reads 'NOTE: This is a non-normative part of the specification.' It sketches a receiver that checks the TTL is within a configured number of hops from 255 and states that such deployment is not specified in this document. | | `B` | Appendix B, Changes Since RFC 3682 | 0 | skipped (appendix-non-normative) | Appendix B, Changes Since RFC 3682. A change list. Its fourth entry names the related-messages rule that Section 3 states and that RFC5082-3-2 carries. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `2.1:1` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | The feature is OPTIONAL and ze decided not to offer it. RFC 5082 Section 2.1: 'This document assumes that, when used with existing protocols, GTSM will be manually configured between protocol peers. That is, no automatic GTSM capability negotiation, such as is provided by RFC 3392 [RFC3392], is assumed or defined.' The same section adds that 'this specification does not offer a generic GTSM capability negotiation mechanism'. This obligation is conditional on running such a negotiation, and ze runs none: parseTTLSettings (internal/component/bgp/reactor/config.go) is the only producer of a peer's GTSM TTL values and it reads the static `connection { ttl }` configuration map alone, and tuneTCPConnectionForSettings (internal/component/bgp/reactor/session_connection.go) installs IP_TTL and IP_MINTTL on the TCP socket at connectionEstablished time, before any OPEN is exchanged, so no message of ze's could carry a GTSM negotiation. The BFD and VRRP paths are the same shape: transport.applySocketOptions (internal/component/bfd/transport/udp_linux.go) sets IP_TTL=255 unconditionally at socket setup, and gtsmTTL (internal/plugins/vrrp/packet/validate.go) is a constant. There is no GTSM capability code and no GTSM negotiation message anywhere in ze. This is a SCOPE DECISION and not outstanding work. The absent feature is recorded as an implementation gap in docs/features/rfc-status.md, which a later scope decision can revisit; it is not a conformance gap. | If, however, dynamic negotiation of GTSM support is necessary, protocol messages used for such negotiation MUST be authenticated using other security mechanisms to prevent DoS attacks. | | `5.2.1:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 2003, which the sentence that introduces the block quotation cites by number: 'For IP-in-IP tunnels, RFC 2003 specifies the following decapsulator behavior'. It binds an IP-in-IP decapsulator to discard an inner datagram whose TTL is 0 after decapsulation. RFC 5082 quotes it to show what the inner TTL can be at the protocol peer, and states no obligation of its own here. | If, after decapsulation, the inner datagram has TTL = 0, the decapsulator MUST discard the datagram. | | `5.2.1:2` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 2784, which the sentence that introduces the block quotation cites by number: 'And similarly, for GRE tunnels, RFC 2784 specifies the following decapsulator behavior'. It binds a GRE tunnel endpoint to forward on the inner destination address and to decrement the payload TTL. RFC 5082 quotes it for the same reason as site 5.2.1:1 and states no obligation of its own here. | When a tunnel endpoint decapsulates a GRE packet which has an IPv4 packet as the payload, the destination address in the IPv4 payload packet header MUST be used to forward the packet and the TTL of the payload packet MUST be decremented. | ## Superseded No document obsoletes RFC 5082, so its obligations are stated where they were written. --- ### Page: RFC 5120 - M-ISIS: Multi Topology (MT) Routing in Intermediate System to Intermediate Systems (IS-ISs) https://ze-software.net/quality/rfc-compliance/rfc5120/ # RFC 5120 - M-ISIS: Multi Topology (MT) Routing in Intermediate System to Intermediate Systems (IS-ISs) Unsupported. Every requirement this repository extracted from RFC 5120, the tests bound to it, and what a reader has verified about them. This summary is not enrolled. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 0 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 0 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 0 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 0 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | | Audit verdicts | 0 | of 0 gated MUSTs judged | 0 weak, wrong or unimplemented, 0 no longer current. Each is named below under its own requirement id | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | MUSTs declared | 0 | of 0 this summary declares | MUST-level requirements this summary DECLARES. The gate holds none of them, because this RFC is not enrolled (blocked), so every share below reads what the summary records rather than what the gate enforces | | Out of scope | 0 | of 0 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 0 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 0 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 0 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | No card above is a share of a population, so there is nothing to add up. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | MUSTs declared | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | ok | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Unsupported | | Enrolment | Not enrolled (blocked) | | Requirements | 0 | | Gated MUST-level | 0 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5120.md` | | Requirement shard | no requirement declared, so no shard is generated | | RFC text | this checkout does not carry the RFC's own text | ## Enrolment Not enrolled (blocked, something outside the summary stops the extraction, and it is named in the reason): IS-IS multi-topology routing. Written 2026-09-01 so the public row is declared by a summary. It is not enrolled because there is no source text at rfc/full/rfc5120.txt: fetch https://www.rfc-editor.org/rfc/rfc5120.txt, then extract. Ze runs IS-IS single-topology dual-stack only and the row claims Unsupported, so no obligation here is gated. ## What the public ledger says **Status:** Unsupported **What the ledger says is covered:** - None - IS-IS runs single-topology dual-stack only. **What the ledger says remains:** Non-congruent IPv4/IPv6 topologies are out of scope; multi-topology TLVs are not implemented. ## Coverage RFC 5120 declares no MUST-level requirement, so the gate counts nothing here. ## Requirements RFC 5120 declares no requirement, so this summary generates no shard. ## Gaps and untested MUSTs RFC 5120 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state RFC 5120 carries no gated, tagged or audited requirement, so there is no proof state to state. ## Extraction sign-off No extraction sign-off exists for RFC 5120, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5120, so its obligations are stated where they were written. --- ### Page: RFC 5176 - Dynamic Authorization Extensions to Remote Authentication Dial In User Service (RADIUS) https://ze-software.net/quality/rfc-compliance/rfc5176/ # RFC 5176 - Dynamic Authorization Extensions to Remote Authentication Dial In User Service (RADIUS) Supported for subscriber access. Every requirement this repository extracted from RFC 5176, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 95.5% | 21 of 22 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 4.5% | 1 of 22 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 22 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 22 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 50.0% | 26 of 52 tagged units, 0 escaped and 4 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 22 | of 23 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 22 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 22 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 22 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 22 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 22 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported for subscriber access | | Enrolment | Enrolled | | Requirements | 23 | | Gated MUST-level | 22 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 52 | | Tagged units | 52 | | Recorded audit verdicts | 0 | | Discrimination records | 30 | | Summary | `rfc/short/rfc5176.md` | | Requirement shard | `rfc/requirements/rfc5176.md` | | RFC text | `rfc/full/rfc5176.txt` | ## Enrolment Enrolled: RADIUS Dynamic Authorization Extensions (CoA/Disconnect): five MUST-level requirements, all met by ze's Dynamic Authorization Server (internal/component/l2tp/plugins/authradius/coa.go, wired at register.go). 3.5-1 (verify Request Authenticator before processing), 3.5-2 (silently discard invalid authenticators), 3.3-1 (require at least one session-identification attribute), and 3.5-4 (Request Authenticator = MD5 over the RFC 2865 fields) each carry positive+negative tags on the CoA listener and packet tests. 3.5-3 (Response Authenticator per RFC 2865) is {single-polarity: positive}: ze only emits responses, so there is no inbound Response Authenticator to reject. ## What the public ledger says **Status:** Supported for subscriber access **What the ledger says is covered** 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 [`rfc/requirements/rfc5176.md`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc5176.md), and the checklist is bounded by [`rfc/extraction/rfc5176.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/rfc5176.json). <!-- source: [`internal/component/l2tp/plugins/authradius/coa.go`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa.go) -- handlePacket/handleCoA/handleDisconnect/sendResponse --> **What the ledger says remains** 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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 21 | one part of the gated population | | Annotated instead of tested | 1 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **22** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (21):** [`RFC5176-3.5-1`](#rfc5176-3.5-1), [`RFC5176-3.5-2`](#rfc5176-3.5-2), [`RFC5176-3.3-1`](#rfc5176-3.3-1), [`RFC5176-3.5-4`](#rfc5176-3.5-4), [`RFC5176-2.3-1`](#rfc5176-2.3-1), [`RFC5176-2.3-2`](#rfc5176-2.3-2), [`RFC5176-2.3-3`](#rfc5176-2.3-3), [`RFC5176-2.3-4`](#rfc5176-2.3-4), [`RFC5176-2.3-5`](#rfc5176-2.3-5), [`RFC5176-2.3-6`](#rfc5176-2.3-6), [`RFC5176-2.3-7`](#rfc5176-2.3-7), [`RFC5176-3.1-1`](#rfc5176-3.1-1), [`RFC5176-3.2-1`](#rfc5176-3.2-1), [`RFC5176-3.3-2`](#rfc5176-3.3-2), [`RFC5176-3.4-1`](#rfc5176-3.4-1), [`RFC5176-3.4-2`](#rfc5176-3.4-2), [`RFC5176-3.4-3`](#rfc5176-3.4-3), [`RFC5176-3.5-5`](#rfc5176-3.5-5), [`RFC5176-3.6-1`](#rfc5176-3.6-1), [`RFC5176-6.1-1`](#rfc5176-6.1-1), [`RFC5176-6.3-1`](#rfc5176-6.3-1) **Annotated instead of tested (1):** [`RFC5176-3.5-3`](#rfc5176-3.5-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5176-3.5-1` | A CoA-Request or Disconnect-Request MUST have its Request Authenticator verified before any attribute of it is acted on (anchored §3.5; the sentence it enforces is at §2.3, "The Authenticator field MUST be calculated in the same way as is specified for an Accounting-Request in [RFC2866]", and a value nobody checks authenticates nothing) | MUST | 3.5 | **positive:** `unit/verify` [`TestCoAListenerUnknownSession`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L337). **negative:** `unit/verify` [`TestCoAListenerInvalidAuth`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L250) | | `RFC5176-3.5-2` | A CoA-Request or Disconnect-Request whose Request Authenticator does not match MUST be discarded with no response emitted (anchored §3.5; a sender that cannot compute the Authenticator holds no shared secret, so §6.1, "A Dynamic Authorization Server MUST silently discard Disconnect-Request or CoA-Request packets from untrusted sources", covers it) | MUST | 3.5 | **positive:** `unit/verify` [`TestCoAListenerInvalidAuth`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L252). **negative:** `unit/verify` [`TestCoAListenerUnknownSession`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L339) | | `RFC5176-3.3-1` | The combination of NAS and session identification attributes in a CoA-Request or Disconnect-Request MUST match at least one session for the request to succeed, and a request matching none MUST be answered with a CoA-NAK or a Disconnect-NAK (anchored §3.3; stated at §3) | MUST | 3.3 | **positive:** `unit/verify` [`TestDisconnectReplayReturnsCachedResponse`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L402). **negative:** `unit/verify` [`TestRFC5176NoSessionIdNotActedOn`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_coa_test.go#L25) | | `RFC5176-3.5-3` | Response Authenticator MUST be computed per RFC 2865 Section 3 (§3.5) | MUST | 3.5 | **positive:** `unit/verify` [`TestRFC5176ResponseAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_coa_test.go#L66). **negative:** no negative test. **{single-polarity}:** the NAS only emits CoA/Disconnect responses and never receives one, so there is no inbound Response Authenticator to reject; correctness is proven by verifying the emitted authenticator against radius.ResponseAuthenticator (internal/component/radius/packet.go:145) | | `RFC5176-3.5-4` | Request Authenticator MUST be computed as MD5(Code + Identifier + Length + 16-zero-octets + Attributes + Secret) (§3.5) | MUST | 3.5 | **positive:** `unit/verify` [`TestRFC5176CoARequestAuthenticatorMatchesTheFormula`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_response_auth_test.go#L183). **positive:** `unit/verify` [`TestVerifyCoARequestAuth`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L352). **negative:** `unit/verify` [`TestRFC5176CoARequestAuthenticatorCoversEveryNamedField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_response_auth_test.go#L218). **negative:** `unit/verify` [`TestVerifyCoARequestAuth`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L358) | | `RFC5176-2.3-1` | A packet received with an invalid Code field MUST be silently discarded (§2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestRFC5176InvalidCodeDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L133). **negative:** `unit/verify` [`TestRFC5176InvalidCodeDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L136) | | `RFC5176-2.3-2` | A Dynamic Authorization Server MUST detect a duplicate request carrying the same source address, Identifier and Request Authenticator within a short span of time, and MUST answer it with the response the first copy earned (§2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestRFC5176DuplicateRequestAnsweredFromCache`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L155). **negative:** `unit/verify` [`TestRFC5176DuplicateRequestAnsweredFromCache`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L158) | | `RFC5176-2.3-3` | Octets outside the range of the Length field MUST be treated as padding and ignored on reception, and a packet shorter than its Length field indicates MUST be silently discarded (§2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestRFC5176LengthFieldGovernsTheOctetsRead`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L192). **negative:** `unit/verify` [`TestRFC5176LengthFieldGovernsTheOctetsRead`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L194) | | `RFC5176-2.3-4` | The Dynamic Authorization Server MUST use the source IP address of the RADIUS UDP packet to decide which shared secret to use (§2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestRFC5176SharedSecretChosenBySourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L216). **negative:** `unit/verify` [`TestRFC5176SharedSecretChosenBySourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L219) | | `RFC5176-2.3-5` | Every attribute of a CoA-Request or Disconnect-Request MUST be treated as mandatory, so a request carrying an attribute the NAS does not support MUST be answered with a CoA-NAK or a Disconnect-NAK; a Disconnect-Request MUST carry only NAS and session identification attributes (§2.3, restated at §3) | MUST | 2.3 | **positive:** `unit/verify` [`TestRFC5176UnsupportedAttributeNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L255). **negative:** `unit/verify` [`TestRFC5176UnsupportedAttributeNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L259) | | `RFC5176-2.3-6` | A CoA-Request whose authorization changes cannot all be carried out MUST be answered with a CoA-NAK and MUST leave the matching session unchanged, and a Disconnect-Request that cannot terminate the matching session MUST be answered with a Disconnect-NAK (§2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestRFC5176ChangeThatCannotBeCarriedOutIsNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L293). **negative:** `unit/verify` [`TestRFC5176ChangeThatCannotBeCarriedOutIsNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L295) | | `RFC5176-2.3-7` | When the identification attributes match more than one session, a NAS that supports multi-session requests MUST apply the request to all of them, and a NAS that does not MUST answer with a CoA-NAK or a Disconnect-NAK (§2.3, with the apply-to-all branch stated at §3) | MUST | 2.3 | **positive:** `unit/verify` [`TestRFC5176MultipleMatchingSessionsNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L325). **negative:** `unit/verify` [`TestRFC5176MultipleMatchingSessionsNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L328) | | `RFC5176-3.1-1` | The Dynamic Authorization Server MUST include the request's Proxy-State attributes in its response, unmodified, in the order they arrived, and treated as opaque data (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRFC5176ProxyStateReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L363). **negative:** `unit/verify` [`TestRFC5176ProxyStateReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L365) | | `RFC5176-3.2-1` | A NAS MUST answer a CoA-Request carrying a Service-Type Attribute whose value it does not support, "Authorize Only" included, with a CoA-NAK, and MUST NOT answer it with a CoA-ACK (§3.2, with the unsupported-value branch stated at §2.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestRFC5176ServiceTypeNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L397). **negative:** `unit/verify` [`TestRFC5176ServiceTypeNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L400) | | `RFC5176-3.3-2` | The Dynamic Authorization Server MUST NOT interpret the State Attribute locally, and MUST send it unmodified in the ACK or NAK it returns (§3.3) | MUST | 3.3 | **positive:** `unit/verify` [`TestRFC5176StateReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L427). **negative:** `unit/verify` [`TestRFC5176StateReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L429) | | `RFC5176-3.4-1` | When the HMAC-MD5 message integrity check of a CoA-Request or Disconnect-Request is calculated, the Request Authenticator field and the Message-Authenticator Attribute MUST each be considered to be sixteen octets of zero (§3.4) | MUST | 3.4 | **positive:** `unit/verify` [`authradius/TestRFC5176MessageAuthenticatorZeroesBothFields`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L456). **positive:** `unit/verify` [`radius/TestRFC5176MessageAuthenticatorZeroesBothFields`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc5176_message_authenticator_test.go#L126). **negative:** `unit/verify` [`TestRFC5176MessageAuthenticatorRefusesEveryOtherStream`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc5176_message_authenticator_test.go#L148). **negative:** `unit/verify` [`authradius/TestRFC5176MessageAuthenticatorZeroesBothFields`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L460) | | `RFC5176-3.4-2` | The Message-Authenticator Attribute is calculated and inserted in the packet before the Request Authenticator is calculated, so the Request Authenticator MUST cover the Message-Authenticator value as sent (§3.4) | MUST | 3.4 | **positive:** `unit/verify` [`TestRFC5176RequestAuthenticatorCoversTheSignedMessageAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc5176_message_authenticator_test.go#L218). **negative:** `unit/verify` [`TestRFC5176RequestAuthenticatorRefusesTheInvertedOrder`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc5176_message_authenticator_test.go#L239) | | `RFC5176-3.4-3` | A Dynamic Authorization Server receiving a CoA-Request or Disconnect-Request with a Message-Authenticator Attribute present MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent (§3.4) | MUST | 3.4 | **positive:** `unit/verify` [`TestRFC5176ListenerAcceptsConformantMessageAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_message_authenticator_test.go#L26). **negative:** `unit/verify` [`TestRFC5176ListenerDiscardsWrongMessageAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_message_authenticator_test.go#L52). **negative:** `unit/verify` [`TestRFC5176WrongMessageAuthenticatorDiscardedWhenNotRequired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_message_authenticator_optional_test.go#L71) | | `RFC5176-3.4-4` | The Message-Authenticator Attribute MAY be used to authenticate and integrity-protect CoA-Request, CoA-ACK, CoA-NAK, Disconnect-Request, Disconnect-ACK and Disconnect-NAK packets in order to prevent spoofing, so a request that carries none is answered rather than discarded; the `require-message-authenticator` leaf turns its absence into a discard for an operator who wants the Blast-RADIUS mitigation (§3.4) | MAY | 3.4 | **positive:** `unit/verify` [`TestRFC5176MessageAuthenticatorAbsentIsAcceptedByDefault`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_message_authenticator_optional_test.go#L26). **negative:** `unit/verify` [`TestCoAListenerMissingMessageAuthenticatorDroppedWhenRequired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L307) | | `RFC5176-3.5-5` | An Error-Cause value in the 200-299 range MUST NOT be sent within a CoA-NAK or Disconnect-NAK, a value in the 400-599 range MUST NOT be sent within a CoA-ACK or Disconnect-ACK, 202 MUST NOT be sent by an implementation of this specification, 502 MUST NOT be sent by a NAS, 201 MUST NOT leave a packet other than a Disconnect-ACK and 504 MUST NOT leave a packet other than a Disconnect-NAK (§3.5) | MUST | 3.5 | **positive:** `unit/verify` [`TestRFC5176ErrorCausePlacement`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L496). **negative:** `unit/verify` [`TestRFC5176ErrorCausePlacement`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L499) | | `RFC5176-3.6-1` | NAS and session identification attributes MUST NOT be used for a purpose other than identification, and the same Vendor-Specific Attribute MUST NOT serve identification and authorization change at the same time (§3.6) | MUST | 3.6 | **positive:** `unit/verify` [`TestRFC5176IdentificationAttributesIdentifyOnly`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L604). **negative:** `unit/verify` [`TestRFC5176IdentificationAttributesIdentifyOnly`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L607) | | `RFC5176-6.1-1` | A Dynamic Authorization Server MUST silently discard Disconnect-Request or CoA-Request packets from untrusted sources, so a source that is not a configured Dynamic Authorization Client is refused; an EMPTY allow list means no configured server resolved and refuses every source rather than accepting all of them (§6.1) | MUST | 6.1 | **positive:** `unit/verify` [`TestCoASourceFilterDiscardsWhenNoServerResolved`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_coa_test.go#L119). **positive:** `unit/verify` [`TestRFC5176UntrustedSourceDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L652). **negative:** `unit/verify` [`TestCoASourceFilterAnswersAConfiguredClient`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_coa_test.go#L173). **negative:** `unit/verify` [`TestRFC5176UntrustedSourceDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L655) | | `RFC5176-6.3-1` | When an Event-Timestamp Attribute is present the Dynamic Authorization Server MUST check that it is current within an acceptable time window, and MUST silently discard the packet when it is not; that window MUST be the one used for duplicate detection (§6.3) | MUST | 6.3 | **positive:** `unit/verify` [`TestRFC5176StaleEventTimestampDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L710). **negative:** `unit/verify` [`TestRFC5176StaleEventTimestampDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L714) | ## Gaps and untested MUSTs RFC 5176 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5176-3.5-1`](#rfc5176-3.5-1) A CoA-Request or Disconnect-Request MUST have its Request Authenticator verified before any attribute of it is acted on (anchored §3.5; the sentence it enforces is at §2.3, "The Authenticator field MUST be calculated in the same way as is specified for an Accounting-Request in [RFC2866]", and a value nobody checks authenticates nothing) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCoAListenerInvalidAuth`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L250) | unit/verify | unproven | | positive | [`TestCoAListenerUnknownSession`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L337) | unit/verify | unproven | ### [`RFC5176-3.5-2`](#rfc5176-3.5-2) A CoA-Request or Disconnect-Request whose Request Authenticator does not match MUST be discarded with no response emitted (anchored §3.5; a sender that cannot compute the Authenticator holds no shared secret, so §6.1, "A Dynamic Authorization Server MUST silently discard Disconnect-Request or CoA-Request packets from untrusted sources", covers it) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCoAListenerUnknownSession`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L339) | unit/verify | unproven | | positive | [`TestCoAListenerInvalidAuth`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L252) | unit/verify | unproven | ### [`RFC5176-3.3-1`](#rfc5176-3.3-1) The combination of NAS and session identification attributes in a CoA-Request or Disconnect-Request MUST match at least one session for the request to succeed, and a request matching none MUST be answered with a CoA-NAK or a Disconnect-NAK (anchored §3.3; stated at §3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176NoSessionIdNotActedOn`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_coa_test.go#L25) | unit/verify | unproven | | positive | [`TestDisconnectReplayReturnsCachedResponse`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L402) | unit/verify | unproven | ### [`RFC5176-3.5-3`](#rfc5176-3.5-3) Response Authenticator MUST be computed per RFC 2865 Section 3 (§3.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5176ResponseAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_coa_test.go#L66) | unit/verify | unproven | ### [`RFC5176-3.5-4`](#rfc5176-3.5-4) Request Authenticator MUST be computed as MD5(Code + Identifier + Length + 16-zero-octets + Attributes + Secret) (§3.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestVerifyCoARequestAuth`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L358) | unit/verify | unproven | | negative | [`TestRFC5176CoARequestAuthenticatorCoversEveryNamedField`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_response_auth_test.go#L218) | unit/verify | unproven | | positive | [`TestVerifyCoARequestAuth`](https://github.com/ze-software/ze/blob/main/internal/component/radius/packet_test.go#L352) | unit/verify | unproven | | positive | [`TestRFC5176CoARequestAuthenticatorMatchesTheFormula`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc2865_response_auth_test.go#L183) | unit/verify | unproven | ### [`RFC5176-2.3-1`](#rfc5176-2.3-1) A packet received with an invalid Code field MUST be silently discarded (§2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176InvalidCodeDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L136) | unit/verify | revert, verified | | positive | [`TestRFC5176InvalidCodeDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L133) | unit/verify | revert, verified | ### [`RFC5176-2.3-2`](#rfc5176-2.3-2) A Dynamic Authorization Server MUST detect a duplicate request carrying the same source address, Identifier and Request Authenticator within a short span of time, and MUST answer it with the response the first copy earned (§2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176DuplicateRequestAnsweredFromCache`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L158) | unit/verify | revert, verified | | positive | [`TestRFC5176DuplicateRequestAnsweredFromCache`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L155) | unit/verify | revert, verified | ### [`RFC5176-2.3-3`](#rfc5176-2.3-3) Octets outside the range of the Length field MUST be treated as padding and ignored on reception, and a packet shorter than its Length field indicates MUST be silently discarded (§2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176LengthFieldGovernsTheOctetsRead`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L194) | unit/verify | revert, verified | | positive | [`TestRFC5176LengthFieldGovernsTheOctetsRead`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L192) | unit/verify | revert, verified | ### [`RFC5176-2.3-4`](#rfc5176-2.3-4) The Dynamic Authorization Server MUST use the source IP address of the RADIUS UDP packet to decide which shared secret to use (§2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176SharedSecretChosenBySourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L219) | unit/verify | revert, verified | | positive | [`TestRFC5176SharedSecretChosenBySourceAddress`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L216) | unit/verify | revert, verified | ### [`RFC5176-2.3-5`](#rfc5176-2.3-5) Every attribute of a CoA-Request or Disconnect-Request MUST be treated as mandatory, so a request carrying an attribute the NAS does not support MUST be answered with a CoA-NAK or a Disconnect-NAK; a Disconnect-Request MUST carry only NAS and session identification attributes (§2.3, restated at §3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176UnsupportedAttributeNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L259) | unit/verify | revert, verified | | positive | [`TestRFC5176UnsupportedAttributeNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L255) | unit/verify | revert, verified | ### [`RFC5176-2.3-6`](#rfc5176-2.3-6) A CoA-Request whose authorization changes cannot all be carried out MUST be answered with a CoA-NAK and MUST leave the matching session unchanged, and a Disconnect-Request that cannot terminate the matching session MUST be answered with a Disconnect-NAK (§2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176ChangeThatCannotBeCarriedOutIsNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L295) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | | positive | [`TestRFC5176ChangeThatCannotBeCarriedOutIsNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L293) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | ### [`RFC5176-2.3-7`](#rfc5176-2.3-7) When the identification attributes match more than one session, a NAS that supports multi-session requests MUST apply the request to all of them, and a NAS that does not MUST answer with a CoA-NAK or a Disconnect-NAK (§2.3, with the apply-to-all branch stated at §3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176MultipleMatchingSessionsNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L328) | unit/verify | revert, verified | | positive | [`TestRFC5176MultipleMatchingSessionsNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L325) | unit/verify | revert, verified | ### [`RFC5176-3.1-1`](#rfc5176-3.1-1) The Dynamic Authorization Server MUST include the request's Proxy-State attributes in its response, unmodified, in the order they arrived, and treated as opaque data (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176ProxyStateReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L365) | unit/verify | revert, verified | | positive | [`TestRFC5176ProxyStateReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L363) | unit/verify | revert, verified | ### [`RFC5176-3.2-1`](#rfc5176-3.2-1) A NAS MUST answer a CoA-Request carrying a Service-Type Attribute whose value it does not support, "Authorize Only" included, with a CoA-NAK, and MUST NOT answer it with a CoA-ACK (§3.2, with the unsupported-value branch stated at §2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176ServiceTypeNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L400) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | | positive | [`TestRFC5176ServiceTypeNAKed`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L397) | unit/verify | revert, producer-changed (the producer's behavior changed since the break was applied to it) | ### [`RFC5176-3.3-2`](#rfc5176-3.3-2) The Dynamic Authorization Server MUST NOT interpret the State Attribute locally, and MUST send it unmodified in the ACK or NAK it returns (§3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176StateReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L429) | unit/verify | revert, verified | | positive | [`TestRFC5176StateReturnedUnmodified`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L427) | unit/verify | revert, verified | ### [`RFC5176-3.4-1`](#rfc5176-3.4-1) When the HMAC-MD5 message integrity check of a CoA-Request or Disconnect-Request is calculated, the Request Authenticator field and the Message-Authenticator Attribute MUST each be considered to be sixteen octets of zero (§3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`authradius/TestRFC5176MessageAuthenticatorZeroesBothFields`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L460) | unit/verify | revert, verified | | negative | [`TestRFC5176MessageAuthenticatorRefusesEveryOtherStream`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc5176_message_authenticator_test.go#L148) | unit/verify | unproven | | positive | [`authradius/TestRFC5176MessageAuthenticatorZeroesBothFields`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L456) | unit/verify | revert, verified | | positive | [`radius/TestRFC5176MessageAuthenticatorZeroesBothFields`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc5176_message_authenticator_test.go#L126) | unit/verify | unproven | ### [`RFC5176-3.4-2`](#rfc5176-3.4-2) The Message-Authenticator Attribute is calculated and inserted in the packet before the Request Authenticator is calculated, so the Request Authenticator MUST cover the Message-Authenticator value as sent (§3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176RequestAuthenticatorRefusesTheInvertedOrder`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc5176_message_authenticator_test.go#L239) | unit/verify | unproven | | positive | [`TestRFC5176RequestAuthenticatorCoversTheSignedMessageAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/radius/rfc5176_message_authenticator_test.go#L218) | unit/verify | unproven | ### [`RFC5176-3.4-3`](#rfc5176-3.4-3) A Dynamic Authorization Server receiving a CoA-Request or Disconnect-Request with a Message-Authenticator Attribute present MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent (§3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176WrongMessageAuthenticatorDiscardedWhenNotRequired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_message_authenticator_optional_test.go#L71) | unit/verify | unproven | | negative | [`TestRFC5176ListenerDiscardsWrongMessageAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_message_authenticator_test.go#L52) | unit/verify | unproven | | positive | [`TestRFC5176ListenerAcceptsConformantMessageAuthenticator`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_message_authenticator_test.go#L26) | unit/verify | unproven | ### [`RFC5176-3.4-4`](#rfc5176-3.4-4) The Message-Authenticator Attribute MAY be used to authenticate and integrity-protect CoA-Request, CoA-ACK, CoA-NAK, Disconnect-Request, Disconnect-ACK and Disconnect-NAK packets in order to prevent spoofing, so a request that carries none is answered rather than discarded; the `require-message-authenticator` leaf turns its absence into a discard for an operator who wants the Blast-RADIUS mitigation (§3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCoAListenerMissingMessageAuthenticatorDroppedWhenRequired`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/coa_test.go#L307) | unit/verify | unproven | | positive | [`TestRFC5176MessageAuthenticatorAbsentIsAcceptedByDefault`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_message_authenticator_optional_test.go#L26) | unit/verify | unproven | ### [`RFC5176-3.5-5`](#rfc5176-3.5-5) An Error-Cause value in the 200-299 range MUST NOT be sent within a CoA-NAK or Disconnect-NAK, a value in the 400-599 range MUST NOT be sent within a CoA-ACK or Disconnect-ACK, 202 MUST NOT be sent by an implementation of this specification, 502 MUST NOT be sent by a NAS, 201 MUST NOT leave a packet other than a Disconnect-ACK and 504 MUST NOT leave a packet other than a Disconnect-NAK (§3.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176ErrorCausePlacement`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L499) | unit/verify | revert, verified | | positive | [`TestRFC5176ErrorCausePlacement`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L496) | unit/verify | revert, verified | ### [`RFC5176-3.6-1`](#rfc5176-3.6-1) NAS and session identification attributes MUST NOT be used for a purpose other than identification, and the same Vendor-Specific Attribute MUST NOT serve identification and authorization change at the same time (§3.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176IdentificationAttributesIdentifyOnly`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L607) | unit/verify | revert, verified | | positive | [`TestRFC5176IdentificationAttributesIdentifyOnly`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L604) | unit/verify | revert, verified | ### [`RFC5176-6.1-1`](#rfc5176-6.1-1) A Dynamic Authorization Server MUST silently discard Disconnect-Request or CoA-Request packets from untrusted sources, so a source that is not a configured Dynamic Authorization Client is refused; an EMPTY allow list means no configured server resolved and refuses every source rather than accepting all of them (§6.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCoASourceFilterAnswersAConfiguredClient`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_coa_test.go#L173) | unit/verify | unproven | | negative | [`TestRFC5176UntrustedSourceDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L655) | unit/verify | revert, verified | | positive | [`TestCoASourceFilterDiscardsWhenNoServerResolved`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_coa_test.go#L119) | unit/verify | unproven | | positive | [`TestRFC5176UntrustedSourceDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L652) | unit/verify | revert, verified | ### [`RFC5176-6.3-1`](#rfc5176-6.3-1) When an Event-Timestamp Attribute is present the Dynamic Authorization Server MUST check that it is current within an acceptable time window, and MUST silently discard the packet when it is not; that window MUST be the one used for duplicate detection (§6.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5176StaleEventTimestampDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L714) | unit/verify | revert, verified | | positive | [`TestRFC5176StaleEventTimestampDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/l2tp/plugins/authradius/rfc5176_walk_test.go#L710) | unit/verify | revert, verified | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement agent, spec-rfcgate-6-supported-extraction-signoff, RFC 5176 conformance package | | Signed off | 2026-09-01 | | Register | rfc2119 | | Source | rfc/full/rfc5176.txt | | Source fingerprint | 4852faf09c5bbdd6 | | Record | rfc/extraction/rfc5176.json | | Mapped sentences | 44 | | Declined as scope | 28 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | Title, status, copyright and table of contents | 0 | skipped (front-matter) | Title, status, copyright and table of contents. | | `1` | not stated | 0 | walked | not stated | | `1.1` | not stated | 0 | walked | not stated | | `1.2` | not stated | 0 | walked | not stated | | `1.3` | not stated | 0 | walked | not stated | | `2` | not stated | 0 | walked | not stated | | `2.1` | not stated | 0 | walked | not stated | | `2.2` | not stated | 1 | walked | not stated | | `2.3` | not stated | 19 | walked | not stated | | `3` | not stated | 4 | walked | not stated | | `3.1` | not stated | 11 | walked | not stated | | `3.2` | not stated | 7 | walked | not stated | | `3.3` | not stated | 9 | walked | not stated | | `3.4` | not stated | 4 | walked | not stated | | `3.5` | not stated | 7 | walked | not stated | | `3.6` | not stated | 3 | walked | not stated | | `4` | not stated | 1 | walked | not stated | | `5` | not stated | 0 | skipped (iana) | IANA Considerations: it allocates Error-Cause values 407 and 508 and binds IANA, not an implementation. | | `6` | not stated | 0 | walked | not stated | | `6.1` | not stated | 2 | walked | not stated | | `6.2` | not stated | 0 | walked | not stated | | `6.3` | not stated | 4 | walked | not stated | | `7` | not stated | 0 | walked | not stated | | `8` | Reference list | 0 | skipped (references) | Reference list. | | `8.1` | Normative references | 0 | skipped (references) | Normative references. | | `8.2` | Informative references | 0 | skipped (references) | Informative references. | | `9` | Acknowledgments | 0 | skipped (acknowledgements) | Acknowledgments. | | `A` | not stated | 0 | skipped (appendix-non-normative) | Appendix A, Changes from RFC 3576: a change log against the obsoleted document. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `2.3:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Identifier management by the sender. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them | The Identifier field MUST be changed whenever the content of the Attributes field changes, or whenever a valid reply has been received for a previous request. | | `2.3:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Identifier reuse on retransmission by the sender. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them | For retransmissions where the contents are identical, the Identifier MUST remain unchanged. | | `2.3:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Retransmission by the sender. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them | If the Dynamic Authorization Client is retransmitting a Disconnect-Request or CoA-Request to the same Dynamic Authorization Server as before, and the attributes haven't changed, the same Request Authenticator, Identifier, and source port MUST be used. | | `2.3:7` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Authenticator and Identifier choice by the sender. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them | If any attributes have changed, a new Authenticator and Identifier MUST be used. | | `2.3:8` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Failover to a secondary DAS by the sender. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them | Since this represents a new request, a new Request Authenticator and Identifier MUST be used. | | `3:3` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | What a Disconnect-Request MUST contain, an obligation on the composer. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them. The receive-side counterpart is site 3:4, which binds ze and maps to RFC5176-2.3-5 | A Disconnect-Request MUST contain only NAS and session identification attributes. | | `3.1:5` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS forwarding proxy. ze forwards no CoA or Disconnect packet: coaListener.handlePacket (internal/component/l2tp/plugins/authradius/coa.go) dispatches to handleCoA or handleDisconnect and both answer locally, and no other file in the tree emits a code in the 40-45 range | The forwarding proxy MUST NOT modify any other Proxy-State attributes that were in the packet; it may choose not to forward them, but it MUST NOT change their contents. | | `3.1:6` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS forwarding proxy. ze forwards no CoA or Disconnect packet: coaListener.handlePacket (internal/component/l2tp/plugins/authradius/coa.go) dispatches to handleCoA or handleDisconnect and both answer locally, and no other file in the tree emits a code in the 40-45 range | If the forwarding proxy omits the Proxy-State attributes in the request, it MUST attach them to the response before sending it. | | `3.1:7` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS forwarding proxy. ze forwards no CoA or Disconnect packet: coaListener.handlePacket (internal/component/l2tp/plugins/authradius/coa.go) dispatches to handleCoA or handleDisconnect and both answer locally, and no other file in the tree emits a code in the 40-45 range | When the proxy forwards a Disconnect-Request or CoA-Request, it MAY add a Proxy-State Attribute, but it MUST NOT add more than one. | | `3.1:8` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS forwarding proxy. ze forwards no CoA or Disconnect packet: coaListener.handlePacket (internal/component/l2tp/plugins/authradius/coa.go) dispatches to handleCoA or handleDisconnect and both answer locally, and no other file in the tree emits a code in the 40-45 range | If a Proxy-State Attribute is added to a packet when forwarding the packet, the Proxy-State Attribute MUST be added after any existing Proxy-State attributes. | | `3.1:9` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS forwarding proxy. ze forwards no CoA or Disconnect packet: coaListener.handlePacket (internal/component/l2tp/plugins/authradius/coa.go) dispatches to handleCoA or handleDisconnect and both answer locally, and no other file in the tree emits a code in the 40-45 range | The forwarding proxy MUST NOT change the order of any attributes of the same type, including Proxy-State. | | `3.1:10` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS forwarding proxy. ze forwards no CoA or Disconnect packet: coaListener.handlePacket (internal/component/l2tp/plugins/authradius/coa.go) dispatches to handleCoA or handleDisconnect and both answer locally, and no other file in the tree emits a code in the 40-45 range | When the proxy receives a response to a CoA-Request or Disconnect- Request, it MUST remove its own Proxy-State Attribute (the last Proxy-State in the packet) before forwarding the response. | | `3.1:11` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Binds the RADIUS forwarding proxy. ze forwards no CoA or Disconnect packet: coaListener.handlePacket (internal/component/l2tp/plugins/authradius/coa.go) dispatches to handleCoA or handleDisconnect and both answer locally, and no other file in the tree emits a code in the 40-45 range | Since Disconnect and CoA responses are authenticated on the entire packet contents, the stripping of the Proxy-State Attribute invalidates the integrity check, so the proxy MUST recompute it. | | `3.2:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | What a Disconnect-Request MUST NOT contain, an obligation on the composer. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them. The receive side is disconnectSupportedAttrs (internal/component/l2tp/plugins/authradius/coa.go), which omits Service-Type so such a request is answered with a Disconnect-NAK under RFC5176-2.3-5 | A Service-Type Attribute MUST NOT be included within a Disconnect-Request. | | `3.2:4` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | What an Authorize Only CoA-Request MUST contain, an obligation on the composer. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them. The receive-side counterpart is site 3.2:5, which maps to RFC5176-3.2-1 | A CoA-Request containing a Service-Type Attribute with value "Authorize Only" MUST in addition contain only NAS or session identification attributes, as well as a State Attribute. | | `3.2:6` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | RFC 5176 Section 3.2: "Support for a CoA-Request including a Service-Type Attribute with value \\"Authorize Only\\" is OPTIONAL on the NAS and Dynamic Authorization Client." The owner declined the feature on 2026-08-31, and handlePacket (internal/component/l2tp/plugins/authradius/coa.go) shows it: every CoA-Request carrying a Service-Type is answered with a CoA-NAK and Error-Cause 405 before any authorization change is read, so no Authorize Only exchange ever starts. The absent feature is disclosed in the RFC 5176 row of docs/features/rfc-status.md | If a CoA-Request packet including a Service-Type value of "Authorize Only" is successfully processed, the NAS MUST respond with a CoA-NAK containing a Service-Type Attribute with value "Authorize Only", and an Error-Cause Attribute with value 507 (Request Initiated). | | `3.2:7` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | RFC 5176 Section 3.2: "Support for a CoA-Request including a Service-Type Attribute with value \\"Authorize Only\\" is OPTIONAL on the NAS and Dynamic Authorization Client." The owner declined the feature on 2026-08-31, and handlePacket (internal/component/l2tp/plugins/authradius/coa.go) shows it: every CoA-Request carrying a Service-Type is answered with a CoA-NAK and Error-Cause 405 before any authorization change is read, so no Authorize Only exchange ever starts. The absent feature is disclosed in the RFC 5176 row of docs/features/rfc-status.md | The NAS then MUST send an Access-Request to the RADIUS server including a Service-Type Attribute with value "Authorize Only", along with a State Attribute. | | `3.3:2` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | Block-quoted RFC 2865 Section 5.44, introduced by "[RFC2865], Section 5.44 states:". The obligation is RFC 2865's and is carried by rfc/short/rfc2865.md | An Access-Request MUST contain either a User-Password or a CHAP-Password or State. | | `3.3:3` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The second sentence of the same block quote of RFC 2865 Section 5.44. The obligation is RFC 2865's | An Access-Request MUST NOT contain both a User-Password and a CHAP-Password. | | `3.3:4` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | RFC 5176 Section 3.2: "Support for a CoA-Request including a Service-Type Attribute with value \\"Authorize Only\\" is OPTIONAL on the NAS and Dynamic Authorization Client." The owner declined the feature on 2026-08-31, and handlePacket (internal/component/l2tp/plugins/authradius/coa.go) shows it: every CoA-Request carrying a Service-Type is answered with a CoA-NAK and Error-Cause 405 before any authorization change is read, so no Authorize Only exchange ever starts. The absent feature is disclosed in the RFC 5176 row of docs/features/rfc-status.md. ze sends no Access-Request carrying Service-Type Authorize Only | In order to satisfy the requirements of [RFC2865], Section 5.44, an Access-Request with Service-Type Attribute with value "Authorize Only" MUST contain a State Attribute. | | `3.3:5` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | RFC 5176 Section 3.2: "Support for a CoA-Request including a Service-Type Attribute with value \\"Authorize Only\\" is OPTIONAL on the NAS and Dynamic Authorization Client." The owner declined the feature on 2026-08-31, and handlePacket (internal/component/l2tp/plugins/authradius/coa.go) shows it: every CoA-Request carrying a Service-Type is answered with a CoA-NAK and Error-Cause 405 before any authorization change is read, so no Authorize Only exchange ever starts. The absent feature is disclosed in the RFC 5176 row of docs/features/rfc-status.md. Its first clause binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them, and its second is conditioned on "the resulting Access-Request, if any", which ze never sends | In order to provide a State Attribute to the NAS, a Dynamic Authorization Client sending a CoA-Request with a Service-Type Attribute with a value of "Authorize Only" MUST include a State Attribute, and the NAS MUST send the State Attribute unmodified to the RADIUS server in the resulting Access-Request, if any. | | `3.3:7` | `feature-out-of-scope` (never bound Ze): the RFC makes a feature OPTIONAL, Ze decided not to offer it, and this obligation is conditional on offering it | RFC 2865 Section 5.29 makes the feature optional: "If the Value is set to RADIUS-Request, upon termination of the specified service the NAS MAY send a new Access-Request to the RADIUS server, including the State attribute if any." ze performs no Termination-Action: the three non-test Access-Request producers in the tree are buildAuthAttrs (internal/component/l2tp/plugins/authradius/handler.go), (*radiusAuthenticator).Authenticate (internal/component/radius/authenticator.go) and the two doctor probes, and each builds an Access-Request at authentication time only. No code names attribute 29. The absent feature is disclosed in the RFC 5176 row of docs/features/rfc-status.md | If the NAS performs the Termination-Action by sending a new Access- Request upon termination of the current session, it MUST include the State Attribute unchanged in that Access-Request. | | `3.3:9` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | A packet-shape rule on the CoA-Request the sender builds; RFC 5176 Section 3.6 states the same bound as the 0-1 column for State. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them | A CoA-Request packet MUST have only zero or one State Attribute. | | `3.4:2` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | Verification of a CoA/Disconnect-ACK or -NAK, a packet ze emits and never receives. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them | A Dynamic Authorization Client receiving a CoA/Disconnect-ACK or CoA/Disconnect-NAK with a Message-Authenticator Attribute present MUST calculate the correct value of the Message-Authenticator and silently discard the packet if it does not match the value sent. | | `3.4:4` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The response-direction Message-Authenticator, whose enclosing construction is RFC 5176 Section 3.4: "The Message-Authenticator Attribute MAY be used to authenticate and integrity-protect CoA-Request, CoA-ACK, CoA-NAK, Disconnect-Request, Disconnect-ACK, and Disconnect-NAK packets in order to prevent spoofing." coaListener.sendResponse (internal/component/l2tp/plugins/authradius/coa.go) includes no Message-Authenticator in a CoA-ACK, CoA-NAK, Disconnect-ACK or Disconnect-NAK, which the MAY permits, so the computation rule has no packet to govern | When the HMAC-MD5 message integrity check is calculated, the Message-Authenticator Attribute MUST be considered to be sixteen octets of zero. | | `3.6:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The legend of the Section 3.6 table of attributes. The keywords define what the 0, 0+, 0-1 and 1 columns MEAN; they state no obligation on an implementation. The obligations the table expresses are its rows | 0 This attribute MUST NOT be present in packet. 0+ Zero or more instances of this attribute MAY be present in packet. 0-1 Zero or one instance of this attribute MAY be present in packet. 1 Exactly one instance of this attribute MUST be present in packet. | | `4:1` | `binds-another-role` (never bound Ze): the obligation is addressed to a role Ze never acts as. Presumed wrong until justified: Ze rarely implements one side of a protocol, so the reason beside this row must name the role, show Ze never acts as it, and cite the producer that would. | The Diameter-considerations restatement of Section 3.2's rule on what a Disconnect-Request may carry. Binds the Dynamic Authorization Client, the entity originating CoA-Request and Disconnect-Request packets (RFC 5176 Section 1.3). ze originates neither: coaListener (internal/component/l2tp/plugins/authradius/coa.go) is the only site in the tree that names radius.CodeCoARequest or radius.CodeDisconnectRequest, and it only receives them | As a result, as noted in Section 3.2, the Service-Type Attribute MUST NOT be used within a Disconnect-Request. | | `6.1:2` | `advisory-in-context` (never bound Ze): the sentence advises on applying a rule stated elsewhere and adds no obligation of its own | The else-branch of an optional check. Its enclosing construction is RFC 5176 Section 6.1: "In situations where the Dynamic Authorization Client is co-resident with a RADIUS authentication or accounting server, a proxy MAY perform a \\"reverse path forwarding\\" (RPF) check to verify that a Disconnect-Request or CoA-Request originates from an authorized Dynamic Authorization Client." ze performs no RPF check and maintains no realm routing table, which the same section says makes an RPF check impossible for a NAS | If the source address of the Disconnect-Request or CoA-Request is within this set, then the CoA-Request or Disconnect-Request is forwarded; otherwise it MUST be silently discarded. | ## Superseded No document obsoletes RFC 5176, so its obligations are stated where they were written. --- ### Page: RFC 5187 - OSPFv3 Graceful Restart https://ze-software.net/quality/rfc-compliance/rfc5187/ # RFC 5187 - OSPFv3 Graceful Restart Experimental. Every requirement this repository extracted from RFC 5187, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 4 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 8 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 4 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 4 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 8 | | Tagged units | 8 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5187.md` | | Requirement shard | `rfc/requirements/rfc5187.md` | | RFC text | `rfc/full/rfc5187.txt` | ## Enrolment Enrolled: OSPFv3 Graceful Restart: four MUST-level requirements, all met and tested with both polarities. RFC5187-2.2-1 (Grace Period TLV always in a grace-LSA) and RFC5187-2.2-2 (Restart Reason TLV always present) via the shared grace-LSA body codec (internal/plugins/ospf/packet/grace_lsa.go EncodeGraceLSA always emits both, DecodeGraceLSA rejects a body missing either): TestGraceLSARoundTrip (both TLVs round-trip) and TestGraceLSADecodeMissingMandatory (a Reason-only or Period-only body is rejected). Ze originates the OSPFv3 link-scoped grace-LSA (LS Type 0x000B, gr_restarter.go:295-302). RFC5187-3.1-1 (preserve LSA-ID to prefix correspondence across restart) and RFC5187-3.2-1 (preserve OSPFv3 Interface ID across restart) via the NVS preservation maps PrefixLSIDs/InterfaceIDs (internal/plugins/ospf/gr_nvs.go:41-45): TestRestartFactPersistsAcrossRestart (maps read back intact after a restart) and TestStaleRestartFactIgnored (an expired/cleared restart fact is inactive, so stale IDs are not restored). No SHOULD/MAY requirements are gated. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** Restarter and helper behavior for OSPFv3. **What the ledger says remains:** Same OSPF experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`RFC5187-2.2-1`](#rfc5187-2.2-1), [`RFC5187-2.2-2`](#rfc5187-2.2-2), [`RFC5187-3.1-1`](#rfc5187-3.1-1), [`RFC5187-3.2-1`](#rfc5187-3.2-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5187-2.2-1` | Grace Period TLV (Type=1, Length=4): "This TLV MUST always appear in a grace-LSA" (§2.2) | MUST | 2.2 | **positive:** `unit/verify` [`TestGraceLSARoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L12). **negative:** `unit/verify` [`TestGraceLSADecodeMissingMandatory`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L65) | | `RFC5187-2.2-2` | Graceful Restart Reason TLV (Type=2, Length=1): "This TLV MUST always appear in a grace-LSA" (§2.2) | MUST | 2.2 | **positive:** `unit/verify` [`TestGraceLSARoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L16). **negative:** `unit/verify` [`TestGraceLSADecodeMissingMandatory`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L69) | | `RFC5187-3.1-1` | "the restarting router MUST preserve the LSA ID to prefix correspondence across graceful restarts" (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRestartFactPersistsAcrossRestart`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_nvs_test.go#L43). **negative:** `unit/verify` [`TestStaleRestartFactIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_nvs_test.go#L90) | | `RFC5187-3.2-1` | "the OSPFv3 Interface ID, as described in section 3.1.2 of [OSPFv3], MUST be preserved by the restarting router across restarts" (§3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestRestartFactPersistsAcrossRestart`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_nvs_test.go#L46). **negative:** `unit/verify` [`TestStaleRestartFactIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_nvs_test.go#L94) | ## Gaps and untested MUSTs RFC 5187 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5187-2.2-1`](#rfc5187-2.2-1) Grace Period TLV (Type=1, Length=4): "This TLV MUST always appear in a grace-LSA" (§2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGraceLSADecodeMissingMandatory`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L65) | unit/verify | unproven | | positive | [`TestGraceLSARoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L12) | unit/verify | unproven | ### [`RFC5187-2.2-2`](#rfc5187-2.2-2) Graceful Restart Reason TLV (Type=2, Length=1): "This TLV MUST always appear in a grace-LSA" (§2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestGraceLSADecodeMissingMandatory`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L69) | unit/verify | unproven | | positive | [`TestGraceLSARoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/grace_lsa_test.go#L16) | unit/verify | unproven | ### [`RFC5187-3.1-1`](#rfc5187-3.1-1) "the restarting router MUST preserve the LSA ID to prefix correspondence across graceful restarts" (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestStaleRestartFactIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_nvs_test.go#L90) | unit/verify | unproven | | positive | [`TestRestartFactPersistsAcrossRestart`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_nvs_test.go#L43) | unit/verify | unproven | ### [`RFC5187-3.2-1`](#rfc5187-3.2-1) "the OSPFv3 Interface ID, as described in section 3.1.2 of [OSPFv3], MUST be preserved by the restarting router across restarts" (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestStaleRestartFactIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_nvs_test.go#L94) | unit/verify | unproven | | positive | [`TestRestartFactPersistsAcrossRestart`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/gr_nvs_test.go#L46) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5187, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5187, so its obligations are stated where they were written. --- ### Page: RFC 5216 - The EAP-TLS Authentication Protocol https://ze-software.net/quality/rfc-compliance/rfc5216/ # RFC 5216 - The EAP-TLS Authentication Protocol Partial. Every requirement this repository extracted from RFC 5216, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 66.7% | 14 of 21 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 28.6% | 6 of 21 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 21 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 15.0% | 6 of 40 tagged units, 0 escaped and 2 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 21 | of 25 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 21 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 21 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 21 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 21 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 4.8% | 1 of 21 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 21 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 25 | | Gated MUST-level | 21 | | Not applicable, so out of scope | 0 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 40 | | Tagged units | 40 | | Recorded audit verdicts | 0 | | Discrimination records | 8 | | Summary | `rfc/short/rfc5216.md` | | Requirement shard | `rfc/requirements/rfc5216.md` | | RFC text | `rfc/full/rfc5216.txt` | ## Enrolment Enrolled: EAP-TLS (RFC 5216, EAP inside IKEv2): 11 MET (fragmentation, L-bit, mutual-auth handshake, cert path validation, and all seven Section 2.1.3 termination MUSTs: EAP-Failure answers a peer TLS alert, the authenticator waits for the peer reply after its own alert, that reply is answered with EAP-Failure, the authenticator sends its change_cipher_spec and Finished closing flight before it concludes, the peer replies to the authenticator before it terminates, the peer answers the closing flight with a no-data EAP-Response, and the authenticator answers that with EAP-Success) + 6 single-polarity positive (reserved flags, TLS>=1.0 both sides, no compression, no cipher-leak, MSK label) + 3 gap (no CRL/revocation, missing 3DES ciphersuite). Handshake harness enabled by the eap deadlock+cert-validation fix (commit 0816c2b74) ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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`](https://github.com/ze-software/ze/blob/main/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. **What the ledger says remains** One MUST gap in [`rfc/short/rfc5216.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5216.md). [`RFC5216-2.4-5`](#rfc5216-2.4-5) the mandatory TLS_RSA_WITH_3DES_EDE_CBC_SHA ciphersuite is not offered (TLS 1.2+ Go defaults exclude insecure 3DES). [`RFC5216-5.4-2`](#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`](https://github.com/ze-software/ze/blob/main/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`](#rfc5216-5.4-1) is no longer among them either: checkChainRevocation ([`internal/core/eap/revocation.go`](https://github.com/ze-software/ze/blob/main/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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 14 | one part of the gated population | | Annotated instead of tested | 7 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **21** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (14):** [`RFC5216-3-1`](#rfc5216-3-1), [`RFC5216-2.1.1-1`](#rfc5216-2.1.1-1), [`RFC5216-2.1.1-2`](#rfc5216-2.1.1-2), [`RFC5216-2.1.5-1`](#rfc5216-2.1.5-1), [`RFC5216-5.3-1`](#rfc5216-5.3-1), [`RFC5216-5.4-1`](#rfc5216-5.4-1), [`RFC5216-5.4-2`](#rfc5216-5.4-2), [`RFC5216-2.1.3-1`](#rfc5216-2.1.3-1), [`RFC5216-2.1.3-3`](#rfc5216-2.1.3-3), [`RFC5216-2.1.3-4`](#rfc5216-2.1.3-4), [`RFC5216-2.1.3-5`](#rfc5216-2.1.3-5), [`RFC5216-2.1.3-6`](#rfc5216-2.1.3-6), [`RFC5216-2.1.3-7`](#rfc5216-2.1.3-7), [`RFC5216-2.1.3-8`](#rfc5216-2.1.3-8) **Annotated instead of tested (7):** [`RFC5216-3-2`](#rfc5216-3-2), [`RFC5216-2.4-1`](#rfc5216-2.4-1), [`RFC5216-2.4-2`](#rfc5216-2.4-2), [`RFC5216-2.4-3`](#rfc5216-2.4-3), [`RFC5216-2.4-4`](#rfc5216-2.4-4), [`RFC5216-2.4-5`](#rfc5216-2.4-5), [`RFC5216-2.3-1`](#rfc5216-2.3-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5216-3-1` | L bit MUST be set on the first fragment of a TLS message (§3, Flags) | MUST | 3 | **positive:** `unit/verify` [`TestTLSFragmenterFirstFragmentHasLength`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L395). **negative:** `unit/verify` [`TestTLSFragmenterMiddleAndLastFragmentsHaveNoLength`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L475) | | `RFC5216-3-2` | Reserved flag bits (3-7) must be zero on send (§3, Flags) | MUST | 3 | **positive:** `unit/verify` [`TestTLSFragmentReservedFlagBitsAreZero`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L494). **negative:** no negative test. **{single-polarity}:** the flags byte is assembled only from L/M in nextFragment (and S alone in Start), so no code path can set reserved bits 3-7 and only the positive assertion is reachable (internal/core/eap/eap_tls.go:106, :190) | | `RFC5216-2.4-1` | Peer MUST offer TLS 1.0 or later in ClientHello (§2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L364). **negative:** no negative test. **{single-polarity}:** the peer's tls.Config forces MinVersion TLS 1.2, so its ClientHello always offers at least TLS 1.0 and no path can offer lower (internal/core/eap/peer.go:334) | | `RFC5216-2.4-2` | Server MUST respond with TLS 1.0 or later in ServerHello (§2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L367). **negative:** no negative test. **{single-polarity}:** the server's tls.Config forces MinVersion TLS 1.2, so any ServerHello it emits is at least TLS 1.0 and it never negotiates lower (internal/core/eap/eap_tls.go:167) | | `RFC5216-2.4-3` | TLS compression MUST NOT be requested or negotiated (§2.4) | MUST NOT | 2.4 | **positive:** `unit/verify` [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L370). **negative:** no negative test. **{single-polarity}:** both tls.Config values delegate to Go crypto/tls, which implements no TLS compression and always sends null compression, so the prohibition holds structurally with no knob to falsify (internal/core/eap/eap_tls.go:163, peer.go:330) | | `RFC5216-2.4-4` | TLS ciphersuite negotiation MUST NOT be used to negotiate lower-layer data ciphersuites (§2.4) | MUST NOT | 2.4 | **positive:** `unit/verify` [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L375). **negative:** no negative test. **{single-polarity}:** the only value ze extracts from the TLS session is the 64-octet MSK fed to the IKEv2 AUTH payload; the ESP/data-plane ciphers come from independent IKE SA proposal negotiation, never from the TLS ciphersuite (internal/core/eap/eap_tls.go:268) | | `RFC5216-2.4-5` | Mandatory ciphersuite TLS_RSA_WITH_3DES_EDE_CBC_SHA MUST be supported (§2.4) | MUST | 2.4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the server sets no CipherSuites and requires TLS 1.2+, so it inherits Go's secure defaults which classify 3DES as insecure and do not enable TLS_RSA_WITH_3DES_EDE_CBC_SHA, leaving the RFC's mandatory-to-implement ciphersuite unavailable (internal/core/eap/eap_tls.go:163, peer.go:330) | | `RFC5216-2.1.1-1` | Server sends CertificateRequest; peer MUST provide a certificate for mutual authentication (§2.1.1) | MUST | 2.1.1 | **positive:** `unit/verify` [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L349). **negative:** `unit/verify` [`TestEAPTLSAuthenticatorRequiresClientCert`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L444) | | `RFC5216-2.1.1-2` | "The EAP-TLS conversation will then begin, with the peer sending an EAP-Response packet with EAP-Type=EAP-TLS. The data field of that packet will encapsulate one or more TLS records in TLS record layer format, containing a TLS client_hello handshake message" -- stated in the indicative register, so the obligation on the peer's answer to the Start carries no RFC 2119 keyword (§2.1.1) | MUST | 2.1.1 | **positive:** `unit/verify` [`TestEAPTLSPeerFirstResponseCarriesClientHello`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_flight_test.go#L89). **negative:** `unit/verify` [`TestEAPTLSPeerStalledClientFailsRatherThanAcknowledging`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_flight_test.go#L231) | | `RFC5216-2.1.5-1` | Fragmentation MUST be implemented; fragment ACK is an empty EAP-TLS message (§2.1.5) | MUST | 2.1.5 | **positive:** `unit/verify` [`TestTLSFragmenterRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L348). **negative:** `unit/verify` [`TestTLSReassemblyRejectsOversized`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L444) | | `RFC5216-5.3-1` | Both sides MUST perform certificate path validation per RFC 3280 (§5.3) | MUST | 5.3 | **positive:** `unit/verify` [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L355). **negative:** `unit/verify` [`TestEAPTLSPeerRejectsUntrustedServerChain`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L497). **negative:** `unit/verify` [`TestEAPTLSPeerWithoutCARefusesToStart`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L532). **negative:** `unit/verify` [`TestEAPTLSServerRejectsUntrustedClientChain`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L468) | | `RFC5216-5.4-1` | CRL checking MUST be supported (§5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestEAPTLS12RefusesARevokedClientCertificate`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc9190_revocation_test.go#L353). **negative:** `unit/verify` [`TestEAPTLS12CompletesWithAnUnrevokedChain`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc9190_revocation_test.go#L384) | | `RFC5216-5.4-2` | Post-authentication revocation checking MUST be supported (§5.4) | MUST | 5.4 | **positive:** `unit/verify` [`TestEAPTLS12PeerKeepsTheChainItAcceptedForTheLaterCheck`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc9190_ocsp_test.go#L396). **positive:** `unit/verify` [`TestPostAuthenticationCheckClosesTheSAWhenTheResponderReportsRevoked`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc9190_postauth_test.go#L253). **negative:** `unit/verify` [`TestEAPTLSPeerPublishesNoChainWhenTheHandshakeRefusedIt`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc9190_ocsp_test.go#L416). **negative:** `unit/verify` [`TestPostAuthenticationCheckLeavesTheSAUpWhenTheResponderReportsGood`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc9190_postauth_test.go#L283) | | `RFC5216-2.1.3-1` | "The EAP Server MUST reply with an EAP-Failure packet since server authentication failure is a terminal condition" (§2.1.3) | MUST | 2.1.3 | **positive:** `unit/verify` [`TestRFC5216ServerRepliesEAPFailureToPeerAlert`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_termination_test.go#L119). **negative:** `unit/verify` [`TestRFC5216ServerSendsNoEAPFailureWhenBothSidesAuthenticate`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_termination_test.go#L202) | | `RFC5216-2.1.3-3` | "To ensure that the peer receives the TLS alert message, the EAP server MUST wait for the peer to reply with an EAP-Response packet" (§2.1.3) | MUST | 2.1.3 | **positive:** `unit/verify` [`TestEAPTLSAuthenticatorSendsTheAlertBeforeItReportsTheFailure`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_alert_flight_test.go#L95). **negative:** `unit/verify` [`TestEAPTLSAuthenticatorReportsTheFailureWithNoAlertToSend`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_alert_flight_test.go#L361) | | `RFC5216-2.1.3-4` | On a reply with EAP-Type=EAP-TLS and no data, "the EAP-Server MUST send an EAP-Failure packet and terminate the conversation" (§2.1.3) | MUST | 2.1.3 | **positive:** `unit/verify` [`TestEAPTLSAuthenticatorSendsTheAlertBeforeItReportsTheFailure`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_alert_flight_test.go#L101). **positive:** `unit/verify` [`TestEAPTLSSessionPutsTheAlertOnTheWireBeforeEAPFailure`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_alert_flight_test.go#L208). **negative:** `unit/verify` [`TestRFC5216ServerSendsNoEAPFailureWhenBothSidesAuthenticate`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_termination_test.go#L207) | | `RFC5216-2.1.3-5` | "If the peer authenticates successfully, the EAP server MUST respond with an EAP-Request packet with EAP-Type=EAP-TLS, which includes, in the case of a new TLS session, one or more TLS records containing TLS change_cipher_spec and finished handshake messages" (§2.1.3) | MUST | 2.1.3 | **positive:** `unit/verify` [`TestRFC5216SuccessfulTerminationSendsFlightAckThenSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L229). **negative:** `unit/verify` [`TestRFC5216NoClosingFlightOrSuccessWhenThePeerIsRejected`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L333) | | `RFC5216-2.1.3-6` | "To ensure that the EAP Server receives the TLS alert message, the peer MUST wait for the EAP Server to reply before terminating the conversation" (§2.1.3) | MUST | 2.1.3 | **positive:** `unit/verify` [`TestRFC5216PeerRepliesBeforeItTerminates`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_peer_wait_test.go#L41). **negative:** `unit/verify` [`TestRFC5216PeerDoesNotWaitWhenItSentNothing`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_peer_wait_test.go#L168) | | `RFC5216-2.1.3-7` | "If the EAP server authenticates successfully, the peer MUST send an EAP-Response packet of EAP-Type=EAP-TLS, and no data" (§2.1.3) | MUST | 2.1.3 | **positive:** `unit/verify` [`TestRFC5216SuccessfulTerminationSendsFlightAckThenSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L237). **negative:** `unit/verify` [`TestRFC5216PeerSendsItsAlertRatherThanTheNoDataResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L386) | | `RFC5216-2.1.3-8` | After that no-data EAP-Response, "The EAP Server then MUST respond with an EAP-Success message" (§2.1.3) | MUST | 2.1.3 | **positive:** `unit/verify` [`TestRFC5216SuccessfulTerminationSendsFlightAckThenSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L242). **negative:** `unit/verify` [`TestRFC5216NoClosingFlightOrSuccessWhenThePeerIsRejected`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L339) | | `RFC5216-2.3-1` | MSK and EMSK are derived from TLS master_secret using the label "client EAP encryption" (§2.3) | MUST | 2.3 | **positive:** `unit/verify` [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L360). **positive:** `unit/verify` [`TestRFC5216MSKIsTheExportUnderTheRFCLabel`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_msk_label_test.go#L112). **negative:** no negative test. **{single-polarity}:** both server and peer export the 64-octet MSK via ExportKeyingMaterial with the exact RFC label 'client EAP encryption', the only key the IKEv2 lower layer consumes (internal/core/eap/eap_tls.go:268, peer.go:381) | | `RFC5216-5.4-3` | OCSP revocation checking SHOULD be supported (§5.4) | SHOULD | 5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC5216-5.4-4` | TLS Certificate Status Request (stapling) SHOULD be supported (§5.4) | SHOULD | 5.4 | **positive:** no positive test. **negative:** no negative test | | `RFC5216-2.4-6` | TLS_RSA_WITH_AES_128_CBC_SHA SHOULD be supported (§2.4) | SHOULD | 2.4 | **positive:** no positive test. **negative:** no negative test | | `RFC5216-2.1.3-2` | Server MAY allow EAP-TLS restart after peer authentication failure, subject to per-peer limit (§2.1.3) | MAY | 2.1.3 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5216-2.4-5`](#rfc5216-2.4-5) Mandatory ciphersuite TLS_RSA_WITH_3DES_EDE_CBC_SHA MUST be supported (§2.4) | {gap}, no test | the server sets no CipherSuites and requires TLS 1.2+, so it inherits Go's secure defaults which classify 3DES as insecure and do not enable TLS_RSA_WITH_3DES_EDE_CBC_SHA, leaving the RFC's mandatory-to-implement ciphersuite unavailable (internal/core/eap/eap_tls.go:163, peer.go:330) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5216-3-1`](#rfc5216-3-1) L bit MUST be set on the first fragment of a TLS message (§3, Flags) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTLSFragmenterMiddleAndLastFragmentsHaveNoLength`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L475) | unit/verify | unproven | | positive | [`TestTLSFragmenterFirstFragmentHasLength`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L395) | unit/verify | unproven | ### [`RFC5216-3-2`](#rfc5216-3-2) Reserved flag bits (3-7) must be zero on send (§3, Flags) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestTLSFragmentReservedFlagBitsAreZero`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L494) | unit/verify | unproven | ### [`RFC5216-2.4-1`](#rfc5216-2.4-1) Peer MUST offer TLS 1.0 or later in ClientHello (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L364) | unit/verify | unproven | ### [`RFC5216-2.4-2`](#rfc5216-2.4-2) Server MUST respond with TLS 1.0 or later in ServerHello (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L367) | unit/verify | unproven | ### [`RFC5216-2.4-3`](#rfc5216-2.4-3) TLS compression MUST NOT be requested or negotiated (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L370) | unit/verify | unproven | ### [`RFC5216-2.4-4`](#rfc5216-2.4-4) TLS ciphersuite negotiation MUST NOT be used to negotiate lower-layer data ciphersuites (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L375) | unit/verify | unproven | ### [`RFC5216-2.4-5`](#rfc5216-2.4-5) Mandatory ciphersuite TLS_RSA_WITH_3DES_EDE_CBC_SHA MUST be supported (§2.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5216-2.4-5, so no unit is bound to it. ### [`RFC5216-2.1.1-1`](#rfc5216-2.1.1-1) Server sends CertificateRequest; peer MUST provide a certificate for mutual authentication (§2.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEAPTLSAuthenticatorRequiresClientCert`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L444) | unit/verify | unproven | | positive | [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L349) | unit/verify | unproven | ### [`RFC5216-2.1.1-2`](#rfc5216-2.1.1-2) "The EAP-TLS conversation will then begin, with the peer sending an EAP-Response packet with EAP-Type=EAP-TLS. The data field of that packet will encapsulate one or more TLS records in TLS record layer format, containing a TLS client_hello handshake message" -- stated in the indicative register, so the obligation on the peer's answer to the Start carries no RFC 2119 keyword (§2.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEAPTLSPeerStalledClientFailsRatherThanAcknowledging`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_flight_test.go#L231) | unit/verify | unproven | | positive | [`TestEAPTLSPeerFirstResponseCarriesClientHello`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_flight_test.go#L89) | unit/verify | unproven | ### [`RFC5216-2.1.5-1`](#rfc5216-2.1.5-1) Fragmentation MUST be implemented; fragment ACK is an empty EAP-TLS message (§2.1.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTLSReassemblyRejectsOversized`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L444) | unit/verify | unproven | | positive | [`TestTLSFragmenterRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/core/eap/peer_test.go#L348) | unit/verify | unproven | ### [`RFC5216-5.3-1`](#rfc5216-5.3-1) Both sides MUST perform certificate path validation per RFC 3280 (§5.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEAPTLSPeerRejectsUntrustedServerChain`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L497) | unit/verify | unproven | | negative | [`TestEAPTLSPeerWithoutCARefusesToStart`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L532) | unit/verify | unproven | | negative | [`TestEAPTLSServerRejectsUntrustedClientChain`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L468) | unit/verify | unproven | | positive | [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L355) | unit/verify | unproven | ### [`RFC5216-5.4-1`](#rfc5216-5.4-1) CRL checking MUST be supported (§5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEAPTLS12CompletesWithAnUnrevokedChain`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc9190_revocation_test.go#L384) | unit/verify | revert, verified | | positive | [`TestEAPTLS12RefusesARevokedClientCertificate`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc9190_revocation_test.go#L353) | unit/verify | revert, verified | ### [`RFC5216-5.4-2`](#rfc5216-5.4-2) Post-authentication revocation checking MUST be supported (§5.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestPostAuthenticationCheckLeavesTheSAUpWhenTheResponderReportsGood`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc9190_postauth_test.go#L283) | unit/verify | revert, verified | | negative | [`TestEAPTLSPeerPublishesNoChainWhenTheHandshakeRefusedIt`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc9190_ocsp_test.go#L416) | unit/verify | revert, verified | | positive | [`TestPostAuthenticationCheckClosesTheSAWhenTheResponderReportsRevoked`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc9190_postauth_test.go#L253) | unit/verify | revert, verified | | positive | [`TestEAPTLS12PeerKeepsTheChainItAcceptedForTheLaterCheck`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc9190_ocsp_test.go#L396) | unit/verify | revert, verified | ### [`RFC5216-2.1.3-1`](#rfc5216-2.1.3-1) "The EAP Server MUST reply with an EAP-Failure packet since server authentication failure is a terminal condition" (§2.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5216ServerSendsNoEAPFailureWhenBothSidesAuthenticate`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_termination_test.go#L202) | unit/verify | unproven | | positive | [`TestRFC5216ServerRepliesEAPFailureToPeerAlert`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_termination_test.go#L119) | unit/verify | unproven | ### [`RFC5216-2.1.3-3`](#rfc5216-2.1.3-3) "To ensure that the peer receives the TLS alert message, the EAP server MUST wait for the peer to reply with an EAP-Response packet" (§2.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestEAPTLSAuthenticatorReportsTheFailureWithNoAlertToSend`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_alert_flight_test.go#L361) | unit/verify | unproven | | positive | [`TestEAPTLSAuthenticatorSendsTheAlertBeforeItReportsTheFailure`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_alert_flight_test.go#L95) | unit/verify | revert, unit-changed (the tagged unit's behavior changed since the red was observed) | ### [`RFC5216-2.1.3-4`](#rfc5216-2.1.3-4) On a reply with EAP-Type=EAP-TLS and no data, "the EAP-Server MUST send an EAP-Failure packet and terminate the conversation" (§2.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5216ServerSendsNoEAPFailureWhenBothSidesAuthenticate`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_termination_test.go#L207) | unit/verify | unproven | | positive | [`TestEAPTLSAuthenticatorSendsTheAlertBeforeItReportsTheFailure`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_alert_flight_test.go#L101) | unit/verify | revert, unit-changed (the tagged unit's behavior changed since the red was observed) | | positive | [`TestEAPTLSSessionPutsTheAlertOnTheWireBeforeEAPFailure`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_alert_flight_test.go#L208) | unit/verify | unproven | ### [`RFC5216-2.1.3-5`](#rfc5216-2.1.3-5) "If the peer authenticates successfully, the EAP server MUST respond with an EAP-Request packet with EAP-Type=EAP-TLS, which includes, in the case of a new TLS session, one or more TLS records containing TLS change_cipher_spec and finished handshake messages" (§2.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5216NoClosingFlightOrSuccessWhenThePeerIsRejected`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L333) | unit/verify | unproven | | positive | [`TestRFC5216SuccessfulTerminationSendsFlightAckThenSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L229) | unit/verify | unproven | ### [`RFC5216-2.1.3-6`](#rfc5216-2.1.3-6) "To ensure that the EAP Server receives the TLS alert message, the peer MUST wait for the EAP Server to reply before terminating the conversation" (§2.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5216PeerDoesNotWaitWhenItSentNothing`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_peer_wait_test.go#L168) | unit/verify | unproven | | positive | [`TestRFC5216PeerRepliesBeforeItTerminates`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_peer_wait_test.go#L41) | unit/verify | unproven | ### [`RFC5216-2.1.3-7`](#rfc5216-2.1.3-7) "If the EAP server authenticates successfully, the peer MUST send an EAP-Response packet of EAP-Type=EAP-TLS, and no data" (§2.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5216PeerSendsItsAlertRatherThanTheNoDataResponse`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L386) | unit/verify | unproven | | positive | [`TestRFC5216SuccessfulTerminationSendsFlightAckThenSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L237) | unit/verify | unproven | ### [`RFC5216-2.1.3-8`](#rfc5216-2.1.3-8) After that no-data EAP-Response, "The EAP Server then MUST respond with an EAP-Success message" (§2.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5216NoClosingFlightOrSuccessWhenThePeerIsRejected`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L339) | unit/verify | unproven | | positive | [`TestRFC5216SuccessfulTerminationSendsFlightAckThenSuccess`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_success_flight_test.go#L242) | unit/verify | unproven | ### [`RFC5216-2.3-1`](#rfc5216-2.3-1) MSK and EMSK are derived from TLS master_secret using the label "client EAP encryption" (§2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEAPTLSMutualAuthHandshakeSucceeds`](https://github.com/ze-software/ze/blob/main/internal/core/eap/eap_tls_handshake_test.go#L360) | unit/verify | unproven | | positive | [`TestRFC5216MSKIsTheExportUnderTheRFCLabel`](https://github.com/ze-software/ze/blob/main/internal/core/eap/rfc5216_msk_label_test.go#L112) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5216, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5216, so its obligations are stated where they were written. --- ### Page: RFC 5250 - The OSPF Opaque LSA Option https://ze-software.net/quality/rfc-compliance/rfc5250/ # RFC 5250 - The OSPF Opaque LSA Option Experimental. Every requirement this repository extracted from RFC 5250, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 77.8% | 7 of 9 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 22.2% | 2 of 9 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 9 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 9 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 19 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 9 | of 11 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 9 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 9 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 9 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 9 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 9 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 11 | | Gated MUST-level | 9 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 19 | | Tagged units | 19 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5250.md` | | Requirement shard | `rfc/requirements/rfc5250.md` | | RFC text | `rfc/full/rfc5250.txt` | ## Enrolment Enrolled: OSPF Opaque LSA Option (opaque LSA types 9/10/11): nine MUST-level requirements, all met (six with positive+negative tags, three single-polarity) in internal/plugins/ospf. 3-1 (opaque types 9/10/11 map to link/area/AS flooding scope), 3.1-1 (a type-9 link-local opaque LSA is stored and flooded only on its arrival interface), 3.1-3 (a type-11 AS-scope opaque LSA is not flooded into a stub area), 3.1-4 (opaque LSAs are flooded only to opaque-capable neighbors), 3.1-5 (opaque capability is learned from the DD Options O-bit, not from Hello), and 5-1 (an AS-scope opaque LSA from an unreachable originator is not usable) carry positive+negative tags. 3.1-2 (a type-10 area-scope opaque LSA is bound to and confined to its receiving area), 3-2 (the Link State ID splits into an Opaque Type octet and a 24-bit Opaque ID), and 5-2 (reachability is recomputed live, never cached) are {single-polarity: positive}. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** Opaque LSA framework and retention. **What the ledger says remains:** Same OSPF experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 7 | one part of the gated population | | Annotated instead of tested | 2 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **9** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (7):** [`RFC5250-3-1`](#rfc5250-3-1), [`RFC5250-3.1-1`](#rfc5250-3.1-1), [`RFC5250-3.1-2`](#rfc5250-3.1-2), [`RFC5250-3.1-3`](#rfc5250-3.1-3), [`RFC5250-3.1-4`](#rfc5250-3.1-4), [`RFC5250-3.1-5`](#rfc5250-3.1-5), [`RFC5250-5-1`](#rfc5250-5-1) **Annotated instead of tested (2):** [`RFC5250-3-2`](#rfc5250-3-2), [`RFC5250-5-2`](#rfc5250-5-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5250-3-1` | Recognize LS types 9, 10, 11 as Opaque LSAs and apply scope-specific flooding (§3, §3.1) | MUST | 3 | **positive:** `unit/verify` [`TestLSTypeKnownValues`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/lstype_test.go#L38). **positive:** `unit/verify` [`TestOpaqueScopeRouting`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L73). **negative:** `unit/verify` [`TestLSTypeKnownValues`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/lstype_test.go#L25). **negative:** `unit/verify` [`TestOpaqueScopeRouting`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L88) | | `RFC5250-3.1-1` | Type-9 LSA received on an interface other than the target interface MUST be discarded and not acknowledged (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOpaqueType9WrongInterfaceDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L116). **negative:** `unit/verify` [`TestOpaqueType9WrongInterfaceDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L120) | | `RFC5250-3.1-2` | Type-10 LSA whose area differs from the target interface's area MUST be discarded (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOpaqueScopeRouting`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L63). **positive:** `unit/verify` [`TestOpaqueType10ConfinedToItsArea`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L203). **negative:** `unit/verify` [`TestOpaqueType10ConfinedToItsArea`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L207) | | `RFC5250-3.1-3` | Type-11 LSA MUST NOT be flooded into stub areas or NSSAs; received on such an interface it MUST be discarded (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOpaqueType11StubDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L161). **negative:** `unit/verify` [`TestOpaqueType11StubDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L152) | | `RFC5250-3.1-4` | Flood Opaque LSAs only to opaque-capable neighbors (O-bit set in DD) (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOpaqueFloodOnlyToOpaqueNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_flood_test.go#L38). **negative:** `unit/verify` [`TestOpaqueFloodOnlyToOpaqueNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_flood_test.go#L42) | | `RFC5250-3-2` | Split the Link State ID into a 1-byte Opaque Type + 3-byte Opaque ID for the LSDB key (§3, Appendix A.2) | MUST | 3 | **positive:** `unit/verify` [`TestOpaqueLinkStateIDSplit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/lsa_opaque_test.go#L19). **negative:** no negative test. **{single-polarity}:** the Link State ID split is a pure bit operation (high octet is the Opaque Type, low 24 bits the Opaque ID) with no validation or reject path, so there is no negative behavior to drive | | `RFC5250-3.1-5` | Ignore the O-bit when received in packets other than Database Description packets (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOpaqueBitIgnoredOutsideDD`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/dd_opaque_test.go#L74). **negative:** `unit/verify` [`TestOpaqueBitIgnoredOutsideDD`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/dd_opaque_test.go#L63) | | `RFC5250-5-1` | For type-11 LSAs, look up the originating ASBR's routing-table entry; if unreachable, do nothing with the LSA (§5) | MUST | 5 | **positive:** `unit/verify` [`TestOpaqueType11UnreachableOriginatorNotUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/opaque_reachability_test.go#L51). **negative:** `unit/verify` [`TestOpaqueType11UnreachableOriginatorNotUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/opaque_reachability_test.go#L44) | | `RFC5250-5-2` | Discontinue using all Opaque LSAs from an originator detected as unreachable (§5) | MUST | 5 | **positive:** `unit/verify` [`TestOpaqueType11UnreachableOriginatorNotUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/opaque_reachability_test.go#L52). **negative:** no negative test. **{single-polarity}:** reachability is recomputed from a live SPF seam on every opaque delivery (internal/plugins/ospf/opaque.go:126,243) and never cached, so a now-unreachable originator yields not-usable on the next evaluation; there is no stale-cache code path to drive a negative | | `RFC5250-3.1-6` | Set the O-bit in DD packets to advertise opaque capability; SHOULD NOT set it in non-DD packets (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5250-8-1` | Rate-limit Opaque LSA origination (>= 5 s) and acceptance (>= 1 s) (§8) | SHOULD | 8 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 5250 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5250-3-1`](#rfc5250-3-1) Recognize LS types 9, 10, 11 as Opaque LSAs and apply scope-specific flooding (§3, §3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpaqueScopeRouting`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L88) | unit/verify | unproven | | negative | [`TestLSTypeKnownValues`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/lstype_test.go#L25) | unit/verify | unproven | | positive | [`TestOpaqueScopeRouting`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L73) | unit/verify | unproven | | positive | [`TestLSTypeKnownValues`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/types/lstype_test.go#L38) | unit/verify | unproven | ### [`RFC5250-3.1-1`](#rfc5250-3.1-1) Type-9 LSA received on an interface other than the target interface MUST be discarded and not acknowledged (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpaqueType9WrongInterfaceDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L120) | unit/verify | unproven | | positive | [`TestOpaqueType9WrongInterfaceDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L116) | unit/verify | unproven | ### [`RFC5250-3.1-2`](#rfc5250-3.1-2) Type-10 LSA whose area differs from the target interface's area MUST be discarded (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpaqueType10ConfinedToItsArea`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L207) | unit/verify | unproven | | positive | [`TestOpaqueScopeRouting`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L63) | unit/verify | unproven | | positive | [`TestOpaqueType10ConfinedToItsArea`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L203) | unit/verify | unproven | ### [`RFC5250-3.1-3`](#rfc5250-3.1-3) Type-11 LSA MUST NOT be flooded into stub areas or NSSAs; received on such an interface it MUST be discarded (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpaqueType11StubDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L152) | unit/verify | unproven | | positive | [`TestOpaqueType11StubDiscarded`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_scope_test.go#L161) | unit/verify | unproven | ### [`RFC5250-3.1-4`](#rfc5250-3.1-4) Flood Opaque LSAs only to opaque-capable neighbors (O-bit set in DD) (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpaqueFloodOnlyToOpaqueNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_flood_test.go#L42) | unit/verify | unproven | | positive | [`TestOpaqueFloodOnlyToOpaqueNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_flood_test.go#L38) | unit/verify | unproven | ### [`RFC5250-3-2`](#rfc5250-3-2) Split the Link State ID into a 1-byte Opaque Type + 3-byte Opaque ID for the LSDB key (§3, Appendix A.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestOpaqueLinkStateIDSplit`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/lsa_opaque_test.go#L19) | unit/verify | unproven | ### [`RFC5250-3.1-5`](#rfc5250-3.1-5) Ignore the O-bit when received in packets other than Database Description packets (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpaqueBitIgnoredOutsideDD`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/dd_opaque_test.go#L63) | unit/verify | unproven | | positive | [`TestOpaqueBitIgnoredOutsideDD`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/neighbor/dd_opaque_test.go#L74) | unit/verify | unproven | ### [`RFC5250-5-1`](#rfc5250-5-1) For type-11 LSAs, look up the originating ASBR's routing-table entry; if unreachable, do nothing with the LSA (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpaqueType11UnreachableOriginatorNotUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/opaque_reachability_test.go#L44) | unit/verify | unproven | | positive | [`TestOpaqueType11UnreachableOriginatorNotUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/opaque_reachability_test.go#L51) | unit/verify | unproven | ### [`RFC5250-5-2`](#rfc5250-5-2) Discontinue using all Opaque LSAs from an originator detected as unreachable (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestOpaqueType11UnreachableOriginatorNotUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/opaque_reachability_test.go#L52) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5250, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5250, so its obligations are stated where they were written. --- ### Page: RFC 5282 - Using Authenticated Encryption Algorithms with the Encrypted Payload of the Internet Key Exchange version 2 (IKEv2) Protocol https://ze-software.net/quality/rfc-compliance/rfc5282/ # RFC 5282 - Using Authenticated Encryption Algorithms with the Encrypted Payload of the Internet Key Exchange version 2 (IKEv2) Protocol Supported. Every requirement this repository extracted from RFC 5282, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 78.9% | 15 of 19 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 19 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 19 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 100.0% | 30 of 30 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 19 | of 30 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 19 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 19 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 19 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 19 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 21.1% | 4 of 19 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 19 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 30 | | Gated MUST-level | 19 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 4 | | Nightly-only evidence | 0 | | Test tags | 30 | | Tagged units | 30 | | Recorded audit verdicts | 0 | | Discrimination records | 30 | | Summary | `rfc/short/rfc5282.md` | | Requirement shard | `rfc/requirements/rfc5282.md` | | RFC text | `rfc/full/rfc5282.txt` | ## Enrolment Enrolled: Using AEAD Algorithms with the IKEv2 Encrypted Payload (RFC 5282): ze speaks one AEAD cipher for the IKE SA, ENCR_AES_GCM_16 (Transform ID 20) at 128 and 256 bits. buildSKMessageAEADWithMsgID (engine/auth.go) seals an 8-octet IV, a salt-then-IV 12-octet nonce and the IKE-header-through-SK-generic-header associated data; decryptSKPayload and crypto.DecryptIKEAEAD (crypto/cipher.go) open it. encKeyMaterialLen (crypto/keys.go) gives SK_ei and SK_er the RFC 4106 KEYMAT layout (cipher key then 4-octet salt) and leaves SK_ai and SK_ar at zero octets. No AES CCM: aeadSaltBytes holds one entry and specifiedEncryption (crypto/proposal.go) accepts no CCM transform off the wire. Of the 19 gated rows, every row that binds an AES GCM implementation now carries a both-polarity tagged test and a discrimination record. The AES CCM rows (RFC5282-3.2-3, RFC5282-3.2-4, RFC5282-4-3, RFC5282-4-4) carry neither test nor annotation: ze implements no AES CCM, so no code produces the behavior, and ai/rules/rfc-compliance.md reserves gap, not-applicable and feature-declined for an owner answer. ## What the public ledger says **Status:** Supported **What the ledger says is covered** AES-GCM IKEv2 AEAD encryption and decryption framing: the eight-octet IV, the salt-then-IV twelve-octet nonce, the full-length sixteen-octet ICV, the associated data from the fixed header through the Encrypted payload's own generic header, any Padding up to 255 octets on receipt, the KEYMAT layout of SK_ei and SK_er, and the negotiation rules that carry the Key Length attribute and refuse an integrity transform. **What the ledger says remains** AES CCM is not implemented. specifiedEncryption (crypto/proposal.go) accepts no CCM transform off the wire and aeadSaltBytes (crypto/transform.go) holds one entry, so the AES CCM rows of the checklist name behavior no layer performs and carry no test. Whether they are conformance debt or an optional feature ze declined is an owner answer, not this summary's. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 15 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 4 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **19** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (15):** [`RFC5282-3-1`](#rfc5282-3-1), [`RFC5282-3.1-1`](#rfc5282-3.1-1), [`RFC5282-3.1-2`](#rfc5282-3.1-2), [`RFC5282-3.2-1`](#rfc5282-3.2-1), [`RFC5282-3.2-2`](#rfc5282-3.2-2), [`RFC5282-4-1`](#rfc5282-4-1), [`RFC5282-4-2`](#rfc5282-4-2), [`RFC5282-5.1-1`](#rfc5282-5.1-1), [`RFC5282-5.1-2`](#rfc5282-5.1-2), [`RFC5282-7.1-1`](#rfc5282-7.1-1), [`RFC5282-7.1-2`](#rfc5282-7.1-2), [`RFC5282-7.3-1`](#rfc5282-7.3-1), [`RFC5282-7.3-2`](#rfc5282-7.3-2), [`RFC5282-8-1`](#rfc5282-8-1), [`RFC5282-8-2`](#rfc5282-8-2) **No test and no annotation (4):** [`RFC5282-3.2-3`](#rfc5282-3.2-3), [`RFC5282-3.2-4`](#rfc5282-3.2-4), [`RFC5282-4-3`](#rfc5282-4-3), [`RFC5282-4-4`](#rfc5282-4-4) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5282-3-1` | The recipient MUST accept any amount of Padding up to 255 octets (§3) | MUST | 3 | **positive:** `unit/verify` [`TestRFC5282AEADReceiveAcceptsAnyPaddingTo255`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L255). **negative:** `unit/verify` [`TestRFC5282AEADReceiveRefusesATruncatedInnerPayload`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L291) | | `RFC5282-3.1-1` | The Initialization Vector MUST be eight octets (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRFC5282AEADSendIVIsEightOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L314). **negative:** `unit/verify` [`TestRFC5282AEADReceiveRefusesAnIVThatIsNotEightOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L358) | | `RFC5282-3.1-2` | The IV MUST be chosen by the encryptor in a manner that ensures the same IV value is used only once for a given key (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestRFC5282AEADSendIVIsUniquePerKey`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L381). **negative:** `unit/verify` [`TestRFC5282AEADSendIVDoesNotRepeatForARepeatedMessage`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L413) | | `RFC5282-3.2-1` | AES GCM implementations MUST support a full-length 16 octet ICV (§3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestRFC5282AEADSendICVIsSixteenOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L440). **negative:** `unit/verify` [`TestRFC5282AEADReceiveRefusesADamagedICV`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L467) | | `RFC5282-3.2-2` | AES GCM implementations MUST NOT support ICV lengths other than 16, 8 and 12 octets (§3.2) | MUST NOT | 3.2 | **positive:** `unit/verify` [`TestRFC5282AEADRefusesAForbiddenICVLength`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L53). **negative:** `unit/verify` [`TestRFC5282AEADOpensTheFullLengthICV`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L81) | | `RFC5282-3.2-3` | AES CCM implementations MUST support ICV sizes of 8 octets and 16 octets (§3.2) | MUST | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-3.2-4` | AES CCM implementations MUST NOT support ICV lengths other than 8, 16 and 12 octets (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-4-1` | For AES GCM the default nonce format MUST be used, the salt concatenated with the IV in that order (§4) | MUST | 4 | **positive:** `unit/verify` [`TestRFC5282AEADNonceIsSaltThenIV`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L496). **negative:** `unit/verify` [`TestRFC5282AEADRefusesAReversedNonce`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L519) | | `RFC5282-4-2` | For AES GCM a 12 octet nonce MUST be used (§4) | MUST | 4 | **positive:** `unit/verify` [`TestRFC5282AEADNonceIsTwelveOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L537). **negative:** `unit/verify` [`TestRFC5282AEADRefusesANonceThatIsNotTwelveOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L564) | | `RFC5282-4-3` | For AES CCM the default nonce format MUST be used, the salt concatenated with the IV in that order (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-4-4` | For AES CCM an 11 octet nonce MUST be used (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-5.1-1` | The associated data MUST consist of the message from the first octet of the Fixed IKE Header through the last octet of the Encrypted Payload's Payload Header, including any payloads between them (§5.1) | MUST | 5.1 | **positive:** `unit/verify` [`TestRFC5282AEADAssociatedDataCoversAnInterveningPayload`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L585). **negative:** `unit/verify` [`TestRFC5282AEADRefusesAnAlteredInterveningPayload`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L617) | | `RFC5282-5.1-2` | The Initialization Vector and Ciphertext fields MUST NOT be included in the associated data (§5.1) | MUST NOT | 5.1 | **positive:** `unit/verify` [`TestRFC5282AEADAssociatedDataExcludesTheIVAndCiphertext`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L641). **negative:** `unit/verify` [`TestRFC5282AEADRefusesAssociatedDataCoveringTheIV`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L662) | | `RFC5282-7.1-1` | With an AEAD cipher the SK_ai and SK_ar integrity keys are unused, and each MUST be treated as having a size of zero octets (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestRFC5282AEADIntegrityKeysAreZeroOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L123). **negative:** `unit/verify` [`TestRFC5282NonAEADIntegrityKeysAreDerived`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L153) | | `RFC5282-7.1-2` | Each of SK_ei and SK_er MUST have the size and format of the KEYMAT for the AES key size in use, the cipher key followed by the salt (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestRFC5282AEADEncryptionKeysCarryTheirSalt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L169). **negative:** `unit/verify` [`TestRFC5282NonAEADEncryptionKeysCarryNoSalt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L230) | | `RFC5282-7.3-1` | The Key Length attribute MUST be specified whenever an AES GCM or AES CCM encryption transform identifier is used (§7.3) | MUST | 7.3 | **positive:** `unit/verify` [`TestRFC5282AEADProposalCarriesTheKeyLengthAttribute`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L677). **negative:** `unit/verify` [`TestRFC5282AEADOfferWithoutAKeyLengthIsRefused`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L258) | | `RFC5282-7.3-2` | The Key Length attribute MUST have a value of 128, 192, or 256 (§7.3) | MUST | 7.3 | **positive:** `unit/verify` [`TestRFC5282AEADKeyLengthAcceptsTheThreeValues`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L277). **negative:** `unit/verify` [`TestRFC5282AEADKeyLengthRefusesEveryOtherValue`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L297) | | `RFC5282-8-1` | When an authenticated encryption algorithm is selected as the encryption algorithm for any SA, an integrity algorithm MUST NOT be selected for that SA (§8) | MUST NOT | 8 | **positive:** `unit/verify` [`TestRFC5282AEADSelectionCarriesNoIntegrityAlgorithm`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L739). **negative:** `unit/verify` [`TestRFC5282AEADOfferWithIntegrityIsRefused`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L773) | | `RFC5282-8-2` | If all of the encryption algorithms in a proposal are authenticated encryption algorithms, the proposal MUST NOT propose any integrity transforms (§8) | MUST NOT | 8 | **positive:** `unit/verify` [`TestRFC5282AEADIKEProposalCarriesNoIntegrityTransform`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_proposal_test.go#L37). **negative:** `unit/verify` [`TestRFC5282NonAEADIKEProposalKeepsItsIntegrityTransform`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_proposal_test.go#L74) | | `RFC5282-4-5` | Specific authenticated encryption algorithms SHOULD use the default nonce format (§4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-7.2-1` | A 16-octet ICV size SHOULD be used with IKEv2 (§7.2) | SHOULD | 7.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-7.2-2` | The use of 12-octet ICVs, transform identifiers 15 and 19, is discouraged (§7.2) | NOT RECOMMENDED | 7.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-7.2-3` | If an ICV size larger than 8 octets is appropriate, 16-octet ICVs SHOULD be used (§7.2) | SHOULD | 7.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-7.3-3` | The use of the Key Length value 192 is discouraged (§7.3) | NOT RECOMMENDED | 7.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-7.3-4` | If an AES key larger than 128 bits is appropriate, a 256-bit AES key SHOULD be used (§7.3) | SHOULD | 7.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-3-2` | Padding MAY contain any value chosen by the sender (§3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-3.1-3` | The encryptor MAY generate the IV in any manner that ensures uniqueness (§3.1) | MAY | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-3.2-5` | AES GCM implementations MAY support 8 or 12 octet ICVs (§3.2) | MAY | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-3.2-6` | AES CCM implementations MAY also support 12 octet ICVs (§3.2) | MAY | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5282-4-6` | Specific authenticated encryption algorithms MAY use different nonce formats (§4) | MAY | 4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5282-3.2-3`](#rfc5282-3.2-3) AES CCM implementations MUST support ICV sizes of 8 octets and 16 octets (§3.2) | no test | no test carries this requirement id | | [`RFC5282-3.2-4`](#rfc5282-3.2-4) AES CCM implementations MUST NOT support ICV lengths other than 8, 16 and 12 octets (§3.2) | no test | no test carries this requirement id | | [`RFC5282-4-3`](#rfc5282-4-3) For AES CCM the default nonce format MUST be used, the salt concatenated with the IV in that order (§4) | no test | no test carries this requirement id | | [`RFC5282-4-4`](#rfc5282-4-4) For AES CCM an 11 octet nonce MUST be used (§4) | no test | no test carries this requirement id | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5282-3-1`](#rfc5282-3-1) The recipient MUST accept any amount of Padding up to 255 octets (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADReceiveRefusesATruncatedInnerPayload`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L291) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADReceiveAcceptsAnyPaddingTo255`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L255) | unit/verify | revert, verified | ### [`RFC5282-3.1-1`](#rfc5282-3.1-1) The Initialization Vector MUST be eight octets (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADReceiveRefusesAnIVThatIsNotEightOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L358) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADSendIVIsEightOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L314) | unit/verify | revert, verified | ### [`RFC5282-3.1-2`](#rfc5282-3.1-2) The IV MUST be chosen by the encryptor in a manner that ensures the same IV value is used only once for a given key (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADSendIVDoesNotRepeatForARepeatedMessage`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L413) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADSendIVIsUniquePerKey`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L381) | unit/verify | revert, verified | ### [`RFC5282-3.2-1`](#rfc5282-3.2-1) AES GCM implementations MUST support a full-length 16 octet ICV (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADReceiveRefusesADamagedICV`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L467) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADSendICVIsSixteenOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L440) | unit/verify | revert, verified | ### [`RFC5282-3.2-2`](#rfc5282-3.2-2) AES GCM implementations MUST NOT support ICV lengths other than 16, 8 and 12 octets (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADOpensTheFullLengthICV`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L81) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADRefusesAForbiddenICVLength`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L53) | unit/verify | revert, verified | ### [`RFC5282-3.2-3`](#rfc5282-3.2-3) AES CCM implementations MUST support ICV sizes of 8 octets and 16 octets (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5282-3.2-3, so no unit is bound to it. ### [`RFC5282-3.2-4`](#rfc5282-3.2-4) AES CCM implementations MUST NOT support ICV lengths other than 8, 16 and 12 octets (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5282-3.2-4, so no unit is bound to it. ### [`RFC5282-4-1`](#rfc5282-4-1) For AES GCM the default nonce format MUST be used, the salt concatenated with the IV in that order (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADRefusesAReversedNonce`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L519) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADNonceIsSaltThenIV`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L496) | unit/verify | revert, verified | ### [`RFC5282-4-2`](#rfc5282-4-2) For AES GCM a 12 octet nonce MUST be used (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADRefusesANonceThatIsNotTwelveOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L564) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADNonceIsTwelveOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L537) | unit/verify | revert, verified | ### [`RFC5282-4-3`](#rfc5282-4-3) For AES CCM the default nonce format MUST be used, the salt concatenated with the IV in that order (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5282-4-3, so no unit is bound to it. ### [`RFC5282-4-4`](#rfc5282-4-4) For AES CCM an 11 octet nonce MUST be used (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5282-4-4, so no unit is bound to it. ### [`RFC5282-5.1-1`](#rfc5282-5.1-1) The associated data MUST consist of the message from the first octet of the Fixed IKE Header through the last octet of the Encrypted Payload's Payload Header, including any payloads between them (§5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADRefusesAnAlteredInterveningPayload`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L617) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADAssociatedDataCoversAnInterveningPayload`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L585) | unit/verify | revert, verified | ### [`RFC5282-5.1-2`](#rfc5282-5.1-2) The Initialization Vector and Ciphertext fields MUST NOT be included in the associated data (§5.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADRefusesAssociatedDataCoveringTheIV`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L662) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADAssociatedDataExcludesTheIVAndCiphertext`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L641) | unit/verify | revert, verified | ### [`RFC5282-7.1-1`](#rfc5282-7.1-1) With an AEAD cipher the SK_ai and SK_ar integrity keys are unused, and each MUST be treated as having a size of zero octets (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282NonAEADIntegrityKeysAreDerived`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L153) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADIntegrityKeysAreZeroOctets`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L123) | unit/verify | revert, verified | ### [`RFC5282-7.1-2`](#rfc5282-7.1-2) Each of SK_ei and SK_er MUST have the size and format of the KEYMAT for the AES key size in use, the cipher key followed by the salt (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282NonAEADEncryptionKeysCarryNoSalt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L230) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADEncryptionKeysCarryTheirSalt`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L169) | unit/verify | revert, verified | ### [`RFC5282-7.3-1`](#rfc5282-7.3-1) The Key Length attribute MUST be specified whenever an AES GCM or AES CCM encryption transform identifier is used (§7.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADOfferWithoutAKeyLengthIsRefused`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L258) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADProposalCarriesTheKeyLengthAttribute`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L677) | unit/verify | revert, verified | ### [`RFC5282-7.3-2`](#rfc5282-7.3-2) The Key Length attribute MUST have a value of 128, 192, or 256 (§7.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADKeyLengthRefusesEveryOtherValue`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L297) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADKeyLengthAcceptsTheThreeValues`](https://github.com/ze-software/ze/blob/main/internal/component/ike/crypto/rfc5282_aead_test.go#L277) | unit/verify | revert, verified | ### [`RFC5282-8-1`](#rfc5282-8-1) When an authenticated encryption algorithm is selected as the encryption algorithm for any SA, an integrity algorithm MUST NOT be selected for that SA (§8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282AEADOfferWithIntegrityIsRefused`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L773) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADSelectionCarriesNoIntegrityAlgorithm`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_sk_test.go#L739) | unit/verify | revert, verified | ### [`RFC5282-8-2`](#rfc5282-8-2) If all of the encryption algorithms in a proposal are authenticated encryption algorithms, the proposal MUST NOT propose any integrity transforms (§8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5282NonAEADIKEProposalKeepsItsIntegrityTransform`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_proposal_test.go#L74) | unit/verify | revert, verified | | positive | [`TestRFC5282AEADIKEProposalCarriesNoIntegrityTransform`](https://github.com/ze-software/ze/blob/main/internal/component/ike/engine/rfc5282_aead_proposal_test.go#L37) | unit/verify | revert, verified | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | claude-opus-5 (ipsecwalk), spec-rfcgate-6-supported-extraction-signoff | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc5282.txt | | Source fingerprint | e4b60d44518cbcd7 | | Record | rfc/extraction/rfc5282.json | | Mapped sentences | 16 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | Status of This Memo, Abstract and Table of Contents | 0 | skipped (front-matter) | Status of This Memo, Abstract and Table of Contents. | | `1` | not stated | 0 | walked | not stated | | `1.1` | not stated | 0 | walked | not stated | | `2` | not stated | 0 | walked | not stated | | `3` | not stated | 1 | walked | not stated | | `3.1` | not stated | 2 | walked | not stated | | `3.2` | not stated | 3 | walked | not stated | | `4` | not stated | 2 | walked | not stated | | `5` | not stated | 0 | walked | not stated | | `5.1` | not stated | 2 | walked | not stated | | `5.2` | not stated | 0 | walked | not stated | | `6` | not stated | 0 | walked | not stated | | `7` | not stated | 0 | walked | not stated | | `7.1` | not stated | 2 | walked | not stated | | `7.2` | not stated | 0 | walked | not stated | | `7.3` | not stated | 2 | walked | not stated | | `8` | not stated | 3 | walked | not stated | | `9` | not stated | 0 | walked | not stated | | `10` | not stated | 0 | walked | not stated | | `10.1` | not stated | 0 | walked | not stated | | `10.1.1` | not stated | 0 | walked | not stated | | `10.1.2` | not stated | 0 | walked | not stated | | `10.1.3` | not stated | 0 | walked | not stated | | `10.1.4` | not stated | 0 | walked | not stated | | `10.2` | not stated | 0 | walked | not stated | | `10.2.1` | not stated | 0 | walked | not stated | | `10.2.2` | not stated | 0 | walked | not stated | | `10.2.3` | not stated | 0 | walked | not stated | | `10.2.4` | not stated | 0 | walked | not stated | | `10.2.5` | not stated | 0 | walked | not stated | | `10.2.6` | not stated | 0 | walked | not stated | | `10.3` | not stated | 0 | walked | not stated | | `11` | not stated | 0 | walked | not stated | | `12` | not stated | 1 | skipped (iana) | IANA Considerations: the transform identifiers were already assigned for ESP, and the AEAD registry rows are a registry action rather than an implementation obligation. | | `13` | Acknowledgments | 0 | skipped (acknowledgements) | Acknowledgments. | | `14` | References | 0 | skipped (references) | References. | | `14.1` | Normative References | 0 | skipped (references) | Normative References. | | `14.2` | Informative References | 1 | skipped (references) | Informative References. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `8:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The sentence states what RFC 4306 Section 3.3.3 requires, so the obligation it carries belongs to that document; it is quoted here only as the rule the next two sentences update. Sites 8:2 and 8:3 carry the update this document makes, and both are mapped. | IKEv2 (Section 3.3.3 of [RFC4306]) specifies that both an encryption algorithm and an integrity checking algorithm are required for an IKE SA (Security Association). | | `12:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | An IANA statement that no registry action is needed for the transform identifiers this document reuses. It imposes nothing on an implementation, and the lowercase 'required' the prose scan matched is the report of an absence. | No IANA actions are required for this usage extension. | | `14.2:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | IPR boilerplate reproduced from the informative reference section. It addresses 'any interested party' about patent disclosure and states no protocol behavior. | The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. | ## Superseded No document obsoletes RFC 5282, so its obligations are stated where they were written. --- ### Page: RFC 5286 - Basic Specification for IP Fast Reroute: Loop-Free Alternates https://ze-software.net/quality/rfc-compliance/rfc5286/ # RFC 5286 - Basic Specification for IP Fast Reroute: Loop-Free Alternates Experimental. Every requirement this repository extracted from RFC 5286, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 50.0% | 3 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 6 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 30 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 16.7% | 1 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 33.3% | 2 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 30 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 1 | | Declared gaps | 2 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 7 | | Tagged units | 6 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5286.md` | | Requirement shard | `rfc/requirements/rfc5286.md` | | RFC text | `rfc/full/rfc5286.txt` | ## Enrolment Enrolled: IP Fast Reroute: Loop-Free Alternates (LFA): six MUST-level requirements, computed in OSPF only (internal/plugins/ospf/spf/lfa.go). x-1 (Inequality 1 strict loop-free criterion), x-2 (no alternate over a link with forward/reverse cost LSInfinity), and x-3 (OSPF: exclude a neighbor whose every reverse link is LSInfinity) each carry positive+negative tags. x-4 (IS-IS overload-bit exclusion) is {not-applicable}: ze has no IS-IS LFA code path. x-5 (alternate only for shortest-path traffic) and x-6 (bound the alternate's lifetime) are {gap}: the backup is attached to every SPF route with no address-family guard so an OSPFv3 multicast AF route inherits it, and ze has no explicit RFC 5286 Section 4.1 hold-down/termination timer (only SPF reconvergence). Disclosed in the docs/features/rfc-status.md RFC 5286 row. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** LFA and TI-LFA fast reroute: per-neighbor SPFs, loop-free / node-protecting / downstream backup selection, SR repair lists, multi-area suppression. **What the ledger says remains** Two MUST gaps gated in [`rfc/short/rfc5286.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5286.md): 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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 3 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (3):** [`RFC5286-x-1`](#rfc5286-x-1), [`RFC5286-x-2`](#rfc5286-x-2), [`RFC5286-x-3`](#rfc5286-x-3) **Annotated instead of tested (3):** [`RFC5286-x-4`](#rfc5286-x-4), [`RFC5286-x-5`](#rfc5286-x-5), [`RFC5286-x-6`](#rfc5286-x-6) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5286-x-1` | Alternate next-hops MUST conform to at least the loop-freeness | MUST | x | **positive:** `unit/verify` [`TestRFC5286LoopFreeInequality1`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L22). **negative:** `unit/verify` [`TestRFC5286LoopFreeInequality1`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L37) | | `RFC5286-x-2` | For computing an alternate, a router MUST NOT use an alternate | MUST NOT | x | **positive:** `unit/verify` [`TestRFC5286CostReverseCostGate`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L60). **negative:** `unit/verify` [`TestRFC5286CostReverseCostGate`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L66) | | `RFC5286-x-3` | In OSPF, if all links from S to a neighbor N_i have reverse | MUST NOT | x | **positive:** `unit/verify` [`TestRFC5286ReverseCostAllInfinite`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L99). **negative:** `unit/verify` [`TestRFC5286ReverseCostAllInfinite`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L114) | | `RFC5286-x-4` | In IS-IS, if N_i has the overload bit set, S MUST NOT consider using N_i as an alternate | MUST NOT | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze computes Loop-Free Alternates only in OSPF (internal/plugins/ospf/spf/lfa.go); IS-IS has no LFA/backup-next-hop computation code path, so the IS-IS overload-bit exclusion has nothing to apply to | | `RFC5286-x-5` | The alternate next-hop MUST be used only for traffic types routed according to the shortest path | MUST | x | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze attaches the RFC 5286 alternate to a route's Loc-RIB Path unconditionally in Installer.insert (internal/plugins/ospf/spf/install.go:213-229) with no guard confining it to unicast shortest-path forwarding; an OSPFv3 multicast address-family engine (family.IPv4Multicast / family.IPv6Multicast, internal/plugins/ospf/multiaf.go:102-115) installs through the same NewInstallerFamily path (internal/plugins/ospf/spf_wiring.go:34) and inherits fast-reroute config (internal/plugins/ospf/config.go:709-711), so a multicast-AF route receives the same backup next-hop, which Section 4 confines to shortest-path traffic and Section 6.5 excludes from multicast RPF. Disclosed in the docs/features/rfc-status.md RFC 5286 row | | `RFC5286-x-6` | A router MUST limit the amount of time an alternate next-hop is used after the primary becomes unavailable | MUST | x | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze relies on SPF reconvergence to replace a route with a stale alternate (internal/plugins/ospf/spf/install.go:171-195) and the kernel RTNH_F_LINKDOWN flag (internal/plugins/fib/kernel/nexthop_linux.go:113), but implements no explicit RFC 5286 Section 4.1 hold-down timer or termination-condition bounding how long an alternate stays active. Disclosed in the docs/features/rfc-status.md RFC 5286 row | | `RFC5286-x-7` | A router SHOULD select a link-and-node-protecting LFA over a | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-8` | It SHOULD be assumed that an alternate offers no node protection | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-9` | If the primary next-hop uses a broadcast link, an alternate | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-10` | A router SHOULD NOT specify the "local protection available" | SHOULD NOT | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-11` | A router SHOULD NOT use an alternate next-hop along a link | SHOULD NOT | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-12` | A router supporting this specification SHOULD attempt to select | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-13` | S SHOULD select a loop-free node-protecting alternate if one is | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-14` | Given a choice between a link-and-node-protecting alternate and a | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-15` | With multiple primary next-hops, S SHOULD select as alternate | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-16` | If no node-protecting alternate and no other primary can provide | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-17` | Implementations SHOULD support a mode preferring other primary | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-18` | On primary next-hop failure the router SHOULD remove the failed | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-19` | A router implementing [MICROLOOP] SHOULD follow that document's | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-20` | A router implementing [ORDERED-FIB] SHOULD follow that document's | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-21` | An implementation SHOULD continue to use the alternate next-hops | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-22` | Use of the alternate next-hops SHOULD terminate when (a) the new | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-23` | A router SHOULD compute the alternate next-hop for an IGP | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-24` | For OSPF external routes with a forwarding address set, alternate | SHOULD | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-25` | Alternate next-hops SHOULD NOT be used for multicast Reverse | SHOULD NOT | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-26` | A router MAY decide not to use an available loop-free alternate | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-27` | If no node-protecting alternate is available, S MAY select a | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-28` | Implementations considering SRLGs MAY use SRLG protection to | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-29` | The router MAY remove other next-hops it believes (via SRLG | MAY | x | **positive:** no positive test. **negative:** no negative test | | `RFC5286-x-30` | A router MAY safely simplify the multi-homed-prefix calculation by | MAY | x | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5286-x-4`](#rfc5286-x-4) In IS-IS, if N_i has the overload bit set, S MUST NOT consider using N_i as an alternate | no test | no test carries this requirement id; annotated {not-applicable}: ze computes Loop-Free Alternates only in OSPF (internal/plugins/ospf/spf/lfa.go); IS-IS has no LFA/backup-next-hop computation code path, so the IS-IS overload-bit exclusion has nothing to apply to | | [`RFC5286-x-5`](#rfc5286-x-5) The alternate next-hop MUST be used only for traffic types routed according to the shortest path | {gap}, no test | ze attaches the RFC 5286 alternate to a route's Loc-RIB Path unconditionally in Installer.insert (internal/plugins/ospf/spf/install.go:213-229) with no guard confining it to unicast shortest-path forwarding; an OSPFv3 multicast address-family engine (family.IPv4Multicast / family.IPv6Multicast, internal/plugins/ospf/multiaf.go:102-115) installs through the same NewInstallerFamily path (internal/plugins/ospf/spf_wiring.go:34) and inherits fast-reroute config (internal/plugins/ospf/config.go:709-711), so a multicast-AF route receives the same backup next-hop, which Section 4 confines to shortest-path traffic and Section 6.5 excludes from multicast RPF. Disclosed in the docs/features/rfc-status.md RFC 5286 row | | [`RFC5286-x-6`](#rfc5286-x-6) A router MUST limit the amount of time an alternate next-hop is used after the primary becomes unavailable | {gap}, no test | ze relies on SPF reconvergence to replace a route with a stale alternate (internal/plugins/ospf/spf/install.go:171-195) and the kernel RTNH_F_LINKDOWN flag (internal/plugins/fib/kernel/nexthop_linux.go:113), but implements no explicit RFC 5286 Section 4.1 hold-down timer or termination-condition bounding how long an alternate stays active. Disclosed in the docs/features/rfc-status.md RFC 5286 row | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5286-x-1`](#rfc5286-x-1) Alternate next-hops MUST conform to at least the loop-freeness Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5286LoopFreeInequality1`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L37) | unit/verify | unproven | | positive | [`TestRFC5286LoopFreeInequality1`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L22) | unit/verify | unproven | ### [`RFC5286-x-2`](#rfc5286-x-2) For computing an alternate, a router MUST NOT use an alternate Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5286CostReverseCostGate`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L66) | unit/verify | unproven | | positive | [`TestRFC5286CostReverseCostGate`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L60) | unit/verify | unproven | ### [`RFC5286-x-3`](#rfc5286-x-3) In OSPF, if all links from S to a neighbor N_i have reverse Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5286ReverseCostAllInfinite`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L114) | unit/verify | unproven | | positive | [`TestRFC5286ReverseCostAllInfinite`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/spf/rfc5286_lfa_test.go#L99) | unit/verify | unproven | ### [`RFC5286-x-4`](#rfc5286-x-4) In IS-IS, if N_i has the overload bit set, S MUST NOT consider using N_i as an alternate Audit verdict: not audited: no reader has judged these tests No test carries RFC5286-x-4, so no unit is bound to it. ### [`RFC5286-x-5`](#rfc5286-x-5) The alternate next-hop MUST be used only for traffic types routed according to the shortest path Audit verdict: not audited: no reader has judged these tests No test carries RFC5286-x-5, so no unit is bound to it. ### [`RFC5286-x-6`](#rfc5286-x-6) A router MUST limit the amount of time an alternate next-hop is used after the primary becomes unavailable Audit verdict: not audited: no reader has judged these tests No test carries RFC5286-x-6, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 5286, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5286, so its obligations are stated where they were written. --- ### Page: RFC 5301 - Dynamic Hostname Exchange Mechanism for IS-IS https://ze-software.net/quality/rfc-compliance/rfc5301/ # RFC 5301 - Dynamic Hostname Exchange Mechanism for IS-IS Supported. Every requirement this repository extracted from RFC 5301, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 7 of 7 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 7 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 7 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 7 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 22 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 7 | of 15 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 7 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 7 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 7 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 7 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 7 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 15 | | Gated MUST-level | 7 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 22 | | Tagged units | 22 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5301.md` | | Requirement shard | `rfc/requirements/rfc5301.md` | | RFC text | `rfc/full/rfc5301.txt` | ## Enrolment Enrolled: Dynamic Hostname Exchange Mechanism for IS-IS (TLV 137): seven MUST-level requirements read from the indicative prose of section 3, all met and test-bound with positive+negative tags. 3-4 (TLV type 137), 3-5 (the length octet is the total length of the value), 3-6 (a value of 1 to 255 bytes) and 3-8 (the string is not null-terminated) are proven over the real origination path in internal/plugins/isis/lsdb (encode_test.go builds the node's own LSP and reads the framed octets out of Entry.Raw), and 3-4 also carries interop evidence: test/interop/scenarios/isis-p2p-frr renders Ze's name in FRR's IS-IS database, which FRR can only do by decoding type 137. 3-7 (the value is encoded in 7-bit ASCII) and 3-9 (the content is a domain name per RFC 2181) are enforced at the config boundary by ISISHostnameValidator (internal/component/config/validators.go), which refuses any octet outside 0x20..0x7e and any break of the RFC 2181 section 11 label lengths; the value is REFUSED rather than sanitised at emit, so the name an operator configures and the name a peer reads are the same string. 3-10 (the IDNA duty of a user-interface that permits Unicode) is met under Reading A, recorded in rfc/short/rfc5301.md: the configuring interface refuses Unicode, so the sentence's antecedent is false and no ToASCII conversion is owed. The refusal is observable in both polarities rather than annotated, and no row carries {gap} or {not-applicable}. test/isis/isis-hostname-ascii.ci proves the boundary through `ze config validate`. Enrolled 2026-08-10. ## What the public ledger says **Status:** Supported **What the ledger says is covered** Dynamic hostname TLV 137 origination and decode ([`internal/plugins/isis/packet/tlv_core.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_core.go) `writeHostnameTLV`, [`internal/plugins/isis/lsdb/encode.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode.go) `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` ([`internal/component/config/validators.go`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators.go)) enforces that, reached through the `ze:validate` extension on the hostname leaf in [`internal/plugins/isis/yang/ze-isis-conf.yang`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/yang/ze-isis-conf.yang). Nothing sanitizes or converts the value on the way to the wire. The name an operator configures is the name a peer reads. `sanitizeHostname` ([`internal/plugins/isis/show.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/show.go)) 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 [`rfc/short/rfc5301.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5301.md). The walk is recorded in [`rfc/extraction/rfc5301.json`](https://github.com/ze-software/ze/blob/main/rfc/extraction/rfc5301.json). **What the ledger says remains** 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 [`rfc/short/rfc5301.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5301.md) 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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 7 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **7** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (7):** [`RFC5301-3-4`](#rfc5301-3-4), [`RFC5301-3-5`](#rfc5301-3-5), [`RFC5301-3-6`](#rfc5301-3-6), [`RFC5301-3-7`](#rfc5301-3-7), [`RFC5301-3-8`](#rfc5301-3-8), [`RFC5301-3-9`](#rfc5301-3-9), [`RFC5301-3-10`](#rfc5301-3-10) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5301-3-4` | The Dynamic hostname TLV is defined here as TLV type 137 (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L110). **negative:** `unit/verify` [`TestISISHostnameEmptyOmitsTLV`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L166). **positive:** `interop/nightly` [`checkISISDynamicHostname`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1135) | | `RFC5301-3-5` | Length - total length of the value field (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L114). **negative:** `unit/verify` [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L125) | | `RFC5301-3-6` | Value - a string of 1 to 255 bytes (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L118). **negative:** `unit/verify` [`TestISISHostnameEmptyOmitsTLV`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L162). **positive:** `functional/verify` [`isis-hostname-ascii.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-ascii.ci#L100). **positive:** `interop/nightly` [`checkISISDynamicHostname`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1139) | | `RFC5301-3-7` | The Value field is encoded in 7-bit ASCII (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestISISHostnameValidatorCharset`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L149). **positive:** `unit/verify` [`TestLoadConfigRefusesAnISISHostnameOutside7BitASCII`](https://github.com/ze-software/ze/blob/main/internal/component/config/cli/cmd_validate_startup_agreement_test.go#L43). **negative:** `unit/verify` [`TestISISHostnameValidatorCharset`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L153). **positive:** `functional/verify` [`isis-hostname-ascii.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-ascii.ci#L55). **positive:** `functional/verify` [`isis-hostname-startup-refused.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-startup-refused.ci#L16) | | `RFC5301-3-8` | The string is not null-terminated (Section 3) | MUST NOT | 3 | **positive:** `unit/verify` [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L121). **negative:** `unit/verify` [`TestISISHostnameTLVIsPrintableASCII`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L80) | | `RFC5301-3-9` | The content of this value is a domain name, see [RFC2181] (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestISISHostnameValidatorLabels`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L196). **negative:** `unit/verify` [`TestISISHostnameValidatorLabels`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L201). **positive:** `functional/verify` [`isis-hostname-ascii.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-ascii.ci#L73) | | `RFC5301-3-10` | If a user-interface for configuring or displaying this field permits Unicode characters, that user-interface is responsible for applying the ToASCII and/or ToUnicode algorithm as described in [RFC3490] to achieve the correct format for transmission or display (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestISISHostnameUnicodeRefusedNotConverted`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L260). **negative:** `unit/verify` [`TestISISHostnameTLVIsPrintableASCII`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L72). **positive:** `functional/verify` [`isis-hostname-ascii.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-ascii.ci#L56) | | `RFC5301-3-1` | The use of FQDN or a subset of it is strongly recommended (Section 3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5301-3-3` | If this TLV is present in a pseudonode LSP, then it SHOULD NOT be interpreted as the DNS hostname of the router (Section 3) | SHOULD NOT | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5301-4-1` | If a system receives a mapping for a name or system ID that is different from the mapping in the local cache, an implementation SHOULD replace the existing mapping with the latest information (Section 4) | SHOULD | 4 - Implementation | **positive:** no positive test. **negative:** no negative test | | `RFC5301-3-2` | This TLV may be present in any fragment of a non-pseudonode LSP (Section 3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5301-3-11` | The Dynamic hostname TLV is optional (Section 3) | OPTIONAL | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5301-4-2` | When originating an LSP, a router may decide to include this TLV in its LSP (Section 4) | MAY | 4 - Implementation | **positive:** no positive test. **negative:** no negative test | | `RFC5301-4-3` | Upon receipt of an LSP with the Dynamic hostname TLV, a router may decide to ignore this TLV, or to install the symbolic name and system ID in its hostname mapping table for the IS-IS network (Section 4) | MAY | 4 - Implementation | **positive:** no positive test. **negative:** no negative test | | `RFC5301-4-4` | A router may also optionally insert this TLV in its pseudonode LSP for the association of a symbolic name to a local LAN (Section 4) | MAY | 4 - Implementation | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 5301 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5301-3-4`](#rfc5301-3-4) The Dynamic hostname TLV is defined here as TLV type 137 (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISHostnameEmptyOmitsTLV`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L166) | unit/verify | unproven | | positive | [`checkISISDynamicHostname`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1135) | interop/nightly | unproven | | positive | [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L110) | unit/verify | unproven | ### [`RFC5301-3-5`](#rfc5301-3-5) Length - total length of the value field (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L125) | unit/verify | unproven | | positive | [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L114) | unit/verify | unproven | ### [`RFC5301-3-6`](#rfc5301-3-6) Value - a string of 1 to 255 bytes (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISHostnameEmptyOmitsTLV`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L162) | unit/verify | unproven | | positive | [`checkISISDynamicHostname`](https://github.com/ze-software/ze/blob/main/internal/le/interoplab/bgp/check_rfc.go#L1139) | interop/nightly | unproven | | positive | [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L118) | unit/verify | unproven | | positive | [`isis-hostname-ascii.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-ascii.ci#L100) | functional/verify | unproven | ### [`RFC5301-3-7`](#rfc5301-3-7) The Value field is encoded in 7-bit ASCII (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISHostnameValidatorCharset`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L153) | unit/verify | unproven | | positive | [`TestLoadConfigRefusesAnISISHostnameOutside7BitASCII`](https://github.com/ze-software/ze/blob/main/internal/component/config/cli/cmd_validate_startup_agreement_test.go#L43) | unit/verify | unproven | | positive | [`TestISISHostnameValidatorCharset`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L149) | unit/verify | unproven | | positive | [`isis-hostname-ascii.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-ascii.ci#L55) | functional/verify | unproven | | positive | [`isis-hostname-startup-refused.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-startup-refused.ci#L16) | functional/verify | unproven | ### [`RFC5301-3-8`](#rfc5301-3-8) The string is not null-terminated (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISHostnameTLVIsPrintableASCII`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L80) | unit/verify | unproven | | positive | [`TestISISHostnameTLVFraming`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L121) | unit/verify | unproven | ### [`RFC5301-3-9`](#rfc5301-3-9) The content of this value is a domain name, see [RFC2181] (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISHostnameValidatorLabels`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L201) | unit/verify | unproven | | positive | [`TestISISHostnameValidatorLabels`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L196) | unit/verify | unproven | | positive | [`isis-hostname-ascii.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-ascii.ci#L73) | functional/verify | unproven | ### [`RFC5301-3-10`](#rfc5301-3-10) If a user-interface for configuring or displaying this field permits Unicode characters, that user-interface is responsible for applying the ToASCII and/or ToUnicode algorithm as described in [RFC3490] to achieve the correct format for transmission or display (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISHostnameTLVIsPrintableASCII`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/encode_test.go#L72) | unit/verify | unproven | | positive | [`TestISISHostnameUnicodeRefusedNotConverted`](https://github.com/ze-software/ze/blob/main/internal/component/config/validators_isis_test.go#L260) | unit/verify | unproven | | positive | [`isis-hostname-ascii.ci`](https://github.com/ze-software/ze/blob/main/test/isis/isis-hostname-ascii.ci#L56) | functional/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-implement agent, spec-rfcgate-4-ledger phase 6 | | Signed off | 2026-07-30 | | Register | prose | | Source | rfc/full/rfc5301.txt | | Source fingerprint | d1ee0177891d12fe | | Record | rfc/extraction/rfc5301.json | | Mapped sentences | 0 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of this Memo, copyright notice and Abstract. The Abstract states what the document defines and directs nobody. | | `1` | Introduction | 1 | walked | Introduction. Motivates a name-to-systemID mapping and describes why static tables do not scale. Its one prose site is the burden static configuration imposes on an operator, excluded below. | | `1.1` | not stated | 0 | walked | Specification of Requirements: the RFC 2119 key-words paragraph. Tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. It is also the whole of the document's capitalised-keyword content. | | `2` | Possible Solutions | 0 | walked | Possible Solutions. Compares static configuration and DNS against in-protocol advertisement, then states that this document defines a new TLV. Rationale only. | | `3` | not stated | 0 | walked | Dynamic Hostname TLV: the definitional section, and the source of every gated requirement this summary declares. Written wholly in indicative prose, so the site scan finds nothing here and each requirement is listed as unsourced against this section rather than mapped to a site. | | `4` | Implementation | 0 | walked | Implementation. Four sentences, all permissive or advisory: a router MAY include the TLV, MAY ignore it on receipt, MAY insert it in a pseudonode LSP, and SHOULD replace a differing cached mapping. Captured as RFC5301-4-1 through RFC5301-4-4. | | `5` | Security Considerations | 0 | walked | Security Considerations. Warns that a compromised router can inject false mapping information and that the mapping must be treated with suspicion during an incident. An operational caution, not a directive on the protocol machinery. | | `6` | Acknowledgments, crediting RFC 2763 and Henk Smit | 0 | skipped (acknowledgements) | Acknowledgments, crediting RFC 2763 and Henk Smit. | | `7` | IANA Considerations | 1 | walked | IANA Considerations. Records that TLV 137 was already allocated by RFC 2763 and that IANA need do nothing. Walked because the prose scan attributes one site here; it is excluded below. | | `8` | not stated | 1 | walked | Informative References, followed by the authors' addresses and the copyright statement the prose scan attributes its last site to. Walked for that site rather than skipped. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Describes the maintenance burden of the STATIC mapping tables this document exists to replace. The 'must' binds a network operator keeping a hand-written table in step, not an IS-IS implementation. | These tables need to contain the names and system IDs of all routers in the network, and must be modified each time an addition, deletion, or change occurs. | | `7:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | An IANA instruction, and a negative one: it states that no registry action is needed because RFC 2763 already allocated TLV 137. It binds no speaker. | As such, no new actions are required on the part of IANA. | | `8:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The standard IETF copyright boilerplate. It addresses parties holding patents, not an IS-IS implementation. | The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. | ## Superseded No document obsoletes RFC 5301, so its obligations are stated where they were written. --- ### Page: RFC 5303 - Three-Way Handshake for IS-IS Point-to-Point Adjacencies https://ze-software.net/quality/rfc-compliance/rfc5303/ # RFC 5303 - Three-Way Handshake for IS-IS Point-to-Point Adjacencies Experimental. Every requirement this repository extracted from RFC 5303, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 50.0% | 9 of 18 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 18 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 18 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 18 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 18 | of 19 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 2 | of 18 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 11.1% | 2 of 18 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 18 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 18 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 38.9% | 7 of 18 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 18 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 19 | | Gated MUST-level | 18 | | Not applicable, so out of scope | 2 | | Declared gaps | 7 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 18 | | Tagged units | 18 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5303.md` | | Requirement shard | `rfc/requirements/rfc5303.md` | | RFC text | `rfc/full/rfc5303.txt` | ## Enrolment Enrolled: Three-Way Handshake for IS-IS P2P Adjacencies (RFC 5303): 9 MET (TLV 240 origination+decode, Adjacency Three-Way State + Extended Local Circuit ID fields, System-ID echo gating adjacency to Up, two-way fall-back) + 7 gap (Ext Local Circuit ID uint8(ifindex) truncation, Neighbor Ext Circuit ID echoed 0 and unexamined, no invalid-state discard, no loop-detection mismatch discard, derived state model lacks distinct Accept/restart-Down actions) + 2 not-applicable (option always processed, option always emitted) ## What the public ledger says **Status:** Experimental **What the ledger says is covered** 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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc5303.md). **What the ledger says remains** Gaps gated in [`rfc/short/rfc5303.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5303.md): the Extended Local Circuit ID is derived as uint8(ifindex) so it is not unique beyond 256 interfaces ([`RFC5303-3.2-4`](#rfc5303-3.2-4)); the Neighbor Extended Local Circuit ID is echoed as 0 and never examined ([`RFC5303-3.2-6`](#rfc5303-3.2-6), [`RFC5303-3.2-8`](#rfc5303-3.2-8)); an invalid three-way state is not discarded ([`RFC5303-3.2-7`](#rfc5303-3.2-7)); the loop-detection neighbor-mismatch discard is absent ([`RFC5303-3.2-9`](#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-11), [`RFC5303-3.2-12`](#rfc5303-3.2-12)). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 9 | one part of the gated population | | Annotated instead of tested | 9 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **18** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (9):** [`RFC5303-3.1-1`](#rfc5303-3.1-1), [`RFC5303-3.1-4`](#rfc5303-3.1-4), [`RFC5303-3.1-6`](#rfc5303-3.1-6), [`RFC5303-3.2-1`](#rfc5303-3.2-1), [`RFC5303-3.2-2`](#rfc5303-3.2-2), [`RFC5303-3.2-3`](#rfc5303-3.2-3), [`RFC5303-3.2-5`](#rfc5303-3.2-5), [`RFC5303-3.2-10`](#rfc5303-3.2-10), [`RFC5303-3.2-13`](#rfc5303-3.2-13) **Annotated instead of tested (9):** [`RFC5303-3.1-2`](#rfc5303-3.1-2), [`RFC5303-3.1-3`](#rfc5303-3.1-3), [`RFC5303-3.2-4`](#rfc5303-3.2-4), [`RFC5303-3.2-6`](#rfc5303-3.2-6), [`RFC5303-3.2-7`](#rfc5303-3.2-7), [`RFC5303-3.2-8`](#rfc5303-3.2-8), [`RFC5303-3.2-9`](#rfc5303-3.2-9), [`RFC5303-3.2-11`](#rfc5303-3.2-11), [`RFC5303-3.2-12`](#rfc5303-3.2-12) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5303-3.1-1` | "Any system that supports this mechanism SHALL include this option in its Point-to-Point IIH packets"; the §3.2 sending clause repeats this as "the IS SHALL include the Point-to-Point Three-Way Adjacency option in the transmitted Point-to-Point IIH PDU" (§3.1) | SHALL | 3.1 | **positive:** `unit/verify` [`TestISISP2PIIHCarriesThreeWayOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L125). **negative:** `unit/verify` [`TestISISP2PIIHCarriesThreeWayOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L129) | | `RFC5303-3.1-2` | "Any system that does not understand this option SHALL ignore it" (§3.1) | SHALL | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this constrains a system that does NOT understand the option; Ze implements and processes it (packet/tlv_core.go DecodeP2PThreeWayTLV decodes it, circuit/runtime.go:143 consumes it), so the "does not understand" role never applies to Ze | | `RFC5303-3.1-3` | A system that does not understand this option "SHALL NOT include it in its own IIH packets" (§3.1) | SHALL NOT | 3.1 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this constrains a non-supporting system's emission; Ze supports the mechanism and always emits TLV 240 in its point-to-point IIH (circuit/hello.go:158 threeWayTLV, circuit/hello.go:204 buildP2PHello), so the "does not understand" role never applies to Ze | | `RFC5303-3.1-4` | "Any system that supports this mechanism MUST include the Adjacency Three-Way State field in this option" (§3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestISISP2PThreeWayStateFieldIncluded`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L16). **negative:** `unit/verify` [`TestISISP2PThreeWayStateFieldPresentWithoutNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L49) | | `RFC5303-3.1-6` | "Any system that is able to process this option SHALL follow the procedures below" (§3.1) | SHALL | 3.1 | **positive:** `unit/verify` [`TestISISThreeWayProceduresEngagedByOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/adjacency/fsm_test.go#L395). **negative:** `unit/verify` [`TestISISThreeWayProceduresEngagedByOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/adjacency/fsm_test.go#L399) | | `RFC5303-3.2-1` | The current three-way state of the adjacency with its neighbor "SHALL be reported in the Adjacency Three-Way State field" of the transmitted Point-to-Point IIH (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISThreeWayReportsCurrentState`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L146). **negative:** `unit/verify` [`TestISISThreeWayReportsCurrentState`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L149) | | `RFC5303-3.2-2` | "If no adjacency exists, the state SHALL be reported as Down" in the Adjacency Three-Way State field (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISThreeWayDownWhenNoAdjacency`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L175). **negative:** `unit/verify` [`TestISISThreeWayDownWhenNoAdjacency`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L178) | | `RFC5303-3.2-3` | "The Extended Local Circuit ID field SHALL contain a value assigned by this IS when the circuit is created" (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISThreeWayExtendedLocalCircuitID`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L203). **negative:** `unit/verify` [`TestISISThreeWayExtendedLocalCircuitID`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L206) | | `RFC5303-3.2-4` | The Extended Local Circuit ID "value SHALL be unique among all the circuits of this Intermediate System" (§3.2) | SHALL | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the Extended Local Circuit ID is derived as uint8(ifindex) (circuits.go:317), truncating the interface index to 8 bits; two circuits whose ifindexes collide modulo 256 emit the same value, so uniqueness among all circuits is not guaranteed and the 4-octet field never exceeds 255 | | `RFC5303-3.2-5` | When the neighbor's system ID and Extended Local Circuit ID are known, in three-way state Initializing or Up, "the neighbor's system ID SHALL be reported in the Neighbor System ID field" (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISThreeWayReportsNeighborSystemID`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L226). **negative:** `unit/verify` [`TestISISThreeWayReportsNeighborSystemID`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L229) | | `RFC5303-3.2-6` | When the neighbor is known, the neighbor's "Extended Local Circuit ID SHALL be reported in the Neighbor Extended Local Circuit ID field" (§3.2) | SHALL | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Ze does not track the neighbor's Extended Local Circuit ID; threeWayTLV echoes a constant 0 in the Neighbor Extended Local Circuit ID field (circuit/hello.go:168) and updateThreeWay never stores the neighbor's value (adjacency/fsm.go:232), so the neighbor's actual extended circuit ID is never reported | | `RFC5303-3.2-7` | "If the option is present and contains invalid Adjacency Three-Way State, the PDU SHALL be discarded and no further action is taken" (§3.2) | SHALL | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the decoder validates only the TLV length, not the state value (packet/tlv_core.go DecodeP2PThreeWayTLV); an out-of-range Adjacency Three-Way State is folded into the FSM (adjacency/fsm.go:237) and treated as not-bidirectional rather than discarding the PDU, so the "discard, no further action" path is absent | | `RFC5303-3.2-8` | If the option carries a valid Adjacency Three-Way State, "the Neighbor System ID and Neighbor Extended Local Circuit ID fields, if present, SHALL be examined" (§3.2) | SHALL | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** updateThreeWay examines only the Neighbor System ID (adjacency/fsm.go:240) to set neighborSawUs; the Neighbor Extended Local Circuit ID is decoded (packet/tlv_core.go DecodeP2PThreeWayTLV, the p2pThreeWayLenFull branch) but the FSM never examines it, so the required examination of both fields is incomplete | | `RFC5303-3.2-9` | If the Neighbor System ID does not match the local system's ID, or the Neighbor Extended Local Circuit ID does not match the local extended circuit ID, "the PDU SHALL be discarded and no further action is taken" (§3.2) | SHALL | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** on a Neighbor System ID that does not match ours Ze keeps the adjacency Initializing (adjacency/fsm.go:240) and still processes the PDU, arming the hold timer and recording the neighbor (adjacency/fsm.go:161); it neither discards the PDU nor checks the Neighbor Extended Local Circuit ID, so the loop-detection discard is absent | | `RFC5303-3.2-10` | When the "Up" action from ISO 10589 state tables 5, 6, 7, and 8 creates a new adjacency, "the three-way state of the adjacency SHALL be Down" (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISThreeWayNewAdjacencyStateDown`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L247). **negative:** `unit/verify` [`TestISISThreeWayNewAdjacencyStateDown`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L251) | | `RFC5303-3.2-11` | If the action taken from ISO 10589 section 8.2.4.2 a or b is "Up" or "Accept", "the IS SHALL perform the action indicated by the new adjacency three-way state table" using the current and received Adjacency Three-Way State (§3.2) | SHALL | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Ze does not maintain a distinct three-way state nor the four-cell section 3.2 state table; bidirectionality is derived from the ISO 10589 adjacency state plus the System ID echo (adjacency/fsm.go:207 bidirectional), so the table's "Accept" and restart "Down" cells are not modeled | | `RFC5303-3.2-12` | If the new action is "Down", "the adjacency SHALL be deleted" and an adjacencyStateChange event for Down is generated with reason "Neighbor restarted" (§3.2) | SHALL | 3.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the "Down"/"Neighbor restarted" action is absent; when our three-way state is Down and the neighbor reports Up Ze brings the adjacency Up (adjacency/fsm.go:189) rather than deleting it and emitting adjacencyStateChange(Down) | | `RFC5303-3.2-13` | If the new action is "Initialize", no event is generated and "the adjacency three-way state SHALL be set to Initializing" (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISThreeWayInitializeAction`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/adjacency/fsm_test.go#L425). **negative:** `unit/verify` [`TestISISThreeWayInitializeAction`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/adjacency/fsm_test.go#L428) | | `RFC5303-3.1-5` | Besides the mandatory Adjacency Three-Way State field, "the other fields in this option SHOULD be included" (§3.1) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5303-3.1-2`](#rfc5303-3.1-2) "Any system that does not understand this option SHALL ignore it" (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: this constrains a system that does NOT understand the option; Ze implements and processes it (packet/tlv_core.go DecodeP2PThreeWayTLV decodes it, circuit/runtime.go:143 consumes it), so the "does not understand" role never applies to Ze | | [`RFC5303-3.1-3`](#rfc5303-3.1-3) A system that does not understand this option "SHALL NOT include it in its own IIH packets" (§3.1) | no test | no test carries this requirement id; annotated {not-applicable}: this constrains a non-supporting system's emission; Ze supports the mechanism and always emits TLV 240 in its point-to-point IIH (circuit/hello.go:158 threeWayTLV, circuit/hello.go:204 buildP2PHello), so the "does not understand" role never applies to Ze | | [`RFC5303-3.2-4`](#rfc5303-3.2-4) The Extended Local Circuit ID "value SHALL be unique among all the circuits of this Intermediate System" (§3.2) | {gap}, no test | the Extended Local Circuit ID is derived as uint8(ifindex) (circuits.go:317), truncating the interface index to 8 bits; two circuits whose ifindexes collide modulo 256 emit the same value, so uniqueness among all circuits is not guaranteed and the 4-octet field never exceeds 255 | | [`RFC5303-3.2-6`](#rfc5303-3.2-6) When the neighbor is known, the neighbor's "Extended Local Circuit ID SHALL be reported in the Neighbor Extended Local Circuit ID field" (§3.2) | {gap}, no test | Ze does not track the neighbor's Extended Local Circuit ID; threeWayTLV echoes a constant 0 in the Neighbor Extended Local Circuit ID field (circuit/hello.go:168) and updateThreeWay never stores the neighbor's value (adjacency/fsm.go:232), so the neighbor's actual extended circuit ID is never reported | | [`RFC5303-3.2-7`](#rfc5303-3.2-7) "If the option is present and contains invalid Adjacency Three-Way State, the PDU SHALL be discarded and no further action is taken" (§3.2) | {gap}, no test | the decoder validates only the TLV length, not the state value (packet/tlv_core.go DecodeP2PThreeWayTLV); an out-of-range Adjacency Three-Way State is folded into the FSM (adjacency/fsm.go:237) and treated as not-bidirectional rather than discarding the PDU, so the "discard, no further action" path is absent | | [`RFC5303-3.2-8`](#rfc5303-3.2-8) If the option carries a valid Adjacency Three-Way State, "the Neighbor System ID and Neighbor Extended Local Circuit ID fields, if present, SHALL be examined" (§3.2) | {gap}, no test | updateThreeWay examines only the Neighbor System ID (adjacency/fsm.go:240) to set neighborSawUs; the Neighbor Extended Local Circuit ID is decoded (packet/tlv_core.go DecodeP2PThreeWayTLV, the p2pThreeWayLenFull branch) but the FSM never examines it, so the required examination of both fields is incomplete | | [`RFC5303-3.2-9`](#rfc5303-3.2-9) If the Neighbor System ID does not match the local system's ID, or the Neighbor Extended Local Circuit ID does not match the local extended circuit ID, "the PDU SHALL be discarded and no further action is taken" (§3.2) | {gap}, no test | on a Neighbor System ID that does not match ours Ze keeps the adjacency Initializing (adjacency/fsm.go:240) and still processes the PDU, arming the hold timer and recording the neighbor (adjacency/fsm.go:161); it neither discards the PDU nor checks the Neighbor Extended Local Circuit ID, so the loop-detection discard is absent | | [`RFC5303-3.2-11`](#rfc5303-3.2-11) If the action taken from ISO 10589 section 8.2.4.2 a or b is "Up" or "Accept", "the IS SHALL perform the action indicated by the new adjacency three-way state table" using the current and received Adjacency Three-Way State (§3.2) | {gap}, no test | Ze does not maintain a distinct three-way state nor the four-cell section 3.2 state table; bidirectionality is derived from the ISO 10589 adjacency state plus the System ID echo (adjacency/fsm.go:207 bidirectional), so the table's "Accept" and restart "Down" cells are not modeled | | [`RFC5303-3.2-12`](#rfc5303-3.2-12) If the new action is "Down", "the adjacency SHALL be deleted" and an adjacencyStateChange event for Down is generated with reason "Neighbor restarted" (§3.2) | {gap}, no test | the "Down"/"Neighbor restarted" action is absent; when our three-way state is Down and the neighbor reports Up Ze brings the adjacency Up (adjacency/fsm.go:189) rather than deleting it and emitting adjacencyStateChange(Down) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5303-3.1-1`](#rfc5303-3.1-1) "Any system that supports this mechanism SHALL include this option in its Point-to-Point IIH packets"; the §3.2 sending clause repeats this as "the IS SHALL include the Point-to-Point Three-Way Adjacency option in the transmitted Point-to-Point IIH PDU" (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISP2PIIHCarriesThreeWayOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L129) | unit/verify | unproven | | positive | [`TestISISP2PIIHCarriesThreeWayOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L125) | unit/verify | unproven | ### [`RFC5303-3.1-2`](#rfc5303-3.1-2) "Any system that does not understand this option SHALL ignore it" (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.1-2, so no unit is bound to it. ### [`RFC5303-3.1-3`](#rfc5303-3.1-3) A system that does not understand this option "SHALL NOT include it in its own IIH packets" (§3.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.1-3, so no unit is bound to it. ### [`RFC5303-3.1-4`](#rfc5303-3.1-4) "Any system that supports this mechanism MUST include the Adjacency Three-Way State field in this option" (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISP2PThreeWayStateFieldPresentWithoutNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L49) | unit/verify | unproven | | positive | [`TestISISP2PThreeWayStateFieldIncluded`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L16) | unit/verify | unproven | ### [`RFC5303-3.1-6`](#rfc5303-3.1-6) "Any system that is able to process this option SHALL follow the procedures below" (§3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISThreeWayProceduresEngagedByOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/adjacency/fsm_test.go#L399) | unit/verify | unproven | | positive | [`TestISISThreeWayProceduresEngagedByOption`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/adjacency/fsm_test.go#L395) | unit/verify | unproven | ### [`RFC5303-3.2-1`](#rfc5303-3.2-1) The current three-way state of the adjacency with its neighbor "SHALL be reported in the Adjacency Three-Way State field" of the transmitted Point-to-Point IIH (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISThreeWayReportsCurrentState`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L149) | unit/verify | unproven | | positive | [`TestISISThreeWayReportsCurrentState`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L146) | unit/verify | unproven | ### [`RFC5303-3.2-2`](#rfc5303-3.2-2) "If no adjacency exists, the state SHALL be reported as Down" in the Adjacency Three-Way State field (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISThreeWayDownWhenNoAdjacency`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L178) | unit/verify | unproven | | positive | [`TestISISThreeWayDownWhenNoAdjacency`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L175) | unit/verify | unproven | ### [`RFC5303-3.2-3`](#rfc5303-3.2-3) "The Extended Local Circuit ID field SHALL contain a value assigned by this IS when the circuit is created" (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISThreeWayExtendedLocalCircuitID`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L206) | unit/verify | unproven | | positive | [`TestISISThreeWayExtendedLocalCircuitID`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L203) | unit/verify | unproven | ### [`RFC5303-3.2-4`](#rfc5303-3.2-4) The Extended Local Circuit ID "value SHALL be unique among all the circuits of this Intermediate System" (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.2-4, so no unit is bound to it. ### [`RFC5303-3.2-5`](#rfc5303-3.2-5) When the neighbor's system ID and Extended Local Circuit ID are known, in three-way state Initializing or Up, "the neighbor's system ID SHALL be reported in the Neighbor System ID field" (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISThreeWayReportsNeighborSystemID`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L229) | unit/verify | unproven | | positive | [`TestISISThreeWayReportsNeighborSystemID`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L226) | unit/verify | unproven | ### [`RFC5303-3.2-6`](#rfc5303-3.2-6) When the neighbor is known, the neighbor's "Extended Local Circuit ID SHALL be reported in the Neighbor Extended Local Circuit ID field" (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.2-6, so no unit is bound to it. ### [`RFC5303-3.2-7`](#rfc5303-3.2-7) "If the option is present and contains invalid Adjacency Three-Way State, the PDU SHALL be discarded and no further action is taken" (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.2-7, so no unit is bound to it. ### [`RFC5303-3.2-8`](#rfc5303-3.2-8) If the option carries a valid Adjacency Three-Way State, "the Neighbor System ID and Neighbor Extended Local Circuit ID fields, if present, SHALL be examined" (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.2-8, so no unit is bound to it. ### [`RFC5303-3.2-9`](#rfc5303-3.2-9) If the Neighbor System ID does not match the local system's ID, or the Neighbor Extended Local Circuit ID does not match the local extended circuit ID, "the PDU SHALL be discarded and no further action is taken" (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.2-9, so no unit is bound to it. ### [`RFC5303-3.2-10`](#rfc5303-3.2-10) When the "Up" action from ISO 10589 state tables 5, 6, 7, and 8 creates a new adjacency, "the three-way state of the adjacency SHALL be Down" (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISThreeWayNewAdjacencyStateDown`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L251) | unit/verify | unproven | | positive | [`TestISISThreeWayNewAdjacencyStateDown`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/rfc5303_threeway_test.go#L247) | unit/verify | unproven | ### [`RFC5303-3.2-11`](#rfc5303-3.2-11) If the action taken from ISO 10589 section 8.2.4.2 a or b is "Up" or "Accept", "the IS SHALL perform the action indicated by the new adjacency three-way state table" using the current and received Adjacency Three-Way State (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.2-11, so no unit is bound to it. ### [`RFC5303-3.2-12`](#rfc5303-3.2-12) If the new action is "Down", "the adjacency SHALL be deleted" and an adjacencyStateChange event for Down is generated with reason "Neighbor restarted" (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5303-3.2-12, so no unit is bound to it. ### [`RFC5303-3.2-13`](#rfc5303-3.2-13) If the new action is "Initialize", no event is generated and "the adjacency three-way state SHALL be set to Initializing" (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISThreeWayInitializeAction`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/adjacency/fsm_test.go#L428) | unit/verify | unproven | | positive | [`TestISISThreeWayInitializeAction`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/adjacency/fsm_test.go#L425) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5303, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5303, so its obligations are stated where they were written. --- ### Page: RFC 5304 - IS-IS Cryptographic Authentication https://ze-software.net/quality/rfc-compliance/rfc5304/ # RFC 5304 - IS-IS Cryptographic Authentication Experimental. Every requirement this repository extracted from RFC 5304, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 77.8% | 7 of 9 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 11.1% | 1 of 9 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 9 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 9 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 16 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 9 | of 14 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 9 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 11.1% | 1 of 9 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 9 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 9 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 9 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 14 | | Gated MUST-level | 9 | | Not applicable, so out of scope | 1 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 16 | | Tagged units | 16 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5304.md` | | Requirement shard | `rfc/requirements/rfc5304.md` | | RFC text | `rfc/full/rfc5304.txt` | ## Enrolment Enrolled: IS-IS Cryptographic Authentication (HMAC-MD5, Authentication TLV type 54): nine MUST-level requirements. Eight are met with positive+negative tags in internal/plugins/isis: 2-1 and 2-2 (Level-1 area and Level-2 domain authentication with the correct key chain), 2-3 (Hello/IIH link-level authentication), 2-6 (reject a PDU whose HMAC does not match), 2-7 (a signed purge carries only the Authentication TLV), 2-8 (do not accept an unauthenticated purge under configured auth), and 2-9 (reject a purge carrying extra TLVs). 2-4 (sign over the padded PDU) is {single-polarity: positive} with a new test capturing the bytes handed to the signer. 2-5 (do not include the auth value in the per-PDU Checksum TLV) is {not-applicable}: ze implements no optional IS-IS per-PDU Checksum TLV (no TLV 12 codec), so the conditional MUST NOT is vacuous. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** Interface and level key-chain authentication. **What the ledger says remains:** Same IS-IS experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 7 | one part of the gated population | | Annotated instead of tested | 2 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **9** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (7):** [`RFC5304-2-1`](#rfc5304-2-1), [`RFC5304-2-2`](#rfc5304-2-2), [`RFC5304-2-3`](#rfc5304-2-3), [`RFC5304-2-6`](#rfc5304-2-6), [`RFC5304-2-7`](#rfc5304-2-7), [`RFC5304-2-8`](#rfc5304-2-8), [`RFC5304-2-9`](#rfc5304-2-9) **Annotated instead of tested (2):** [`RFC5304-2-4`](#rfc5304-2-4), [`RFC5304-2-5`](#rfc5304-2-5) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5304-2-1` | Level 1 Sequence Number PDUs SHALL use the Area Authentication string as in Level 1 Link State PDUs (§2) | SHALL | 2 | **positive:** `unit/verify` [`TestISISAuthEngineSignLevel`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L108). **negative:** `unit/verify` [`TestISISAuthChainSelection`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L201) | | `RFC5304-2-2` | Level 2 Sequence Number PDUs SHALL use the domain authentication string as in Level 2 Link State PDUs (§2) | SHALL | 2 | **positive:** `unit/verify` [`TestISISAuthEngineSignLevel`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L111). **negative:** `unit/verify` [`TestISISAuthLevelChainCrossUseL2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L283) | | `RFC5304-2-3` | IS-IS Hello PDUs SHALL use the Link Level Authentication String (§2) | SHALL | 2 | **positive:** `unit/verify` [`TestISISAuthReject`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L156). **negative:** `unit/verify` [`TestISISAuthReject`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L168) | | `RFC5304-2-4` | The HMAC-MD5 result for the IS-IS Hello PDUs SHALL be calculated after the packet is padded to the MTU size, if padding is not disabled (§2) | SHALL | 2 | **positive:** `unit/verify` [`TestISISHelloSignedOverPaddedPDU`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/runtime_test.go#L69). **negative:** no negative test. **{single-polarity}:** the LAN/P2P hello is padded then signed in one straight-line path (internal/plugins/isis/circuit/runtime.go:273-276), and there is no unpadded-sign code path to drive a negative | | `RFC5304-2-5` | Implementations that support the optional checksum for the Sequence Number PDUs and IS-IS Hello PDUs MUST NOT include the Checksum TLV (§2) | MUST NOT | 2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the antecedent is false -- ze implements no optional IS-IS per-PDU/SNP Checksum TLV (internal/plugins/isis/packet has no TLV 12 codec; only the LSP header Fletcher checksum field exists), so this conditional MUST NOT has no applicable code path | | `RFC5304-2-6` | An implementation that implements HMAC-MD5 authentication and receives HMAC-MD5 Authentication Information MUST discard the PDU if the Authentication Value is incorrect (§2) | MUST | 2 | **positive:** `unit/verify` [`TestISISAuthSignVerifyHMACMD5`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L131). **negative:** `unit/verify` [`TestISISAuthConstantTimeCompare`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L608). **negative:** `unit/verify` [`TestISISAuthWrongKeyRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L635) | | `RFC5304-2-7` | ISes (routers) that implement HMAC-MD5 authentication and initiate LSP purges MUST remove the body of the LSP and add the authentication TLV (§2) | MUST | 2 | **positive:** `unit/verify` [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L455). **negative:** `unit/verify` [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L466) | | `RFC5304-2-8` | ISes implementing HMAC-MD5 authentication MUST NOT accept unauthenticated purges (§2) | MUST NOT | 2 | **positive:** `unit/verify` [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L447). **negative:** `unit/verify` [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L464) | | `RFC5304-2-9` | ISes MUST NOT accept purges that contain TLVs other than the authentication TLV (§2) | MUST NOT | 2 | **positive:** `unit/verify` [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L449). **negative:** `unit/verify` [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L483) | | `RFC5304-2.1-1` | If an inbound LSP with an authentication failure has the local System ID and a higher Sequence Number than the IS-IS process has, the process SHOULD increase its own LSP Sequence Number accordingly and re-flood the LSPs (§2.1) | SHOULD | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5304-2-10` | The Link Level Authentication String used by IS-IS Hello PDUs MAY be different from that of Link State PDUs (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC5304-2-11` | An implementation MAY have a transition mode where it includes HMAC-MD5 Authentication Information in PDUs but does not verify the HMAC-MD5 Authentication Information (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC5304-2-12` | An implementation MAY check a set of passwords when verifying the Authentication Value (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC5304-2-13` | An implementation that does not implement HMAC-MD5 authentication MAY accept a PDU that contains the HMAC-MD5 Authentication Type (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5304-2-5`](#rfc5304-2-5) Implementations that support the optional checksum for the Sequence Number PDUs and IS-IS Hello PDUs MUST NOT include the Checksum TLV (§2) | no test | no test carries this requirement id; annotated {not-applicable}: the antecedent is false -- ze implements no optional IS-IS per-PDU/SNP Checksum TLV (internal/plugins/isis/packet has no TLV 12 codec; only the LSP header Fletcher checksum field exists), so this conditional MUST NOT has no applicable code path | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5304-2-1`](#rfc5304-2-1) Level 1 Sequence Number PDUs SHALL use the Area Authentication string as in Level 1 Link State PDUs (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthChainSelection`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L201) | unit/verify | unproven | | positive | [`TestISISAuthEngineSignLevel`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L108) | unit/verify | unproven | ### [`RFC5304-2-2`](#rfc5304-2-2) Level 2 Sequence Number PDUs SHALL use the domain authentication string as in Level 2 Link State PDUs (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthLevelChainCrossUseL2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L283) | unit/verify | unproven | | positive | [`TestISISAuthEngineSignLevel`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L111) | unit/verify | unproven | ### [`RFC5304-2-3`](#rfc5304-2-3) IS-IS Hello PDUs SHALL use the Link Level Authentication String (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthReject`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L168) | unit/verify | unproven | | positive | [`TestISISAuthReject`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L156) | unit/verify | unproven | ### [`RFC5304-2-4`](#rfc5304-2-4) The HMAC-MD5 result for the IS-IS Hello PDUs SHALL be calculated after the packet is padded to the MTU size, if padding is not disabled (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISHelloSignedOverPaddedPDU`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/runtime_test.go#L69) | unit/verify | unproven | ### [`RFC5304-2-5`](#rfc5304-2-5) Implementations that support the optional checksum for the Sequence Number PDUs and IS-IS Hello PDUs MUST NOT include the Checksum TLV (§2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5304-2-5, so no unit is bound to it. ### [`RFC5304-2-6`](#rfc5304-2-6) An implementation that implements HMAC-MD5 authentication and receives HMAC-MD5 Authentication Information MUST discard the PDU if the Authentication Value is incorrect (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthConstantTimeCompare`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L608) | unit/verify | unproven | | negative | [`TestISISAuthWrongKeyRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L635) | unit/verify | unproven | | positive | [`TestISISAuthSignVerifyHMACMD5`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L131) | unit/verify | unproven | ### [`RFC5304-2-7`](#rfc5304-2-7) ISes (routers) that implement HMAC-MD5 authentication and initiate LSP purges MUST remove the body of the LSP and add the authentication TLV (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L466) | unit/verify | unproven | | positive | [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L455) | unit/verify | unproven | ### [`RFC5304-2-8`](#rfc5304-2-8) ISes implementing HMAC-MD5 authentication MUST NOT accept unauthenticated purges (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L464) | unit/verify | unproven | | positive | [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L447) | unit/verify | unproven | ### [`RFC5304-2-9`](#rfc5304-2-9) ISes MUST NOT accept purges that contain TLVs other than the authentication TLV (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L483) | unit/verify | unproven | | positive | [`TestISISAuthPurge`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L449) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5304, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5304, so its obligations are stated where they were written. --- ### Page: RFC 5305 - IS-IS Extensions for Traffic Engineering https://ze-software.net/quality/rfc-compliance/rfc5305/ # RFC 5305 - IS-IS Extensions for Traffic Engineering Experimental. Every requirement this repository extracted from RFC 5305, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 62.5% | 5 of 8 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 8 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 8 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 8 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 8.3% | 1 of 12 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 8 | of 10 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 3 | of 8 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 37.5% | 3 of 8 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 8 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 8 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 8 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 10 | | Gated MUST-level | 8 | | Not applicable, so out of scope | 3 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 12 | | Tagged units | 12 | | Recorded audit verdicts | 0 | | Discrimination records | 1 | | Summary | `rfc/short/rfc5305.md` | | Requirement shard | `rfc/requirements/rfc5305.md` | | RFC text | `rfc/full/rfc5305.txt` | ## Enrolment Enrolled: IS-IS Extensions for Traffic Engineering (TLV 22 extended IS reachability, TLV 135 extended IP reachability): eight MUST-level requirements. Five are met with positive+negative tags in internal/plugins/isis: 3-1 (a maximum-metric link is not considered in normal SPF), 3-2 (SHALL clamp/saturate the wide path metric), 4-1 (do not install a route at or above MaxPathMetric), 4.1-1 (set the up/down bit correctly on a down-leak), 2-1 (retain unknown sub-TLVs without rejecting). 3.2-1, 3.2-2, 4.3-1 are {not-applicable}: ze implements no IS-IS TE sub-TLVs (6/8) or TLV 134. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** - 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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc5305.md). **What the ledger says remains:** TE sub-TLVs (6/8) and TLV 134 (TE Router ID) are not implemented (no IS-IS TE). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 5 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **8** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (5):** [`RFC5305-3-1`](#rfc5305-3-1), [`RFC5305-3-2`](#rfc5305-3-2), [`RFC5305-4-1`](#rfc5305-4-1), [`RFC5305-4.1-1`](#rfc5305-4.1-1), [`RFC5305-2-1`](#rfc5305-2-1) **Annotated instead of tested (3):** [`RFC5305-3.2-1`](#rfc5305-3.2-1), [`RFC5305-3.2-2`](#rfc5305-3.2-2), [`RFC5305-4.3-1`](#rfc5305-4.3-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5305-3-1` | Use a TLV 22 link advertised with metric 2^24 minus 1 in normal SPF (Section 3) | MUST NOT | 3 | **positive:** `unit/verify` [`TestISISSPFMaxLinkMetricExcluded`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L317). **negative:** `unit/verify` [`TestISISSPFMaxLinkMetricExcluded`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L318) | | `RFC5305-3-2` | Clamp metrics at or above MAX_PATH_METRIC (0xFE000000) to MAX_PATH_METRIC (Section 3, Section 3.7) | SHALL | 3 | **positive:** `unit/verify` [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L252). **negative:** `unit/verify` [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L253) | | `RFC5305-4-1` | Consider a TLV 135 prefix with metric above MAX_PATH_METRIC in normal SPF (Section 4) | MUST NOT | 4 | **positive:** `unit/verify` [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L295). **negative:** `unit/verify` [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L300) | | `RFC5305-4.1-1` | Set the TLV 135 up/down bit to 1 when advertising down the hierarchy or across same-level areas (Section 4.1) | MUST | 4.1 | **positive:** `unit/verify` [`TestISISEngineLeakOrigination`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb_wiring_test.go#L255). **negative:** `unit/verify` [`TestISISEngineLeakOrigination`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb_wiring_test.go#L266). **negative:** `unit/verify` [`TestISISRedistConsumerConnected`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/redistribute/consumer_test.go#L169) | | `RFC5305-3.2-1` | Inject sub-TLV 6 / sub-TLV 8 / Router-ID addresses as /32 routes (Section 3.2, Section 3.3, Section 4.3) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze never decodes the RFC 5305 TE sub-TLVs (sub-TLV 6/8) or TLV 134 into an address; internal/plugins/isis/spf/graph.go:192-194 reads only edges and drops sub-TLVs, and route.go:155 installs only node.Prefixes, so there is no per-link /32 injection code path | | `RFC5305-3.2-2` | Include sub-TLV 6 (and sub-TLV 8 on point-to-point) when implementing TE (Section 3.2, Section 3.3) | MUST | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this obligation is conditional on implementing IS-IS TE; ze originates no TLV 22 TE sub-TLVs (internal/plugins/isis/lsdb/encode.go:101-107 writes a zero sub-TLV length), so it does not implement the TE metric it would govern | | `RFC5305-4.3-1` | Include the TE Router ID TLV (134) when implementing TE (Section 4.3) | MUST | 4.3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this obligation is conditional on implementing IS-IS TE; TLV 134 (TE Router ID) is absent from ze's IS-IS codec type set (internal/plugins/isis/packet/tlv.go:17-32), so ze originates and consumes no TLV 134 | | `RFC5305-2-1` | Ignore and skip unknown sub-TLVs on receipt (Section 2) | MUST | 2 | **positive:** `unit/verify` [`TestISISTLV22RoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_core_test.go#L185). **positive:** `unit/verify` [`TestISISTLVIPv4RoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_ipv4_test.go#L85). **negative:** `unit/verify` [`TestISISTLV22Truncated`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_core_test.go#L205) | | `RFC5305-3.1-1` | Include each optional sub-TLV (3, 9, 10, 11, 18) at most once per TLV 22 (Section 3.1, Section 3.4 through Section 3.7) | SHOULD | 3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5305-4.3-2` | Include the TE Router ID TLV more than once per LSP (Section 4.3) | SHOULD NOT | 4.3 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5305-3.2-1`](#rfc5305-3.2-1) Inject sub-TLV 6 / sub-TLV 8 / Router-ID addresses as /32 routes (Section 3.2, Section 3.3, Section 4.3) | no test | no test carries this requirement id; annotated {not-applicable}: ze never decodes the RFC 5305 TE sub-TLVs (sub-TLV 6/8) or TLV 134 into an address; internal/plugins/isis/spf/graph.go:192-194 reads only edges and drops sub-TLVs, and route.go:155 installs only node.Prefixes, so there is no per-link /32 injection code path | | [`RFC5305-3.2-2`](#rfc5305-3.2-2) Include sub-TLV 6 (and sub-TLV 8 on point-to-point) when implementing TE (Section 3.2, Section 3.3) | no test | no test carries this requirement id; annotated {not-applicable}: this obligation is conditional on implementing IS-IS TE; ze originates no TLV 22 TE sub-TLVs (internal/plugins/isis/lsdb/encode.go:101-107 writes a zero sub-TLV length), so it does not implement the TE metric it would govern | | [`RFC5305-4.3-1`](#rfc5305-4.3-1) Include the TE Router ID TLV (134) when implementing TE (Section 4.3) | no test | no test carries this requirement id; annotated {not-applicable}: this obligation is conditional on implementing IS-IS TE; TLV 134 (TE Router ID) is absent from ze's IS-IS codec type set (internal/plugins/isis/packet/tlv.go:17-32), so ze originates and consumes no TLV 134 | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5305-3-1`](#rfc5305-3-1) Use a TLV 22 link advertised with metric 2^24 minus 1 in normal SPF (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISSPFMaxLinkMetricExcluded`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L318) | unit/verify | mutant, verified | | positive | [`TestISISSPFMaxLinkMetricExcluded`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L317) | unit/verify | unproven | ### [`RFC5305-3-2`](#rfc5305-3-2) Clamp metrics at or above MAX_PATH_METRIC (0xFE000000) to MAX_PATH_METRIC (Section 3, Section 3.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L253) | unit/verify | unproven | | positive | [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L252) | unit/verify | unproven | ### [`RFC5305-4-1`](#rfc5305-4-1) Consider a TLV 135 prefix with metric above MAX_PATH_METRIC in normal SPF (Section 4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L300) | unit/verify | unproven | | positive | [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L295) | unit/verify | unproven | ### [`RFC5305-4.1-1`](#rfc5305-4.1-1) Set the TLV 135 up/down bit to 1 when advertising down the hierarchy or across same-level areas (Section 4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISEngineLeakOrigination`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb_wiring_test.go#L266) | unit/verify | unproven | | negative | [`TestISISRedistConsumerConnected`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/redistribute/consumer_test.go#L169) | unit/verify | unproven | | positive | [`TestISISEngineLeakOrigination`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb_wiring_test.go#L255) | unit/verify | unproven | ### [`RFC5305-3.2-1`](#rfc5305-3.2-1) Inject sub-TLV 6 / sub-TLV 8 / Router-ID addresses as /32 routes (Section 3.2, Section 3.3, Section 4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5305-3.2-1, so no unit is bound to it. ### [`RFC5305-3.2-2`](#rfc5305-3.2-2) Include sub-TLV 6 (and sub-TLV 8 on point-to-point) when implementing TE (Section 3.2, Section 3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5305-3.2-2, so no unit is bound to it. ### [`RFC5305-4.3-1`](#rfc5305-4.3-1) Include the TE Router ID TLV (134) when implementing TE (Section 4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5305-4.3-1, so no unit is bound to it. ### [`RFC5305-2-1`](#rfc5305-2-1) Ignore and skip unknown sub-TLVs on receipt (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISTLV22Truncated`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_core_test.go#L205) | unit/verify | unproven | | positive | [`TestISISTLV22RoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_core_test.go#L185) | unit/verify | unproven | | positive | [`TestISISTLVIPv4RoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/tlv_ipv4_test.go#L85) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5305, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5305, so its obligations are stated where they were written. --- ### Page: RFC 5308 - Routing IPv6 with IS-IS https://ze-software.net/quality/rfc-compliance/rfc5308/ # RFC 5308 - Routing IPv6 with IS-IS Experimental. Every requirement this repository extracted from RFC 5308, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 100.0% | 7 of 7 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 7 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 7 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 7 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 18 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 7 | of 8 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 7 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 7 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 7 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 7 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 7 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 8 | | Gated MUST-level | 7 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 18 | | Tagged units | 18 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5308.md` | | Requirement shard | `rfc/requirements/rfc5308.md` | | RFC text | `rfc/full/rfc5308.txt` | ## Enrolment Enrolled: Routing IPv6 with IS-IS: seven MUST-level requirements, all met and test-bound with positive+negative tags. 2-1 (no link-local in IPv6 Reachability TLV 236) and 3-2 (no link-local in the LSP IPv6 address set) via internal/plugins/isis/lsdb origination tests; 3-1 (IPv6 Interface Address TLV 232 only for link-local in Hellos) and 4-1 (advertise IPv6 NLPID 0x8E in Protocols Supported) via internal/plugins/isis/circuit tests; 2-2 (do not route a metric above MaxV6PathMetric), 5-1 (up/down and level-aware preference), and 5-2 (clamp the path metric) via internal/plugins/isis/spf tests. Single-topology IS-IS: IPv6 rides the shared per-level SPF, so 5-1/5-2 reuse the IPv4 producers exercised for IPv6 via BuildRoutesV6. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** IPv6 reachability over the same IS-IS instance. **What the ledger says remains:** Same IS-IS experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 7 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **7** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (7):** [`RFC5308-2-1`](#rfc5308-2-1), [`RFC5308-2-2`](#rfc5308-2-2), [`RFC5308-3-1`](#rfc5308-3-1), [`RFC5308-3-2`](#rfc5308-3-2), [`RFC5308-4-1`](#rfc5308-4-1), [`RFC5308-5-1`](#rfc5308-5-1), [`RFC5308-5-2`](#rfc5308-5-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5308-2-1` | Advertise link-local prefixes in TLV 236 (Section 2) | MUST NOT | 2 | **positive:** `unit/verify` [`TestISISOriginateTLV236`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L65). **negative:** `unit/verify` [`TestISISOriginateTLV236`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L66) | | `RFC5308-2-2` | Consider a TLV 236 prefix with metric above MAX_V6_PATH_METRIC (0xFE000000) in normal SPF (Section 2) | MUST NOT | 2 | **positive:** `unit/verify` [`TestISISIPv6MetricAboveMaxIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/ipv6_test.go#L207). **negative:** `unit/verify` [`TestISISIPv6MetricAboveMaxIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/ipv6_test.go#L206) | | `RFC5308-3-1` | Carry only link-local IPv6 addresses in TLV 232 in Hellos (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestISISIIHTLV232LinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L30). **negative:** `unit/verify` [`TestISISIIHTLV232OmittedNoLinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L103). **negative:** `unit/verify` [`TestISISIIHTLV232RejectsNonLinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L130) | | `RFC5308-3-2` | Carry only non-link-local IPv6 addresses in TLV 232 in LSPs (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestISISOriginateTLV232Scope`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L110). **negative:** `unit/verify` [`TestISISOriginateTLV232Scope`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L111) | | `RFC5308-4-1` | Advertise the IPv6 NLPID (142, 0x8E) in the NLPID TLV when supporting IPv6 (Section 4) | MUST | 4 | **positive:** `unit/verify` [`TestISISIIHTLV232LinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L31). **positive:** `unit/verify` [`TestISISProtocolsSupportedDualStack`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L148). **negative:** `unit/verify` [`TestISISIIHNoTLV232WhenIPv4Only`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L78). **negative:** `unit/verify` [`TestISISProtocolsSupportedDualStack`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L149) | | `RFC5308-5-1` | Apply the up/down-aware path preference order (Level 1 up, Level 2 up, Level 2 down, Level 1 down) (Section 5) | MUST | 5 | **positive:** `unit/verify` [`TestISISIPv6LevelArbitration`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/ipv6_test.go#L91). **positive:** `unit/verify` [`TestISISLeakUpDownBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/route_test.go#L46). **negative:** `unit/verify` [`TestISISLeakUpDownBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/route_test.go#L47) | | `RFC5308-5-2` | Clamp an SPF path metric that would exceed MAX_V6_PATH_METRIC to MAX_V6_PATH_METRIC (Section 5) | SHALL | 5 | **positive:** `unit/verify` [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L250). **negative:** `unit/verify` [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L251) | | `RFC5308-5-3` | Consider equal-best paths for equal-cost multi-path routing where supported (Section 5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 5308 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5308-2-1`](#rfc5308-2-1) Advertise link-local prefixes in TLV 236 (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISOriginateTLV236`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L66) | unit/verify | unproven | | positive | [`TestISISOriginateTLV236`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L65) | unit/verify | unproven | ### [`RFC5308-2-2`](#rfc5308-2-2) Consider a TLV 236 prefix with metric above MAX_V6_PATH_METRIC (0xFE000000) in normal SPF (Section 2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISIPv6MetricAboveMaxIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/ipv6_test.go#L206) | unit/verify | unproven | | positive | [`TestISISIPv6MetricAboveMaxIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/ipv6_test.go#L207) | unit/verify | unproven | ### [`RFC5308-3-1`](#rfc5308-3-1) Carry only link-local IPv6 addresses in TLV 232 in Hellos (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISIIHTLV232OmittedNoLinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L103) | unit/verify | unproven | | negative | [`TestISISIIHTLV232RejectsNonLinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L130) | unit/verify | unproven | | positive | [`TestISISIIHTLV232LinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L30) | unit/verify | unproven | ### [`RFC5308-3-2`](#rfc5308-3-2) Carry only non-link-local IPv6 addresses in TLV 232 in LSPs (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISOriginateTLV232Scope`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L111) | unit/verify | unproven | | positive | [`TestISISOriginateTLV232Scope`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L110) | unit/verify | unproven | ### [`RFC5308-4-1`](#rfc5308-4-1) Advertise the IPv6 NLPID (142, 0x8E) in the NLPID TLV when supporting IPv6 (Section 4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISIIHNoTLV232WhenIPv4Only`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L78) | unit/verify | unproven | | negative | [`TestISISProtocolsSupportedDualStack`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L149) | unit/verify | unproven | | positive | [`TestISISIIHTLV232LinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/hello_ipv6_test.go#L31) | unit/verify | unproven | | positive | [`TestISISProtocolsSupportedDualStack`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/lsdb/origination_ipv6_test.go#L148) | unit/verify | unproven | ### [`RFC5308-5-1`](#rfc5308-5-1) Apply the up/down-aware path preference order (Level 1 up, Level 2 up, Level 2 down, Level 1 down) (Section 5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISLeakUpDownBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/route_test.go#L47) | unit/verify | unproven | | positive | [`TestISISIPv6LevelArbitration`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/ipv6_test.go#L91) | unit/verify | unproven | | positive | [`TestISISLeakUpDownBit`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/route_test.go#L46) | unit/verify | unproven | ### [`RFC5308-5-2`](#rfc5308-5-2) Clamp an SPF path metric that would exceed MAX_V6_PATH_METRIC to MAX_V6_PATH_METRIC (Section 5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L251) | unit/verify | unproven | | positive | [`TestISISMetricWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/spf/spf_test.go#L250) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5308, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5308, so its obligations are stated where they were written. --- ### Page: RFC 5310 - IS-IS Generic Cryptographic Authentication https://ze-software.net/quality/rfc-compliance/rfc5310/ # RFC 5310 - IS-IS Generic Cryptographic Authentication Experimental. Every requirement this repository extracted from RFC 5310, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 44.4% | 4 of 9 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 44.4% | 4 of 9 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 9 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 9 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 16 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 9 | of 12 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 9 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 11.1% | 1 of 9 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 9 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 9 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 9 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 12 | | Gated MUST-level | 9 | | Not applicable, so out of scope | 1 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 16 | | Tagged units | 16 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5310.md` | | Requirement shard | `rfc/requirements/rfc5310.md` | | RFC text | `rfc/full/rfc5310.txt` | ## Enrolment Enrolled: IS-IS Generic Cryptographic Authentication (HMAC-SHA, Authentication TLV type 3): nine MUST-level requirements. Eight are met with tags in internal/plugins/isis (sharing the auth backend enrolled for RFC 5304): 3.2-1 and 3.2-2 (Level-1 area and Level-2 domain authentication with the correct key chain), 3.2-3 (Hello/IIH link-level authentication), and 4-2 (accept a PDU signed by any currently-valid key during a key rollover) carry positive+negative tags; 3.2-5 (sign over the padded PDU), 3.4-1 (the HMAC pre-image includes the auth-type byte and TLV length with Apad in the value region), 3.4-2 (pad then sign), and 4-1 (the LSP HMAC excludes the Checksum and Remaining Lifetime) are {single-polarity: positive} (no reject path exists for these construction properties). 3.2-6 (do not include the auth value in the optional per-PDU Checksum TLV) is {not-applicable}: ze implements no such optional Checksum TLV. ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** HMAC-SHA authentication path. **What the ledger says remains:** Same IS-IS experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 5 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **9** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`RFC5310-3.2-1`](#rfc5310-3.2-1), [`RFC5310-3.2-2`](#rfc5310-3.2-2), [`RFC5310-3.2-3`](#rfc5310-3.2-3), [`RFC5310-4-2`](#rfc5310-4-2) **Annotated instead of tested (5):** [`RFC5310-3.2-5`](#rfc5310-3.2-5), [`RFC5310-3.2-6`](#rfc5310-3.2-6), [`RFC5310-3.4-1`](#rfc5310-3.4-1), [`RFC5310-3.4-2`](#rfc5310-3.4-2), [`RFC5310-4-1`](#rfc5310-4-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5310-3.2-1` | Level 1 Sequence Number PDUs "SHALL use the Area Authentication string, as in Level 1 Link State PDUs" (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISAuthEngineSignLevel`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L114). **negative:** `unit/verify` [`TestISISAuthChainSelection`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L204) | | `RFC5310-3.2-2` | Level 2 Sequence Number PDUs shall use the domain authentication string, as in Level 2 Link State PDUs (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISAuthEngineSignLevel`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L118). **negative:** `unit/verify` [`TestISISAuthLevelChainCrossUseL2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L286) | | `RFC5310-3.2-3` | "IS-IS HELLO PDUs SHALL use the Link Level Authentication string" (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISAuthReject`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L158). **negative:** `unit/verify` [`TestISISAuthReject`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L170) | | `RFC5310-3.2-5` | "The CRYPTO_AUTH result for the IS-IS HELLO PDUs SHALL be calculated after the PDU is padded to the MTU size, if padding is not disabled" (§3.2) | SHALL | 3.2 | **positive:** `unit/verify` [`TestISISHelloSignedOverPaddedPDU`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/runtime_test.go#L72). **negative:** no negative test. **{single-polarity}:** ze always pads the IIH before signing it (circuit/runtime.go:273-276 for LAN, :293-304 for P2P), so there is no sign-before-pad code path and no negative (unpadded-sign) behavior to assert. The positive is proven in TestISISHelloSignedOverPaddedPDU: the bytes handed to the signer are already padded to MTU-LLC and carry Padding TLV 8 | | `RFC5310-3.2-6` | Implementations that support the optional checksum for the Sequence Number PDUs and IS-IS HELLO PDUs "MUST NOT include the Checksum TLV" (§3.2) | MUST NOT | 3.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the antecedent is false -- ze implements no optional IS-IS per-PDU Checksum TLV (internal/plugins/isis/packet/tlv.go has no TLV 12 codec), so this conditional MUST NOT has no applicable code path | | `RFC5310-3.4-1` | "An implementation MUST fill the authentication type and the length before the authentication data is computed" (§3.4) | MUST | 3.4 | **positive:** `unit/verify` [`TestISISAuthHMACSHAApadPreimage`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L234). **positive:** `unit/verify` [`TestISISAuthSignVerifyHMACSHA256`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L161). **negative:** no negative test. **{single-polarity}:** ze always builds the Authentication TLV with the auth-type byte and sets the TLV length before the digest is computed (auth_sign.go:47-52), then runs the HMAC over the full signed PDU (auth_sign.go:274-276), so no code path omits the type or length from the pre-image and there is no negative to assert. The positive is proven by the known-answer TestISISAuthHMACSHAApadPreimage (the re-hashed pre-image still carries the type byte and length) and the on-wire type-3 round-trip TestISISAuthSignVerifyHMACSHA256 | | `RFC5310-3.4-2` | "The authentication data for the IS-IS IIH PDUs MUST be computed after the IS-IS Hello (IIH) has been padded to the MTU size, if padding is not explicitly disabled" (§3.4) | MUST | 3.4 | **positive:** `unit/verify` [`TestISISHelloSignedOverPaddedPDU`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/runtime_test.go#L75). **negative:** no negative test. **{single-polarity}:** ze pads the IIH before signing it (circuit/runtime.go:273-276 pads then signs), with no sign-before-pad code path, so no negative (auth-computed-before-padding) behavior exists to assert. The positive is proven in TestISISHelloSignedOverPaddedPDU: the signer receives the PDU already padded to MTU-LLC | | `RFC5310-4-1` | "the remaining lifetime of the LSP MUST be set to zero before computing the authentication", so that field is not authenticated (§4) | MUST | 4 | **positive:** `unit/verify` [`TestISISAuthLSPChecksumAfterSign`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L391). **positive:** `unit/verify` [`TestISISAuthRotationOverlapAccepts`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L666). **negative:** no negative test. **{single-polarity}:** ze always zeroes the Checksum and Remaining Lifetime before the LSP digest (auth_sign.go:268-272), so those fields are never part of the HMAC. The exclusion is observable only as non-rejection -- a post-sign Remaining-Lifetime change still verifies -- and there is no field-included code path that rejects, so no negative exists. The positive is proven in TestISISAuthLSPChecksumAfterSign and the HMAC-SHA-256 type-3 LSP round-trip TestISISAuthRotationOverlapAccepts | | `RFC5310-4-2` | "implementations MUST be able to store and use more than one key at the same time" (§4) | MUST | 4 | **positive:** `unit/verify` [`TestISISAuthRotation`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_keystore_test.go#L78). **positive:** `unit/verify` [`TestISISAuthRotationOverlapAccepts`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L663). **negative:** `unit/verify` [`TestISISAuthKeyIDMismatchRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L688). **negative:** `unit/verify` [`TestISISAuthWrongKeyRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L637) | | `RFC5310-3.5-1` | When the calculated data and the received authentication data do not match, the PDU is discarded and "an error event SHOULD be logged" (§3.5) | SHOULD | 3.5 | **positive:** no positive test. **negative:** no negative test | | `RFC5310-3.2-4` | The IS-IS HELLO PDU Link Level Authentication string "MAY be different from that of Link State PDUs" (§3.2) | MAY | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5310-3.5-2` | "An implementation MAY have a transition mode where it includes CRYPTO_AUTH information in the PDUs but does not verify this information" as a migration aid (§3.5) | MAY | 3.5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5310-3.2-6`](#rfc5310-3.2-6) Implementations that support the optional checksum for the Sequence Number PDUs and IS-IS HELLO PDUs "MUST NOT include the Checksum TLV" (§3.2) | no test | no test carries this requirement id; annotated {not-applicable}: the antecedent is false -- ze implements no optional IS-IS per-PDU Checksum TLV (internal/plugins/isis/packet/tlv.go has no TLV 12 codec), so this conditional MUST NOT has no applicable code path | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5310-3.2-1`](#rfc5310-3.2-1) Level 1 Sequence Number PDUs "SHALL use the Area Authentication string, as in Level 1 Link State PDUs" (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthChainSelection`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L204) | unit/verify | unproven | | positive | [`TestISISAuthEngineSignLevel`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L114) | unit/verify | unproven | ### [`RFC5310-3.2-2`](#rfc5310-3.2-2) Level 2 Sequence Number PDUs shall use the domain authentication string, as in Level 2 Link State PDUs (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthLevelChainCrossUseL2`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L286) | unit/verify | unproven | | positive | [`TestISISAuthEngineSignLevel`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L118) | unit/verify | unproven | ### [`RFC5310-3.2-3`](#rfc5310-3.2-3) "IS-IS HELLO PDUs SHALL use the Link Level Authentication string" (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthReject`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L170) | unit/verify | unproven | | positive | [`TestISISAuthReject`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_wiring_test.go#L158) | unit/verify | unproven | ### [`RFC5310-3.2-5`](#rfc5310-3.2-5) "The CRYPTO_AUTH result for the IS-IS HELLO PDUs SHALL be calculated after the PDU is padded to the MTU size, if padding is not disabled" (§3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISHelloSignedOverPaddedPDU`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/runtime_test.go#L72) | unit/verify | unproven | ### [`RFC5310-3.2-6`](#rfc5310-3.2-6) Implementations that support the optional checksum for the Sequence Number PDUs and IS-IS HELLO PDUs "MUST NOT include the Checksum TLV" (§3.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5310-3.2-6, so no unit is bound to it. ### [`RFC5310-3.4-1`](#rfc5310-3.4-1) "An implementation MUST fill the authentication type and the length before the authentication data is computed" (§3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISAuthHMACSHAApadPreimage`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L234) | unit/verify | unproven | | positive | [`TestISISAuthSignVerifyHMACSHA256`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L161) | unit/verify | unproven | ### [`RFC5310-3.4-2`](#rfc5310-3.4-2) "The authentication data for the IS-IS IIH PDUs MUST be computed after the IS-IS Hello (IIH) has been padded to the MTU size, if padding is not explicitly disabled" (§3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISHelloSignedOverPaddedPDU`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/circuit/runtime_test.go#L75) | unit/verify | unproven | ### [`RFC5310-4-1`](#rfc5310-4-1) "the remaining lifetime of the LSP MUST be set to zero before computing the authentication", so that field is not authenticated (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestISISAuthLSPChecksumAfterSign`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L391) | unit/verify | unproven | | positive | [`TestISISAuthRotationOverlapAccepts`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L666) | unit/verify | unproven | ### [`RFC5310-4-2`](#rfc5310-4-2) "implementations MUST be able to store and use more than one key at the same time" (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestISISAuthKeyIDMismatchRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L688) | unit/verify | unproven | | negative | [`TestISISAuthWrongKeyRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L637) | unit/verify | unproven | | positive | [`TestISISAuthRotation`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/auth_keystore_test.go#L78) | unit/verify | unproven | | positive | [`TestISISAuthRotationOverlapAccepts`](https://github.com/ze-software/ze/blob/main/internal/plugins/isis/packet/auth_verify_test.go#L663) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5310, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5310, so its obligations are stated where they were written. --- ### Page: RFC 5340 - OSPF for IPv6 https://ze-software.net/quality/rfc-compliance/rfc5340/ # RFC 5340 - OSPF for IPv6 Partial. Every requirement this repository extracted from RFC 5340, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 69.6% | 16 of 23 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 4.3% | 1 of 23 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 23 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 33 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 23 | of 46 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 23 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 4.3% | 1 of 23 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 23 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 23 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 21.7% | 5 of 23 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 23 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 46 | | Gated MUST-level | 23 | | Not applicable, so out of scope | 1 | | Declared gaps | 5 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 33 | | Tagged units | 33 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5340.md` | | Requirement shard | `rfc/requirements/rfc5340.md` | | RFC text | `rfc/full/rfc5340.txt` | ## Enrolment Enrolled: OSPF for IPv6 (OSPFv3) ## What the public ledger says **Status:** Partial **What the ledger says is covered** 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`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5340.md). **What the ledger says remains** Five MUST gaps, annotated in [`rfc/short/rfc5340.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5340.md) and gated by `./le rfc check`. [`RFC5340-2.5-2`](#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`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_summary.go)) and the ASBR redistribution path ([`internal/plugins/ospf/origination_v6_external.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/origination_v6_external.go)) apply no link-local filter. [`RFC5340-2.8-2`](#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`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6.go)). [`RFC5340-4.2.2-1`](#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`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/dispatcher.go)). [`RFC5340-4.9-1`](#rfc5340-4.9-1) and [`RFC5340-4.9-2`](#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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 16 | one part of the gated population | | Annotated instead of tested | 7 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **23** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (16):** [`RFC5340-2.5-1`](#rfc5340-2.5-1), [`RFC5340-2.8-3`](#rfc5340-2.8-3), [`RFC5340-2.8-4`](#rfc5340-2.8-4), [`RFC5340-4.1.2-2`](#rfc5340-4.1.2-2), [`RFC5340-4.2.1.1-1`](#rfc5340-4.2.1.1-1), [`RFC5340-4.2.1.1-2`](#rfc5340-4.2.1.1-2), [`RFC5340-4.2.1.2-1`](#rfc5340-4.2.1.2-1), [`RFC5340-4.2.2-5`](#rfc5340-4.2.2-5), [`RFC5340-A.3.1-2`](#rfc5340-a.3.1-2), [`RFC5340-A.4.7-1`](#rfc5340-a.4.7-1), [`RFC5340-A.4.7-2`](#rfc5340-a.4.7-2), [`RFC5340-A.4.8-1`](#rfc5340-a.4.8-1), [`RFC5340-C.3-1`](#rfc5340-c.3-1), [`RFC5340-C.3-2`](#rfc5340-c.3-2), [`RFC5340-C.3-3`](#rfc5340-c.3-3), [`RFC5340-C.3-4`](#rfc5340-c.3-4) **Annotated instead of tested (7):** [`RFC5340-2.5-2`](#rfc5340-2.5-2), [`RFC5340-2.8-2`](#rfc5340-2.8-2), [`RFC5340-4.2.2-1`](#rfc5340-4.2.2-1), [`RFC5340-4.2.2-2`](#rfc5340-4.2.2-2), [`RFC5340-4.2.2-3`](#rfc5340-4.2.2-3), [`RFC5340-4.9-1`](#rfc5340-4.9-1), [`RFC5340-4.9-2`](#rfc5340-4.9-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5340-2.5-1` | On virtual links, a global scope IPv6 address MUST be used as the source address for OSPF protocol packets (§2.5) | MUST | 2.5 | **positive:** `unit/verify` [`TestV6VirtualEndpointResolvesGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L439). **negative:** `unit/verify` [`TestV6VirtualEndpointRequiresGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L475) | | `RFC5340-2.5-2` | Link-local addresses MUST NOT be advertised in inter-area-prefix-LSAs, AS-external-LSAs, NSSA-LSAs, or intra-area-prefix-LSAs; restated for inter-area-prefix-LSAs in §4.4.3.4 (§2.5) | MUST NOT | 2.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the ban holds for intra-area-prefix-LSAs on both origination paths (interfaceIPv6Prefixes, origination_v6.go:564; v6HostPrefixes, origination_v6.go:432; v6AggregatedLinkPrefixes, origination_v6_link.go:153), but the ABR summary path copies every prefix out of a received intra-area-prefix-LSA into an inter-area-prefix-LSA with no link-local filter (v6SummaryNetworks, origination_v6_summary.go:136-147), and the ASBR path wire-encodes a redistributed prefix with no link-local filter either (v6InjectExternal -> netipToV6Prefix, origination_v6_external.go:55 and origination_v6.go:592), so a link-local supplied by a peer or by redistribution reaches an inter-area-prefix-LSA / AS-external-LSA / NSSA-LSA. Disclosed in docs/features/rfc-status.md RFC 5340 row | | `RFC5340-2.8-2` | Receivers MUST concatenate all the router-LSAs originated by a given router, treating them as a single aggregate, when running the SPF calculation; reaffirmed in §4.8 and §4.8.1 (§2.8) | MUST | 2.8 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the OSPFv3 graph build keys a router vertex by Advertising Router alone and ASSIGNS rather than concatenates, so a second Router-LSA from the same router (a different Link State ID) replaces the first instead of aggregating its links (v6Strategy.BuildGraph, afstrategy_v6.go:113-117). Disclosed in docs/features/rfc-status.md RFC 5340 row | | `RFC5340-2.8-3` | A network-LSA MUST list all routers connected to the link (§2.8) | MUST | 2.8 | **positive:** `unit/verify` [`TestRFC5340NetworkLSAListsAttachedRouters`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L183). **negative:** `unit/verify` [`TestRFC5340NetworkLSAListsAttachedRouters`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L186) | | `RFC5340-2.8-4` | A link-LSA MUST list all of a router's addresses on the link (§2.8) | MUST | 2.8 | **positive:** `unit/verify` [`TestRFC5340LinkLSAListsLinkAddresses`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L221). **negative:** `unit/verify` [`TestRFC5340LinkLSAListsLinkAddresses`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L225) | | `RFC5340-4.1.2-2` | A virtual link MUST use one of the router's own global-scope IPv6 addresses as its IP interface address, instead of a link-local address; also §4.7 (§4.1.2) | MUST | 4.1.2 | **positive:** `unit/verify` [`TestV6VirtualEndpointResolvesGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L444). **negative:** `unit/verify` [`TestRFC5340VirtualLinkRefusesLocalLinkLocalInterfaceAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_vlink_test.go#L14) | | `RFC5340-4.2.1.1-1` | Before a Hello packet is sent on an interface, the interface's Interface ID MUST be copied into the Hello packet (§4.2.1.1) | MUST | 4.2.1.1 | **positive:** `unit/verify` [`TestRFC5340HelloCarriesInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L74). **negative:** `unit/verify` [`TestRFC5340HelloCarriesInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L80) | | `RFC5340-4.2.1.1-2` | The Options bits that MUST be set correctly in Hello packets are the E-bit (regular area), N-bit (NSSA area), and DC-bit (demand circuit) (§4.2.1.1) | MUST | 4.2.1.1 | **positive:** `unit/verify` [`TestRFC5340HelloOptionsBits`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L119). **negative:** `unit/verify` [`TestRFC5340HelloOptionsBits`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L123) | | `RFC5340-4.2.1.2-1` | The Options bits that MUST be set correctly in Database Description packets include the DC-bit for demand circuits (§4.2.1.2) | MUST | 4.2.1.2 | **positive:** `unit/verify` [`TestRFC5340DBDescOptionsBits`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L154). **negative:** `unit/verify` [`TestRFC5340DBDescOptionsBits`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L158) | | `RFC5340-4.2.2-1` | A received packet's IP destination address MUST be a unicast address of the receiving interface, the AllSPFRouters or AllDRouters multicast address, or (for virtual links) an IPv6 global address (§4.2.2) | MUST | 4.2.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** no destination-address acceptance check exists. The v3 backend records the datagram destination from the IPV6_PKTINFO control message (backend_linux.go:307) and the dispatcher consumes it ONLY as pseudo-header input to the checksum (dispatcher.go:65), never comparing it against the interface's addresses or ff02::5 / ff02::6; grep for `.Dst` over internal/plugins/ospf finds no other reader. A protocol-89 datagram sent to a group the host already joins (for example ff02::1) is therefore accepted, since its sender computed a checksum for that same destination. Disclosed in docs/features/rfc-status.md RFC 5340 row | | `RFC5340-4.2.2-2` | The Next Header field of the immediately encapsulating IPv6 header MUST specify the OSPF protocol (89) (§4.2.2) | MUST | 4.2.2 | **positive:** `unit/verify` [`TestRFC5340TransportUsesOSPFProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/v3/transport/rfc5340_linux_test.go#L13). **negative:** no negative test. **{single-polarity}:** the transport opens its raw socket on "ip6:89" (listenNetwork, v3/transport/backend_linux.go:28), so the kernel stamps Next Header 89 on every send and demultiplexes only Next Header 89 to this socket on receive. ze never sees a non-89 datagram, so it has no reject path of its own to exercise | | `RFC5340-4.2.2-3` | Any encapsulating IP Authentication Headers and IP Encapsulating Security Payloads MUST be processed and/or verified to ensure integrity and authentication/confidentiality (§4.2.2) | MUST | 4.2.2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** AH and ESP are processed by the kernel XFRM inbound transform before the datagram reaches the socket; ze installs the require-policy that makes that happen (buildIPsecPolicies SADirIn, ipsec_install.go:449) and only samples the resulting drop counters (readXfrmDropsPlatform, ipsec_drops_linux.go:32). ze never parses or verifies an AH/ESP header, matching the RFC 4552 rows for the same delegation | | `RFC5340-4.2.2-5` | The version number field MUST specify protocol version 3 (§4.2.2) | MUST | 4.2.2 | **positive:** `unit/verify` [`TestOSPFv3HeaderRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/v3/packet/header_test.go#L31). **negative:** `unit/verify` [`TestOSPFv3DecodeHeaderBounds`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/v3/packet/header_test.go#L80) | | `RFC5340-4.9-1` | Each of a router's multiple interfaces to a single link MUST be configured with the same Interface Instance ID to be considered on the same link (§4.9) | MUST | 4.9 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze has no notion of several interfaces sharing one link. Each configured interface is enrolled independently, keyed by its own name and OS ifindex (openInterface, instance.go:679-691; the Interface ID is interfaceIndex, interface_addr.go:107-123), and two interfaces on the same physical link with the same Instance ID form two separate adjacencies rather than one Active/Standby pair. Disclosed in docs/features/rfc-status.md RFC 5340 row | | `RFC5340-4.9-2` | When a Standby Interface goes down, the link-local scope LSAs originated for it MUST be flushed on the Active Interface (§4.9) | MUST | 4.9 | **positive:** no positive test. **negative:** no negative test. **{gap}:** there is no Active/Standby interface model to flush from -- grep for "Standby" over internal/plugins/ospf finds no producer. A link-local scope Link-LSA is flushed only with its own interface's link store (v6OriginateLinkLSA / OriginateLinkSelf, origination_v6_link.go:58), never re-flushed onto a sibling interface. Disclosed in docs/features/rfc-status.md RFC 5340 row | | `RFC5340-A.3.1-2` | The reserved header fields MUST be ignored when receiving protocol packets (§A.3.1) | MUST | A.3.1 | **positive:** `unit/verify` [`TestRFC5340ReservedHeaderOctetIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L366). **negative:** `unit/verify` [`TestRFC5340ReservedHeaderOctetIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L370) | | `RFC5340-A.4.7-1` | An AS-external-LSA forwarding address MUST NOT be set to the IPv6 Unspecified Address or an IPv6 Link-Local Address (§A.4.7) | MUST NOT | A.4.7 | **positive:** `unit/verify` [`TestRFC5340ForwardingAddressIsGlobal`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L292). **negative:** `unit/verify` [`TestRFC5340ForwardingAddressIsGlobal`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L294) | | `RFC5340-A.4.7-2` | An OSPFv3 implementation advertising a forwarding address MUST advertise a global IPv6 address (§A.4.7) | MUST | A.4.7 | **positive:** `unit/verify` [`TestRFC5340ForwardingAddressIsGlobal`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L300). **negative:** `unit/verify` [`TestRFC5340ForwardingAddressIsGlobal`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L304) | | `RFC5340-A.4.8-1` | A global IPv6 address MUST be selected as the forwarding address for NSSA-LSAs that are to be propagated by NSSA area border routers (§A.4.8) | MUST | A.4.8 | **positive:** `unit/verify` [`TestRFC5340NSSAPropagationNeedsGlobalForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L336). **negative:** `unit/verify` [`TestRFC5340NSSAPropagationNeedsGlobalForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L339) | | `RFC5340-C.3-1` | The interface output cost MUST always be greater than 0 (§C.3) | MUST | C.3 | **positive:** `unit/verify` [`TestRFC5340IPv6InterfaceCostAndTransmitDelay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L423). **negative:** `unit/verify` [`TestRFC5340IPv6InterfaceCostAndTransmitDelay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L426) | | `RFC5340-C.3-2` | InfTransDelay MUST be greater than 0 (§C.3) | MUST | C.3 | **positive:** `unit/verify` [`TestRFC5340IPv6InterfaceCostAndTransmitDelay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L429). **negative:** `unit/verify` [`TestRFC5340IPv6InterfaceCostAndTransmitDelay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L432) | | `RFC5340-C.3-3` | HelloInterval MUST be the same for all routers attached to a common link (§C.3) | MUST | C.3 | **positive:** `unit/verify` [`TestRFC5340HelloAndDeadIntervalMustMatchOnTheLink`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L465). **negative:** `unit/verify` [`TestRFC5340HelloAndDeadIntervalMustMatchOnTheLink`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L468) | | `RFC5340-C.3-4` | RouterDeadInterval MUST be the same for all routers attached to a common link (§C.3) | MUST | C.3 | **positive:** `unit/verify` [`TestRFC5340HelloAndDeadIntervalMustMatchOnTheLink`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L471). **negative:** `unit/verify` [`TestRFC5340HelloAndDeadIntervalMustMatchOnTheLink`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L473) | | `RFC5340-2.11-1` | The Router ID of 0.0.0.0 is reserved and SHOULD NOT be used; restated in §C.1 (§2.11) | SHOULD NOT | 2.11 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.2.2-4` | If the OSPF header fields do not match those configured for the receiving OSPFv3 interface, the packet SHOULD be discarded (§4.2.2) | SHOULD | 4.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.2.2-6` | Locally originated packets SHOULD NOT be processed by OSPF, except in support of multiple interfaces attached to the same link per §4.9 (§4.2.2) | SHOULD NOT | 4.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.4.3.8-1` | A link-LSA SHOULD NOT be originated for a virtual link, which has no link-local address or associated prefixes; also §4.7 (§4.4.3.8) | SHOULD NOT | 4.4.3.8 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.4.3.9-2` | When building an intra-area-prefix-LSA, prefixes having the NU-bit and/or LA-bit set in their PrefixOptions SHOULD NOT be copied, nor should link-local addresses (§4.4.3.9) | SHOULD NOT | 4.4.3.9 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.4.3.9-3` | Prefixes that would normally have the LA-bit set SHOULD be advertised independent of whether the interface is advertised as a transit link (§4.4.3.9) | SHOULD | 4.4.3.9 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.8.1-1` | A prefix advertisement whose NU-bit is set SHOULD NOT be included in the routing calculation (§4.8.1) | SHOULD NOT | 4.8.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.9-3` | When the Active Interface fails, the new Active Interface SHOULD form all new neighbor adjacencies with routers on the link (§4.9) | SHOULD | 4.9 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-A.1-1` | The OSPF IP protocol number 89 SHOULD be inserted in the Next Header field of the encapsulating IPv6 header (§A.1) | SHOULD | A.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-A.1-2` | Where the IPv6 Traffic Class is mapped to DSCP, OSPFv3 packets SHOULD be sent with their DSCP set to CS6 (§A.1) | SHOULD | A.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-A.3.1-1` | The reserved header fields SHOULD be set to 0 when sending protocol packets (§A.3.1) | SHOULD | A.3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-2.8-1` | Router interface information MAY be spread across multiple router-LSAs (§2.8) | MAY | 2.8 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.1.2-1` | An implementation MAY use the MIB-II IfIndex as the Interface ID (§4.1.2) | MAY | 4.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.4.3.2-1` | A router MAY originate one or more router-LSAs for a given area (§4.4.3.2) | MAY | 4.4.3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.4.3.9-1` | A router MAY originate multiple intra-area-prefix-LSAs for a given area (§4.4.3.9) | MAY | 4.4.3.9 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.4.4-1` | Reachability validation for future non-SPF LSA types MAY be done less frequently than every SPF calculation (§4.4.4) | MAY | 4.4.4 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.5.2-1` | All LS types MAY not be understood by all routers (§4.5.2) | MAY | 4.5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-4.5.2-2` | A new LSA type with its U-bit set to 0 MAY only be understood by a subset of routers (§4.5.2) | MAY | 4.5.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-A.3.6-1` | Multiple LSAs MAY be acknowledged in a single Link State Acknowledgment packet (§A.3.6) | MAY | A.3.6 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-A.4.1.1-1` | An implementation MAY also set the LA-bit for prefixes advertised with a host PrefixLength (128) (§A.4.1.1) | MAY | A.4.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-A.4.7-3` | The External Route Tag is a 32-bit field that MAY be used to communicate additional information between AS boundary routers (§A.4.7) | MAY | A.4.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-A.4.7-4` | All, none, or some of the Forwarding Address, External Route Tag, and Referenced Link State ID fields MAY be present in the AS-external-LSA (§A.4.7) | MAY | A.4.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5340-C.3-5` | Future interface types MAY specify a different default for LinkLSASuppression (§C.3) | MAY | C.3 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5340-2.5-2`](#rfc5340-2.5-2) Link-local addresses MUST NOT be advertised in inter-area-prefix-LSAs, AS-external-LSAs, NSSA-LSAs, or intra-area-prefix-LSAs; restated for inter-area-prefix-LSAs in §4.4.3.4 (§2.5) | {gap}, no test | the ban holds for intra-area-prefix-LSAs on both origination paths (interfaceIPv6Prefixes, origination_v6.go:564; v6HostPrefixes, origination_v6.go:432; v6AggregatedLinkPrefixes, origination_v6_link.go:153), but the ABR summary path copies every prefix out of a received intra-area-prefix-LSA into an inter-area-prefix-LSA with no link-local filter (v6SummaryNetworks, origination_v6_summary.go:136-147), and the ASBR path wire-encodes a redistributed prefix with no link-local filter either (v6InjectExternal -> netipToV6Prefix, origination_v6_external.go:55 and origination_v6.go:592), so a link-local supplied by a peer or by redistribution reaches an inter-area-prefix-LSA / AS-external-LSA / NSSA-LSA. Disclosed in docs/features/rfc-status.md RFC 5340 row | | [`RFC5340-2.8-2`](#rfc5340-2.8-2) Receivers MUST concatenate all the router-LSAs originated by a given router, treating them as a single aggregate, when running the SPF calculation; reaffirmed in §4.8 and §4.8.1 (§2.8) | {gap}, no test | the OSPFv3 graph build keys a router vertex by Advertising Router alone and ASSIGNS rather than concatenates, so a second Router-LSA from the same router (a different Link State ID) replaces the first instead of aggregating its links (v6Strategy.BuildGraph, afstrategy_v6.go:113-117). Disclosed in docs/features/rfc-status.md RFC 5340 row | | [`RFC5340-4.2.2-1`](#rfc5340-4.2.2-1) A received packet's IP destination address MUST be a unicast address of the receiving interface, the AllSPFRouters or AllDRouters multicast address, or (for virtual links) an IPv6 global address (§4.2.2) | {gap}, no test | no destination-address acceptance check exists. The v3 backend records the datagram destination from the IPV6_PKTINFO control message (backend_linux.go:307) and the dispatcher consumes it ONLY as pseudo-header input to the checksum (dispatcher.go:65), never comparing it against the interface's addresses or ff02::5 / ff02::6; grep for `.Dst` over internal/plugins/ospf finds no other reader. A protocol-89 datagram sent to a group the host already joins (for example ff02::1) is therefore accepted, since its sender computed a checksum for that same destination. Disclosed in docs/features/rfc-status.md RFC 5340 row | | [`RFC5340-4.2.2-3`](#rfc5340-4.2.2-3) Any encapsulating IP Authentication Headers and IP Encapsulating Security Payloads MUST be processed and/or verified to ensure integrity and authentication/confidentiality (§4.2.2) | no test | no test carries this requirement id; annotated {not-applicable}: AH and ESP are processed by the kernel XFRM inbound transform before the datagram reaches the socket; ze installs the require-policy that makes that happen (buildIPsecPolicies SADirIn, ipsec_install.go:449) and only samples the resulting drop counters (readXfrmDropsPlatform, ipsec_drops_linux.go:32). ze never parses or verifies an AH/ESP header, matching the RFC 4552 rows for the same delegation | | [`RFC5340-4.9-1`](#rfc5340-4.9-1) Each of a router's multiple interfaces to a single link MUST be configured with the same Interface Instance ID to be considered on the same link (§4.9) | {gap}, no test | ze has no notion of several interfaces sharing one link. Each configured interface is enrolled independently, keyed by its own name and OS ifindex (openInterface, instance.go:679-691; the Interface ID is interfaceIndex, interface_addr.go:107-123), and two interfaces on the same physical link with the same Instance ID form two separate adjacencies rather than one Active/Standby pair. Disclosed in docs/features/rfc-status.md RFC 5340 row | | [`RFC5340-4.9-2`](#rfc5340-4.9-2) When a Standby Interface goes down, the link-local scope LSAs originated for it MUST be flushed on the Active Interface (§4.9) | {gap}, no test | there is no Active/Standby interface model to flush from -- grep for "Standby" over internal/plugins/ospf finds no producer. A link-local scope Link-LSA is flushed only with its own interface's link store (v6OriginateLinkLSA / OriginateLinkSelf, origination_v6_link.go:58), never re-flushed onto a sibling interface. Disclosed in docs/features/rfc-status.md RFC 5340 row | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5340-2.5-1`](#rfc5340-2.5-1) On virtual links, a global scope IPv6 address MUST be used as the source address for OSPF protocol packets (§2.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestV6VirtualEndpointRequiresGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L475) | unit/verify | unproven | | positive | [`TestV6VirtualEndpointResolvesGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L439) | unit/verify | unproven | ### [`RFC5340-2.5-2`](#rfc5340-2.5-2) Link-local addresses MUST NOT be advertised in inter-area-prefix-LSAs, AS-external-LSAs, NSSA-LSAs, or intra-area-prefix-LSAs; restated for inter-area-prefix-LSAs in §4.4.3.4 (§2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC5340-2.5-2, so no unit is bound to it. ### [`RFC5340-2.8-2`](#rfc5340-2.8-2) Receivers MUST concatenate all the router-LSAs originated by a given router, treating them as a single aggregate, when running the SPF calculation; reaffirmed in §4.8 and §4.8.1 (§2.8) Audit verdict: not audited: no reader has judged these tests No test carries RFC5340-2.8-2, so no unit is bound to it. ### [`RFC5340-2.8-3`](#rfc5340-2.8-3) A network-LSA MUST list all routers connected to the link (§2.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340NetworkLSAListsAttachedRouters`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L186) | unit/verify | unproven | | positive | [`TestRFC5340NetworkLSAListsAttachedRouters`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L183) | unit/verify | unproven | ### [`RFC5340-2.8-4`](#rfc5340-2.8-4) A link-LSA MUST list all of a router's addresses on the link (§2.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340LinkLSAListsLinkAddresses`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L225) | unit/verify | unproven | | positive | [`TestRFC5340LinkLSAListsLinkAddresses`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L221) | unit/verify | unproven | ### [`RFC5340-4.1.2-2`](#rfc5340-4.1.2-2) A virtual link MUST use one of the router's own global-scope IPv6 addresses as its IP interface address, instead of a link-local address; also §4.7 (§4.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340VirtualLinkRefusesLocalLinkLocalInterfaceAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_vlink_test.go#L14) | unit/verify | unproven | | positive | [`TestV6VirtualEndpointResolvesGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L444) | unit/verify | unproven | ### [`RFC5340-4.2.1.1-1`](#rfc5340-4.2.1.1-1) Before a Hello packet is sent on an interface, the interface's Interface ID MUST be copied into the Hello packet (§4.2.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340HelloCarriesInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L80) | unit/verify | unproven | | positive | [`TestRFC5340HelloCarriesInterfaceID`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L74) | unit/verify | unproven | ### [`RFC5340-4.2.1.1-2`](#rfc5340-4.2.1.1-2) The Options bits that MUST be set correctly in Hello packets are the E-bit (regular area), N-bit (NSSA area), and DC-bit (demand circuit) (§4.2.1.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340HelloOptionsBits`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L123) | unit/verify | unproven | | positive | [`TestRFC5340HelloOptionsBits`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L119) | unit/verify | unproven | ### [`RFC5340-4.2.1.2-1`](#rfc5340-4.2.1.2-1) The Options bits that MUST be set correctly in Database Description packets include the DC-bit for demand circuits (§4.2.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340DBDescOptionsBits`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L158) | unit/verify | unproven | | positive | [`TestRFC5340DBDescOptionsBits`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L154) | unit/verify | unproven | ### [`RFC5340-4.2.2-1`](#rfc5340-4.2.2-1) A received packet's IP destination address MUST be a unicast address of the receiving interface, the AllSPFRouters or AllDRouters multicast address, or (for virtual links) an IPv6 global address (§4.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5340-4.2.2-1, so no unit is bound to it. ### [`RFC5340-4.2.2-2`](#rfc5340-4.2.2-2) The Next Header field of the immediately encapsulating IPv6 header MUST specify the OSPF protocol (89) (§4.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5340TransportUsesOSPFProtocolNumber`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/v3/transport/rfc5340_linux_test.go#L13) | unit/verify | unproven | ### [`RFC5340-4.2.2-3`](#rfc5340-4.2.2-3) Any encapsulating IP Authentication Headers and IP Encapsulating Security Payloads MUST be processed and/or verified to ensure integrity and authentication/confidentiality (§4.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5340-4.2.2-3, so no unit is bound to it. ### [`RFC5340-4.2.2-5`](#rfc5340-4.2.2-5) The version number field MUST specify protocol version 3 (§4.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFv3DecodeHeaderBounds`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/v3/packet/header_test.go#L80) | unit/verify | unproven | | positive | [`TestOSPFv3HeaderRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/v3/packet/header_test.go#L31) | unit/verify | unproven | ### [`RFC5340-4.9-1`](#rfc5340-4.9-1) Each of a router's multiple interfaces to a single link MUST be configured with the same Interface Instance ID to be considered on the same link (§4.9) Audit verdict: not audited: no reader has judged these tests No test carries RFC5340-4.9-1, so no unit is bound to it. ### [`RFC5340-4.9-2`](#rfc5340-4.9-2) When a Standby Interface goes down, the link-local scope LSAs originated for it MUST be flushed on the Active Interface (§4.9) Audit verdict: not audited: no reader has judged these tests No test carries RFC5340-4.9-2, so no unit is bound to it. ### [`RFC5340-A.3.1-2`](#rfc5340-a.3.1-2) The reserved header fields MUST be ignored when receiving protocol packets (§A.3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340ReservedHeaderOctetIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L370) | unit/verify | unproven | | positive | [`TestRFC5340ReservedHeaderOctetIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L366) | unit/verify | unproven | ### [`RFC5340-A.4.7-1`](#rfc5340-a.4.7-1) An AS-external-LSA forwarding address MUST NOT be set to the IPv6 Unspecified Address or an IPv6 Link-Local Address (§A.4.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340ForwardingAddressIsGlobal`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L294) | unit/verify | unproven | | positive | [`TestRFC5340ForwardingAddressIsGlobal`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L292) | unit/verify | unproven | ### [`RFC5340-A.4.7-2`](#rfc5340-a.4.7-2) An OSPFv3 implementation advertising a forwarding address MUST advertise a global IPv6 address (§A.4.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340ForwardingAddressIsGlobal`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L304) | unit/verify | unproven | | positive | [`TestRFC5340ForwardingAddressIsGlobal`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L300) | unit/verify | unproven | ### [`RFC5340-A.4.8-1`](#rfc5340-a.4.8-1) A global IPv6 address MUST be selected as the forwarding address for NSSA-LSAs that are to be propagated by NSSA area border routers (§A.4.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340NSSAPropagationNeedsGlobalForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L339) | unit/verify | unproven | | positive | [`TestRFC5340NSSAPropagationNeedsGlobalForwardingAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L336) | unit/verify | unproven | ### [`RFC5340-C.3-1`](#rfc5340-c.3-1) The interface output cost MUST always be greater than 0 (§C.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340IPv6InterfaceCostAndTransmitDelay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L426) | unit/verify | unproven | | positive | [`TestRFC5340IPv6InterfaceCostAndTransmitDelay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L423) | unit/verify | unproven | ### [`RFC5340-C.3-2`](#rfc5340-c.3-2) InfTransDelay MUST be greater than 0 (§C.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340IPv6InterfaceCostAndTransmitDelay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L432) | unit/verify | unproven | | positive | [`TestRFC5340IPv6InterfaceCostAndTransmitDelay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L429) | unit/verify | unproven | ### [`RFC5340-C.3-3`](#rfc5340-c.3-3) HelloInterval MUST be the same for all routers attached to a common link (§C.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340HelloAndDeadIntervalMustMatchOnTheLink`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L468) | unit/verify | unproven | | positive | [`TestRFC5340HelloAndDeadIntervalMustMatchOnTheLink`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L465) | unit/verify | unproven | ### [`RFC5340-C.3-4`](#rfc5340-c.3-4) RouterDeadInterval MUST be the same for all routers attached to a common link (§C.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5340HelloAndDeadIntervalMustMatchOnTheLink`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L473) | unit/verify | unproven | | positive | [`TestRFC5340HelloAndDeadIntervalMustMatchOnTheLink`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/rfc5340_test.go#L471) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5340, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5340, so its obligations are stated where they were written. --- ### Page: RFC 5392 - OSPF Extensions in Support of Inter-Autonomous System (AS) MPLS and GMPLS Traffic Engineering https://ze-software.net/quality/rfc-compliance/rfc5392/ # RFC 5392 - OSPF Extensions in Support of Inter-Autonomous System (AS) MPLS and GMPLS Traffic Engineering Experimental. Every requirement this repository extracted from RFC 5392, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 25.0% | 3 of 12 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 41.7% | 5 of 12 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 12 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 11 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 12 | of 30 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 12 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 12 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 12 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 12 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 33.3% | 4 of 12 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 12 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 30 | | Gated MUST-level | 12 | | Not applicable, so out of scope | 0 | | Declared gaps | 4 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 11 | | Tagged units | 11 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5392.md` | | Requirement shard | `rfc/requirements/rfc5392.md` | | RFC text | `rfc/full/rfc5392.txt` | ## Enrolment Enrolled: OSPF inter-AS TE (RFC 5392): OSPFv2 Opaque-type-6; 3 MET (Remote-AS required, Link-ID prohibited, re-advert rate-limit) + 5 single-polarity positive + 4 gap (OSPFv3 Inter-AS-TE-v3 function code 13 unimplemented) ## What the public ledger says **Status:** Experimental **What the ledger says is covered** - 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. **What the ledger says remains** 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`](#rfc5392-3.1.2-1)), the v3 Neighbor-ID prohibition ([`RFC5392-3.2.1-2`](#rfc5392-3.2.1-2)), and the v3 IPv6/IPv4 Remote-ASBR-ID inclusion rules ([`RFC5392-3.3.3-1`](#rfc5392-3.3.3-1), [`RFC5392-3.3.3-2`](#rfc5392-3.3.3-2)) have no v3 carrier to bind. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 3 | one part of the gated population | | Annotated instead of tested | 9 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **12** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (3):** [`RFC5392-3.2.1-4`](#rfc5392-3.2.1-4), [`RFC5392-3.2.1-1`](#rfc5392-3.2.1-1), [`RFC5392-4-3`](#rfc5392-4-3) **Annotated instead of tested (9):** [`RFC5392-3.3.1-1`](#rfc5392-3.3.1-1), [`RFC5392-3.1.2-1`](#rfc5392-3.1.2-1), [`RFC5392-3.2.1-2`](#rfc5392-3.2.1-2), [`RFC5392-3.3.2-1`](#rfc5392-3.3.2-1), [`RFC5392-3.3.2-2`](#rfc5392-3.3.2-2), [`RFC5392-3.3.3-1`](#rfc5392-3.3.3-1), [`RFC5392-3.3.3-2`](#rfc5392-3.3.3-2), [`RFC5392-4-1`](#rfc5392-4-1), [`RFC5392-4-2`](#rfc5392-4-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5392-3.2.1-4` | The Remote-AS-Number sub-TLV (type 21) is included in the Link TLV of both the Inter-AS-TE-v2 and Inter-AS-TE-v3 LSA; it is REQUIRED in any Link TLV advertising an inter-AS TE link (§3.2.1, §3.3.1) -- Ze (v2): `remote-as` mandatory in YANG + validateConfig; emitted as sub-TLV 21 (spec-ospf-ext-2) | REQUIRED | 3.2.1 | **positive:** `unit/verify` [`TestInterAsTEOriginateScopePolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L250). **negative:** `unit/verify` [`TestTEReceiveType6MissingRemoteASSkipped`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_test.go#L73) | | `RFC5392-3.3.1-1` | When only two octets are used for the AS number, the left (high-order) two octets of the Remote AS Number field MUST be set to zero (§3.3.1) -- Ze encodes the 4-octet field big-endian from a uint32, so a 2-byte ASN is zero-extended | MUST | 3.3.1 | **positive:** `unit/verify` [`TestInterAsTERemoteAsTLV`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/te_interas_test.go#L29). **negative:** no negative test. **{single-polarity}:** ze stores remote-as as a uint32 and encodes it big-endian into the fixed 4-octet field, so a 2-byte ASN is zero-extended by construction and no code path can set the high octets non-zero (internal/plugins/ospf/packet/te_interas.go:36, te_lsa.go:133) | | `RFC5392-3.1.2-1` | The Inter-AS-TE-v3 U-bit is always set to 1 so an OSPFv3 router floods the LSA at its defined flooding scope even if it does not recognize the LS type (§3.1.2) | MUST | 3.1.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze originates inter-AS TE only as the OSPFv2 Opaque-type-6 LSA; the OSPFv3 Inter-AS-TE-v3 LSA (function code 13) is not implemented, so there is no LS Type or U-bit to set (internal/plugins/ospf/te.go:88-91) | | `RFC5392-3.2.1-1` | The Link ID sub-TLV MUST NOT be used in the Link TLV of an Inter-AS-TE-v2 LSA (§3.2.1) -- Ze never emits sub-TLV 2 for an inter-AS link, and a received type-6 Link TLV carrying it is skipped (validateReceivedTELink) | MUST NOT | 3.2.1 | **positive:** `unit/verify` [`TestInterAsTEOriginateScopePolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L245). **negative:** `unit/verify` [`TestTEReceiveMalformedNoEntry`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_test.go#L52) | | `RFC5392-3.2.1-2` | The Neighbor ID sub-TLV MUST NOT be used in the Link TLV of an Inter-AS-TE-v3 LSA (§3.2.1) | MUST NOT | 3.2.1 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze implements no OSPFv3 Inter-AS-TE-v3 LSA, so there is no v3 inter-AS Link TLV in which a Neighbor ID sub-TLV could be emitted or prohibited (internal/plugins/ospf/te.go:88-91) | | `RFC5392-3.3.2-1` | In OSPFv2 advertisements, the IPv4 Remote ASBR ID sub-TLV (type 22) MUST be included if the neighboring ASBR has an IPv4 address (§3.3.2) -- Ze: `remote-asbr-ipv4` leaf emitted as sub-TLV 22; validateConfig requires at least one remote-asbr (spec-ospf-ext-2) | MUST | 3.3.2 | **positive:** `unit/verify` [`TestInterAsTEOriginateScopePolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L252). **negative:** no negative test. **{single-polarity}:** ze emits sub-TLV 22 whenever the operator configures remote-asbr-ipv4; the remote ASBR's addresses are proxied from config, so ze cannot independently detect an IPv4 address and there is no adversarial negative (internal/plugins/ospf/packet/te_interas.go:38-39) | | `RFC5392-3.3.2-2` | In OSPFv2, if the neighboring ASBR has no IPv4 address (not even an IPv4 TE Router ID), the IPv6 Remote ASBR ID sub-TLV MUST be included instead (§3.3.2) | MUST | 3.3.2 | **positive:** `unit/verify` [`TestInterAsTEIPv6AsbrIdType24`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/te_interas_test.go#L91). **negative:** no negative test. **{single-polarity}:** validateConfig requires at least one Remote ASBR ID, so a v4-less inter-AS link carries the IPv6 Remote ASBR ID sub-TLV 24; the selection is operator config and the only enforced rejection is neither-present (internal/plugins/ospf/te_config.go:163, packet/te_interas.go:41-44) | | `RFC5392-3.3.3-1` | In OSPFv3 advertisements, the IPv6 Remote ASBR ID sub-TLV (type 24) MUST be included if the neighboring ASBR has an IPv6 address (§3.3.3) | MUST | 3.3.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** OSPFv3 inter-AS TE (function code 13) is not implemented, so ze originates no OSPFv3 advertisement in which to require the IPv6 Remote ASBR ID (the type-24 codec exists but is only ever emitted into a v2 LSA) (internal/plugins/ospf/te.go:88-91) | | `RFC5392-3.3.3-2` | In OSPFv3, if the neighboring ASBR has no IPv6 address, the IPv4 Remote ASBR ID sub-TLV MUST be included instead (§3.3.3) | MUST | 3.3.3 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze implements no OSPFv3 Inter-AS-TE-v3 LSA, so the v3 IPv4-fallback rule has no origination path to bind (internal/plugins/ospf/te.go:88-91) | | `RFC5392-4-1` | Hellos MUST NOT be exchanged over the inter-AS link (§4) -- Ze proxies the inter-AS link from config only; the `inter-as` block forms no adjacency and sends no Hello on that link | MUST NOT | 4 | **positive:** `unit/verify` [`TestInterASTEOriginatesWithoutNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L278). **negative:** no negative test. **{single-polarity}:** ze's inter-AS TE advertisement is a config-only proxy that forms no adjacency and requires no Full neighbor, so the feature originates no Hellos; ze provides no guard forcing the interface passive, so there is no enforced rejection to exercise as a negative (internal/plugins/ospf/te_originate.go:167-202, te_config.go:53) | | `RFC5392-4-2` | An OSPF adjacency MUST NOT be formed on the inter-AS link (§4) -- the inter-AS advertisement is config-driven (a passive/loopback proxy link); no FSM runs on it | MUST NOT | 4 | **positive:** `unit/verify` [`TestInterASTEOriginatesWithoutNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L281). **negative:** no negative test. **{single-polarity}:** inter-AS TE origination is decoupled from the adjacency FSM and never consults neighbor state, so the feature forms no adjacency; there is no guard rejecting an inter-as block on a non-passive interface, so no testable negative exists (internal/plugins/ospf/te_originate.go:167-202, te_config.go:45-53) | | `RFC5392-4-3` | When re-advertising on TE parameter change, the ASBR MUST take precautions against excessive re-advertisements as described in [RFC3630] (§4) | MUST | 4 | **positive:** `unit/verify` [`TestInterASTEReAdvertiseRateLimited`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_originate_test.go#L143). **negative:** `unit/verify` [`TestInterASTEReAdvertiseRateLimited`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_originate_test.go#L153) | | `RFC5392-3.1.1-1` | The inter-AS TE link advertisement SHOULD be carried in a Type 10 Opaque LSA when flooding scope is limited to the ASBR's IGP area (§3.1.1) -- Ze: `inter-as scope area` (the default) originates a Type 10 opaque LSA | SHOULD | 3.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.1.1-2` | Configuration control of the Type 10 vs Type 11 (Inter-AS-TE-v2) choice SHOULD be provided in ASBR implementations that advertise inter-AS TE links (§3.1.1) -- Ze: the `inter-as scope { area \| as }` leaf selects Type 10 vs Type 11 per link | SHOULD | 3.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.1.2-2` | For the Inter-AS-TE-v3 LSA, the S2/S1 bits SHOULD be set to 01 to limit flooding scope to the ASBR's IGP area (§3.1.2) | SHOULD | 3.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.1.2-3` | Configuration control of the 01 vs 10 (Inter-AS-TE-v3) scope choice SHOULD be provided in ASBR implementations that advertise inter-AS TE links (§3.1.2) | SHOULD | 3.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.2.1-3` | At least one of the IPv4-Remote-ASBR-ID and IPv6-Remote-ASBR-ID sub-TLV SHOULD be included in the Link TLV of both LSAs (§3.2.1) -- Ze: validateConfig requires at least one of `remote-asbr-ipv4` / `remote-asbr-ipv6` | SHOULD | 3.2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-4-4` | When TE is enabled on an inter-AS link and the link is up, the ASBR SHOULD advertise this link using normal OSPF-TE procedures (§4) -- Ze originates the Opaque-type-6 LSA via the standard opaque origination pass | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-4-5` | When the link is down or TE is disabled, the ASBR SHOULD withdraw the advertisement (§4) -- Ze's pull-model origination emits a Withdraw for a removed inter-AS instance (MaxAge-flush) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-4-6` | On TE parameter change, the ASBR SHOULD re-advertise the link (§4) -- a changed body re-originates on the next self-LSA pass under the carrier's MinLSInterval rate-limit | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-4-7` | Routers/PCEs SHOULD NOT use inter-AS TE links to compute paths that exit an AS to a remote ASBR then immediately re-enter the AS through another TE link (§4) | SHOULD NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-4-8` | Such exit-and-re-enter paths SHOULD NOT be allowed except as a result of specific policy configurations at the computing router or PCE (§4) | SHOULD NOT | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-5-1` | If a different remote AS number is received in a BGP OPEN than locally configured into OSPF-TE, local policy SHOULD be applied to alert the operator or suppress the OSPF advertisement (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-5-2` | If BGP is used to exchange TE information (§4.1), the inter-AS BGP session SHOULD be secured per [RFC4271] for authentication and integrity (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.3.2-3` | Use of the TE Router ID from the Router Address TLV [RFC3630] is RECOMMENDED for the IPv4 Remote ASBR ID value (§3.3.2) | RECOMMENDED | 3.3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.3.3-3` | Use of the IPv6 TE Router ID from the IPv6 Router Address TLV [RFC5329] is RECOMMENDED for the IPv6 Remote ASBR ID value (§3.3.3) | RECOMMENDED | 3.3.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-2.1-1` | TE aggregation is not supported or recommended (§2.1, §3) | NOT RECOMMENDED | 2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.1.1-3` | The inter-AS TE link advertisement MAY be carried in a Type 11 Opaque LSA when the information is intended to reach all routers (ABRs, ASBRs, PCEs) in the AS (Inter-AS-TE-v2) (§3.1.1) | MAY | 3.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.1.2-4` | For the Inter-AS-TE-v3 LSA, the S2/S1 bits MAY be set to 10 when the information should reach all routers (ABRs, ASBRs, PCEs) in the AS (§3.1.2) | MAY | 3.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5392-3.3.2-4` | An IPv4 Remote ASBR ID sub-TLV and an IPv6 Remote ASBR ID sub-TLV MAY both be present in a Link TLV in OSPFv2 or OSPFv3 (§3.3.2, §3.3.3) | MAY | 3.3.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5392-3.1.2-1`](#rfc5392-3.1.2-1) The Inter-AS-TE-v3 U-bit is always set to 1 so an OSPFv3 router floods the LSA at its defined flooding scope even if it does not recognize the LS type (§3.1.2) | {gap}, no test | ze originates inter-AS TE only as the OSPFv2 Opaque-type-6 LSA; the OSPFv3 Inter-AS-TE-v3 LSA (function code 13) is not implemented, so there is no LS Type or U-bit to set (internal/plugins/ospf/te.go:88-91) | | [`RFC5392-3.2.1-2`](#rfc5392-3.2.1-2) The Neighbor ID sub-TLV MUST NOT be used in the Link TLV of an Inter-AS-TE-v3 LSA (§3.2.1) | {gap}, no test | ze implements no OSPFv3 Inter-AS-TE-v3 LSA, so there is no v3 inter-AS Link TLV in which a Neighbor ID sub-TLV could be emitted or prohibited (internal/plugins/ospf/te.go:88-91) | | [`RFC5392-3.3.3-1`](#rfc5392-3.3.3-1) In OSPFv3 advertisements, the IPv6 Remote ASBR ID sub-TLV (type 24) MUST be included if the neighboring ASBR has an IPv6 address (§3.3.3) | {gap}, no test | OSPFv3 inter-AS TE (function code 13) is not implemented, so ze originates no OSPFv3 advertisement in which to require the IPv6 Remote ASBR ID (the type-24 codec exists but is only ever emitted into a v2 LSA) (internal/plugins/ospf/te.go:88-91) | | [`RFC5392-3.3.3-2`](#rfc5392-3.3.3-2) In OSPFv3, if the neighboring ASBR has no IPv6 address, the IPv4 Remote ASBR ID sub-TLV MUST be included instead (§3.3.3) | {gap}, no test | ze implements no OSPFv3 Inter-AS-TE-v3 LSA, so the v3 IPv4-fallback rule has no origination path to bind (internal/plugins/ospf/te.go:88-91) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5392-3.2.1-4`](#rfc5392-3.2.1-4) The Remote-AS-Number sub-TLV (type 21) is included in the Link TLV of both the Inter-AS-TE-v2 and Inter-AS-TE-v3 LSA; it is REQUIRED in any Link TLV advertising an inter-AS TE link (§3.2.1, §3.3.1) -- Ze (v2): `remote-as` mandatory in YANG + validateConfig; emitted as sub-TLV 21 (spec-ospf-ext-2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTEReceiveType6MissingRemoteASSkipped`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_test.go#L73) | unit/verify | unproven | | positive | [`TestInterAsTEOriginateScopePolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L250) | unit/verify | unproven | ### [`RFC5392-3.3.1-1`](#rfc5392-3.3.1-1) When only two octets are used for the AS number, the left (high-order) two octets of the Remote AS Number field MUST be set to zero (§3.3.1) -- Ze encodes the 4-octet field big-endian from a uint32, so a 2-byte ASN is zero-extended Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInterAsTERemoteAsTLV`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/te_interas_test.go#L29) | unit/verify | unproven | ### [`RFC5392-3.1.2-1`](#rfc5392-3.1.2-1) The Inter-AS-TE-v3 U-bit is always set to 1 so an OSPFv3 router floods the LSA at its defined flooding scope even if it does not recognize the LS type (§3.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5392-3.1.2-1, so no unit is bound to it. ### [`RFC5392-3.2.1-1`](#rfc5392-3.2.1-1) The Link ID sub-TLV MUST NOT be used in the Link TLV of an Inter-AS-TE-v2 LSA (§3.2.1) -- Ze never emits sub-TLV 2 for an inter-AS link, and a received type-6 Link TLV carrying it is skipped (validateReceivedTELink) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestTEReceiveMalformedNoEntry`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_test.go#L52) | unit/verify | unproven | | positive | [`TestInterAsTEOriginateScopePolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L245) | unit/verify | unproven | ### [`RFC5392-3.2.1-2`](#rfc5392-3.2.1-2) The Neighbor ID sub-TLV MUST NOT be used in the Link TLV of an Inter-AS-TE-v3 LSA (§3.2.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5392-3.2.1-2, so no unit is bound to it. ### [`RFC5392-3.3.2-1`](#rfc5392-3.3.2-1) In OSPFv2 advertisements, the IPv4 Remote ASBR ID sub-TLV (type 22) MUST be included if the neighboring ASBR has an IPv4 address (§3.3.2) -- Ze: `remote-asbr-ipv4` leaf emitted as sub-TLV 22; validateConfig requires at least one remote-asbr (spec-ospf-ext-2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInterAsTEOriginateScopePolicy`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L252) | unit/verify | unproven | ### [`RFC5392-3.3.2-2`](#rfc5392-3.3.2-2) In OSPFv2, if the neighboring ASBR has no IPv4 address (not even an IPv4 TE Router ID), the IPv6 Remote ASBR ID sub-TLV MUST be included instead (§3.3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInterAsTEIPv6AsbrIdType24`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/te_interas_test.go#L91) | unit/verify | unproven | ### [`RFC5392-3.3.3-1`](#rfc5392-3.3.3-1) In OSPFv3 advertisements, the IPv6 Remote ASBR ID sub-TLV (type 24) MUST be included if the neighboring ASBR has an IPv6 address (§3.3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5392-3.3.3-1, so no unit is bound to it. ### [`RFC5392-3.3.3-2`](#rfc5392-3.3.3-2) In OSPFv3, if the neighboring ASBR has no IPv6 address, the IPv4 Remote ASBR ID sub-TLV MUST be included instead (§3.3.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5392-3.3.3-2, so no unit is bound to it. ### [`RFC5392-4-1`](#rfc5392-4-1) Hellos MUST NOT be exchanged over the inter-AS link (§4) -- Ze proxies the inter-AS link from config only; the `inter-as` block forms no adjacency and sends no Hello on that link Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInterASTEOriginatesWithoutNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L278) | unit/verify | unproven | ### [`RFC5392-4-2`](#rfc5392-4-2) An OSPF adjacency MUST NOT be formed on the inter-AS link (§4) -- the inter-AS advertisement is config-driven (a passive/loopback proxy link); no FSM runs on it Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInterASTEOriginatesWithoutNeighbor`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/te_originate_test.go#L281) | unit/verify | unproven | ### [`RFC5392-4-3`](#rfc5392-4-3) When re-advertising on TE parameter change, the ASBR MUST take precautions against excessive re-advertisements as described in [RFC3630] (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInterASTEReAdvertiseRateLimited`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_originate_test.go#L153) | unit/verify | unproven | | positive | [`TestInterASTEReAdvertiseRateLimited`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/lsdb/opaque_originate_test.go#L143) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5392, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5392, so its obligations are stated where they were written. --- ### Page: RFC 5443 - LDP IGP Synchronization https://ze-software.net/quality/rfc-compliance/rfc5443/ # RFC 5443 - LDP IGP Synchronization Experimental. Every requirement this repository extracted from RFC 5443, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 62.5% | 5 of 8 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 8 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 8 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 10 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 8 | of 14 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 2 | of 8 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 25.0% | 2 of 8 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 8 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 8 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 12.5% | 1 of 8 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 8 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 14 | | Gated MUST-level | 8 | | Not applicable, so out of scope | 2 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 10 | | Tagged units | 10 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5443.md` | | Requirement shard | `rfc/requirements/rfc5443.md` | | RFC text | `rfc/full/rfc5443.txt` | ## Enrolment Enrolled: LDP IGP Synchronization: eight MUST-level requirements. Five are met with positive+negative tags in internal/plugins/ospf (OSPF LDP-IGP sync state machine): 2-1 (advertise a link at maximum metric until LDP sync is achieved), 2-2 (the maximum metric value is LSInfinity 0xFFFF), 2-5 (do not declare sync until the LDP session is up and the hold-down estimate completes), 3-1 (the cost-out applies to the whole interface/segment, not per-neighbor), and 4-1 (only the IGP link metric is raised, not TE). 2-3 (IS-IS uses 2^24-2 until sync) is {gap}: ze implements LDP-IGP sync only in OSPF; IS-IS has no sync state machine. 2-4 (IS-IS must not use 2^24-1) and 2-6 (use End-of-LIB if implemented) are {not-applicable}: ze runs no IS-IS sync and its LDP has no End-of-LIB. Disclosed in the docs/features/rfc-status.md RFC 5443 row. ## What the public ledger says **Status:** Experimental **What the ledger says is covered** - OSPF LDP-IGP sync state machine ([`internal/plugins/ospf/ldp_sync.go`](https://github.com/ze-software/ze/blob/main/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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc5443.md). **What the ledger says remains** One MUST gap gated in [`rfc/short/rfc5443.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5443.md): ze has no IS-IS LDP-IGP sync, so the IS-IS 2^24-2 max-metric cost-out ([`RFC5443-2-3`](#rfc5443-2-3)) is unimplemented. End-of-LIB ([`RFC5443-2-6`](#rfc5443-2-6)) and the 2^24-1 misuse guard ([`RFC5443-2-4`](#rfc5443-2-4)) are not applicable (no IS-IS sync, no End-of-LIB). ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 5 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **8** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (5):** [`RFC5443-2-1`](#rfc5443-2-1), [`RFC5443-2-2`](#rfc5443-2-2), [`RFC5443-2-5`](#rfc5443-2-5), [`RFC5443-3-1`](#rfc5443-3-1), [`RFC5443-4-1`](#rfc5443-4-1) **Annotated instead of tested (3):** [`RFC5443-2-3`](#rfc5443-2-3), [`RFC5443-2-4`](#rfc5443-2-4), [`RFC5443-2-6`](#rfc5443-2-6) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5443-2-1` | While LDP is not fully operational on a link, the IGP advertises that link with maximum cost to avoid transit traffic ("when LDP is not 'fully operational' ... on a given link, the IGP will advertise the link with maximum cost to avoid any transit traffic over it") (§2) | MUST | 2 | **positive:** `unit/verify` [`TestLDPSyncForcesMaxMetric`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L143). **negative:** `unit/verify` [`TestLDPSyncRestoresAfterHoldDown`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L186) | | `RFC5443-2-2` | In OSPF, the maximum cost advertised is LSInfinity, the 16-bit value `0xFFFF` ("In the case of OSPF, this cost is LSInfinity (16-bit value 0xFFFF), as proposed in [RFC3137]") (§2) | MUST | 2 | **positive:** `unit/verify` [`TestLDPSyncMaxMetricValue`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L156). **negative:** `unit/verify` [`TestLDPSyncDisabledIsNoOp`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L292) | | `RFC5443-2-3` | In IS-IS, the maximum metric advertised is `2^24-2` (`0xFFFFFE`) ("In the case of ISIS, the maximum metric value is 2^24-2 (0xFFFFFE)") (§2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze implements RFC 5443 LDP-IGP sync only in OSPF (internal/plugins/ospf/ldp_sync.go); IS-IS has no LDP-IGP sync state machine, so there is no IS-IS 2^24-2 max-metric cost-out producer (internal/plugins/isis defines only the generic MaxMetric topology-removal value) | | `RFC5443-2-4` | Do not advertise the IS-IS link at `2^24-1` (the per-RFC-5305 maximum link metric), because that removes the link from the topology and loses the last-resort IP path ("if a link is configured with 2^24-1 ... then this link is not advertised in the topology. It is important to keep the link in the topology to allow IP traffic to use the link as a last resort") (§2) | MUST NOT | 2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs no IS-IS LDP-IGP sync (internal/plugins/isis has no sync state machine), so it never originates an LDP-sync-driven IS-IS metric and cannot misuse the 2^24-1 value | | `RFC5443-2-5` | Treat LDP as fully operational on a link only when all three conditions hold: an LDP hello adjacency exists, a suitable associated LDP session matching the hello adjacency's LDP Identifier is established to the peer at the other end of the link, and all label bindings have been exchanged over the session ("LDP is considered fully operational on a link when an LDP hello adjacency exists on it, a suitable associated LDP session ... is established ... and all label bindings have been exchanged over the session") (§2) | MUST | 2 | **positive:** `unit/verify` [`TestLDPSyncSubscribesSessionEvents`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L165). **negative:** `unit/verify` [`TestLDPSyncRestoresAfterHoldDown`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L188) | | `RFC5443-2-6` | When LDP End-of-LIB is implemented, consider the neighbor LDP session fully operational only upon receipt of the End-of-LIB notification message ("The neighbor LDP session is considered fully operational when the End-of-LIB notification message is received") (§2) | MUST | 2 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** the precondition is unmet -- ze's LDP (internal/plugins/ldp) implements no End-of-LIB notification, so ze uses the RFC 5443 hold-down-estimate alternative instead | | `RFC5443-3-1` | On broadcast links with more than one IGP/LDP peer, apply the cost-out procedure to the link as a whole, not to an individual peer ("the cost-out procedure can only be applied to the link as a whole and not to an individual peer") (§3) | MUST | 3 | **positive:** `unit/verify` [`TestLDPSyncTECostUntouched`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L370). **negative:** `unit/verify` [`TestLDPSyncTECostUntouched`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L373) | | `RFC5443-4-1` | Apply the cost-raising mechanism only to the IP link cost, not the TE link cost ("The mechanism described in this document should only be applied to the IP link cost to prevent unnecessary TE tunnel reroutes") (§4) | MUST | 4 | **positive:** `unit/verify` [`TestLDPSyncTECostUntouched`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L364). **negative:** `unit/verify` [`TestLDPSyncTECostUntouched`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L367) | | `RFC5443-3-2` | When a genuine link problem (not merely link bring-up) causes the cost-out, the implementation should issue network management alerts so the operator can address the condition ("an implementation should issue network management alerts to report the error condition and enable the operator to address it") (§3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5443-5-1` | Follow current best security practice for MPLS/GMPLS networks ("implementors should follow the current best security practice [MPLS-GMPLS-Sec]") (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC5443-2-7` | Use a configurable hold-down timer after LDP session establishment as the estimation strategy for "all label bindings exchanged" when End-of-LIB is not available ("A simple implementation strategy is to use a configurable hold-down timer to allow LDP session establishment before declaring LDP fully operational") (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC5443-2-8` | Omit the hold-down timer entirely when LDP End-of-LIB is implemented ("When LDP End-of-LIB is implemented, the configurable hold-down timer is no longer needed") (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC5443-3-3` | As a policy decision on broadcast links, divert traffic away from all peers on the link when LDP service to one peer is unavailable ("a policy decision has to be made whether the unavailability of LDP service to one peer should result in the traffic being diverted away from all the peers on the link") (§3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5443-4-2` | Raise the IP cost of a TE tunnel while there is no operational targeted LDP session between tunnel endpoints ("raising the IP cost of the tunnel while there is no operational LDP session will solve the problem") (§4) | MAY | 4 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5443-2-3`](#rfc5443-2-3) In IS-IS, the maximum metric advertised is `2^24-2` (`0xFFFFFE`) ("In the case of ISIS, the maximum metric value is 2^24-2 (0xFFFFFE)") (§2) | {gap}, no test | ze implements RFC 5443 LDP-IGP sync only in OSPF (internal/plugins/ospf/ldp_sync.go); IS-IS has no LDP-IGP sync state machine, so there is no IS-IS 2^24-2 max-metric cost-out producer (internal/plugins/isis defines only the generic MaxMetric topology-removal value) | | [`RFC5443-2-4`](#rfc5443-2-4) Do not advertise the IS-IS link at `2^24-1` (the per-RFC-5305 maximum link metric), because that removes the link from the topology and loses the last-resort IP path ("if a link is configured with 2^24-1 ... then this link is not advertised in the topology. It is important to keep the link in the topology to allow IP traffic to use the link as a last resort") (§2) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs no IS-IS LDP-IGP sync (internal/plugins/isis has no sync state machine), so it never originates an LDP-sync-driven IS-IS metric and cannot misuse the 2^24-1 value | | [`RFC5443-2-6`](#rfc5443-2-6) When LDP End-of-LIB is implemented, consider the neighbor LDP session fully operational only upon receipt of the End-of-LIB notification message ("The neighbor LDP session is considered fully operational when the End-of-LIB notification message is received") (§2) | no test | no test carries this requirement id; annotated {not-applicable}: the precondition is unmet -- ze's LDP (internal/plugins/ldp) implements no End-of-LIB notification, so ze uses the RFC 5443 hold-down-estimate alternative instead | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5443-2-1`](#rfc5443-2-1) While LDP is not fully operational on a link, the IGP advertises that link with maximum cost to avoid transit traffic ("when LDP is not 'fully operational' ... on a given link, the IGP will advertise the link with maximum cost to avoid any transit traffic over it") (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLDPSyncRestoresAfterHoldDown`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L186) | unit/verify | unproven | | positive | [`TestLDPSyncForcesMaxMetric`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L143) | unit/verify | unproven | ### [`RFC5443-2-2`](#rfc5443-2-2) In OSPF, the maximum cost advertised is LSInfinity, the 16-bit value `0xFFFF` ("In the case of OSPF, this cost is LSInfinity (16-bit value 0xFFFF), as proposed in [RFC3137]") (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLDPSyncDisabledIsNoOp`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L292) | unit/verify | unproven | | positive | [`TestLDPSyncMaxMetricValue`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L156) | unit/verify | unproven | ### [`RFC5443-2-3`](#rfc5443-2-3) In IS-IS, the maximum metric advertised is `2^24-2` (`0xFFFFFE`) ("In the case of ISIS, the maximum metric value is 2^24-2 (0xFFFFFE)") (§2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5443-2-3, so no unit is bound to it. ### [`RFC5443-2-4`](#rfc5443-2-4) Do not advertise the IS-IS link at `2^24-1` (the per-RFC-5305 maximum link metric), because that removes the link from the topology and loses the last-resort IP path ("if a link is configured with 2^24-1 ... then this link is not advertised in the topology. It is important to keep the link in the topology to allow IP traffic to use the link as a last resort") (§2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5443-2-4, so no unit is bound to it. ### [`RFC5443-2-5`](#rfc5443-2-5) Treat LDP as fully operational on a link only when all three conditions hold: an LDP hello adjacency exists, a suitable associated LDP session matching the hello adjacency's LDP Identifier is established to the peer at the other end of the link, and all label bindings have been exchanged over the session ("LDP is considered fully operational on a link when an LDP hello adjacency exists on it, a suitable associated LDP session ... is established ... and all label bindings have been exchanged over the session") (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLDPSyncRestoresAfterHoldDown`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L188) | unit/verify | unproven | | positive | [`TestLDPSyncSubscribesSessionEvents`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L165) | unit/verify | unproven | ### [`RFC5443-2-6`](#rfc5443-2-6) When LDP End-of-LIB is implemented, consider the neighbor LDP session fully operational only upon receipt of the End-of-LIB notification message ("The neighbor LDP session is considered fully operational when the End-of-LIB notification message is received") (§2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5443-2-6, so no unit is bound to it. ### [`RFC5443-3-1`](#rfc5443-3-1) On broadcast links with more than one IGP/LDP peer, apply the cost-out procedure to the link as a whole, not to an individual peer ("the cost-out procedure can only be applied to the link as a whole and not to an individual peer") (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLDPSyncTECostUntouched`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L373) | unit/verify | unproven | | positive | [`TestLDPSyncTECostUntouched`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L370) | unit/verify | unproven | ### [`RFC5443-4-1`](#rfc5443-4-1) Apply the cost-raising mechanism only to the IP link cost, not the TE link cost ("The mechanism described in this document should only be applied to the IP link cost to prevent unnecessary TE tunnel reroutes") (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestLDPSyncTECostUntouched`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L367) | unit/verify | unproven | | positive | [`TestLDPSyncTECostUntouched`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/ldp_sync_test.go#L364) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5443, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5443, so its obligations are stated where they were written. --- ### Page: RFC 5492 - Capabilities Advertisement with BGP-4 https://ze-software.net/quality/rfc-compliance/rfc5492/ # RFC 5492 - Capabilities Advertisement with BGP-4 Supported. Every requirement this repository extracted from RFC 5492, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 77.8% | 7 of 9 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 11.1% | 1 of 9 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 9 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 9 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 17 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 9 | of 16 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 9 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 11.1% | 1 of 9 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 9 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 9 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 9 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 16 | | Gated MUST-level | 9 | | Not applicable, so out of scope | 1 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 17 | | Tagged units | 17 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5492.md` | | Requirement shard | `rfc/requirements/rfc5492.md` | | RFC text | `rfc/full/rfc5492.txt` | ## Enrolment Enrolled: BGP Capabilities Advertisement: nine MUST-level requirements. Eight are met with test tags: 3-1 (Unsupported-Capability NOTIFICATION carries the offending capabilities), 5-1 (each capability encoded code+length+value as in OPEN), 4-2 (every Type-2 Optional Parameter processed), 3-2 (unknown capability preserved without error), and the MUST NOTs 3-3/3-4/5-2 (a not-understood capability is not rejected and triggers no NOTIFICATION) carry positive+negative tags in internal/core/bgp/capability and internal/component/bgp/reactor. 4-1 (accept multiple identical capability instances) is {single-polarity: positive} bound to a new parser test (capability.go:177 appends every TLV with no reject path). 4-3 is {not-applicable}: it obliges the author of a capability-defining specification to describe its error handling, not a runtime behavior ze implements. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** Capability TLV parser, encoder, unknown-capability ignore behavior, negotiated session view. **What the ledger says remains:** No tracked gap in current source anchors. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 7 | one part of the gated population | | Annotated instead of tested | 2 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **9** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (7):** [`RFC5492-3-1`](#rfc5492-3-1), [`RFC5492-5-1`](#rfc5492-5-1), [`RFC5492-4-2`](#rfc5492-4-2), [`RFC5492-3-2`](#rfc5492-3-2), [`RFC5492-3-3`](#rfc5492-3-3), [`RFC5492-3-4`](#rfc5492-3-4), [`RFC5492-5-2`](#rfc5492-5-2) **Annotated instead of tested (2):** [`RFC5492-4-1`](#rfc5492-4-1), [`RFC5492-4-3`](#rfc5492-4-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5492-3-1` | When sending Unsupported Capability NOTIFICATION, the message MUST contain the capability or capabilities that cause the speaker to send the message (§3) | MUST | 3 - Overview of Operations | **positive:** `unit/verify` [`TestBuildUnsupportedCapabilityData`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_validate_test.go#L359). **positive:** `unit/verify` [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L93). **negative:** `unit/verify` [`TestSessionAcceptsRequiredCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_test.go#L1312) | | `RFC5492-5-1` | The Data field in the NOTIFICATION message MUST list the set of capabilities that causes the speaker to send the message, each encoded as in an OPEN message (§5) | MUST | 5 - Extensions to Error Handling | **positive:** `unit/verify` [`TestBuildUnsupportedCapabilityDataCodes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_test.go#L1160). **positive:** `unit/verify` [`TestBuildUnsupportedCapabilityDataCodes_MultipleCodes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_validate_test.go#L389). **negative:** `unit/verify` [`TestBuildUnsupportedCapabilityDataCodes_Empty`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_validate_test.go#L411) | | `RFC5492-4-1` | A BGP speaker MUST be prepared to accept multiple instances of a capability with the same Code, Length, and Value (§4) | MUST | 4 - Capabilities Optional Parameter (Parameter Type 2) | **positive:** `unit/verify` [`TestParseAcceptsMultipleIdenticalCapabilityInstances`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L254). **negative:** no negative test. **{single-polarity}:** ze's capability parser appends every capability TLV without dedup or reject (internal/core/bgp/capability/capability.go:177), so multiple identical instances are all accepted; there is no reject path, so no negative case exists | | `RFC5492-4-2` | A BGP speaker MUST be prepared to receive an OPEN message that contains multiple Capabilities Optional Parameters (§4) | MUST | 4 - Capabilities Optional Parameter (Parameter Type 2) | **positive:** `unit/verify` [`TestParseFromOptionalParamsMultipleCapabilitiesParameters`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L285). **negative:** `unit/verify` [`TestOptionalParamRejectsTruncatedCapabilityTLV`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L201) | | `RFC5492-3-2` | A BGP speaker MUST ignore unrecognized capability codes (§3) | MUST | 3 - Overview of Operations | **positive:** `unit/verify` [`TestParseUnknownCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L138). **negative:** `unit/verify` [`TestParseRejectsMalformedKnownCapabilityLength`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L167) | | `RFC5492-4-3` | Processing of multiple instances of the same Capability Code with different values MUST be described in the document introducing the new capability (§4) | MUST | 4 - Capabilities Optional Parameter (Parameter Type 2) | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this obligation binds the author of a specification that introduces a new capability to describe its multiple-instance error handling; it is not a runtime behavior ze implements | | `RFC5492-3-3` | The BGP session MUST NOT be terminated in response to reception of a capability that is not supported by the local speaker (§3) | MUST NOT | 3 - Overview of Operations | **positive:** `unit/verify` [`TestOptionalParamPreservesUnknownCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L222). **negative:** `unit/verify` [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L98) | | `RFC5492-3-4` | The Unsupported Capability NOTIFICATION message MUST NOT be generated in response to an unrecognized capability (§3, §5) | MUST NOT | 3 - Overview of Operations | **positive:** `unit/verify` [`TestOptionalParamPreservesUnknownCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L227). **negative:** `unit/verify` [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L101) | | `RFC5492-5-2` | Unsupported Capability NOTIFICATION MUST NOT be used when a BGP speaker receives a capability it does not understand; such capabilities MUST be ignored (§5) | MUST NOT | 5 - Extensions to Error Handling | **positive:** `unit/verify` [`TestOptionalParamPreservesUnknownCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L229). **negative:** `unit/verify` [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L104) | | `RFC5492-3-5` | On receiving Unsupported Optional Parameter NOTIFICATION, speaker SHOULD attempt to re-establish without Capabilities Optional Parameter (§3) | SHOULD | 3 - Overview of Operations | **positive:** no positive test. **negative:** no negative test | | `RFC5492-4-4` | A BGP speaker SHOULD NOT include more than one instance of a capability with the same Code, Length, and Value (§4) | SHOULD NOT | 4 - Capabilities Optional Parameter (Parameter Type 2) | **positive:** no positive test. **negative:** no negative test | | `RFC5492-4-5` | The Capabilities Optional Parameter SHOULD only be included in the OPEN message once (§4) | SHOULD | 4 - Capabilities Optional Parameter (Parameter Type 2) | **positive:** no positive test. **negative:** no negative test | | `RFC5492-4-6` | All capabilities SHOULD be listed as TLVs within a single Capabilities Optional Parameter (§4) | SHOULD | 4 - Capabilities Optional Parameter (Parameter Type 2) | **positive:** no positive test. **negative:** no negative test | | `RFC5492-3-6` | Peering terminated due to Unsupported Capability SHOULD NOT be re-established automatically (§3) | SHOULD NOT | 3 - Overview of Operations | **positive:** no positive test. **negative:** no negative test | | `RFC5492-3-7` | A BGP speaker MAY send a NOTIFICATION and terminate peering when peer doesn't support a required capability (§3) | MAY | 3 - Overview of Operations | **positive:** no positive test. **negative:** no negative test | | `RFC5492-4-7` | A BGP speaker MAY include more than one instance of a capability with non-zero Length but different Value (§4) | MAY | 4 - Capabilities Optional Parameter (Parameter Type 2) | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5492-4-3`](#rfc5492-4-3) Processing of multiple instances of the same Capability Code with different values MUST be described in the document introducing the new capability (§4) | no test | no test carries this requirement id; annotated {not-applicable}: this obligation binds the author of a specification that introduces a new capability to describe its multiple-instance error handling; it is not a runtime behavior ze implements | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5492-3-1`](#rfc5492-3-1) When sending Unsupported Capability NOTIFICATION, the message MUST contain the capability or capabilities that cause the speaker to send the message (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestSessionAcceptsRequiredCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_test.go#L1312) | unit/verify | unproven | | positive | [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L93) | unit/verify | unproven | | positive | [`TestBuildUnsupportedCapabilityData`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_validate_test.go#L359) | unit/verify | unproven | ### [`RFC5492-5-1`](#rfc5492-5-1) The Data field in the NOTIFICATION message MUST list the set of capabilities that causes the speaker to send the message, each encoded as in an OPEN message (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBuildUnsupportedCapabilityDataCodes_Empty`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_validate_test.go#L411) | unit/verify | unproven | | positive | [`TestBuildUnsupportedCapabilityDataCodes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_test.go#L1160) | unit/verify | unproven | | positive | [`TestBuildUnsupportedCapabilityDataCodes_MultipleCodes`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_validate_test.go#L389) | unit/verify | unproven | ### [`RFC5492-4-1`](#rfc5492-4-1) A BGP speaker MUST be prepared to accept multiple instances of a capability with the same Code, Length, and Value (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestParseAcceptsMultipleIdenticalCapabilityInstances`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L254) | unit/verify | unproven | ### [`RFC5492-4-2`](#rfc5492-4-2) A BGP speaker MUST be prepared to receive an OPEN message that contains multiple Capabilities Optional Parameters (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOptionalParamRejectsTruncatedCapabilityTLV`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L201) | unit/verify | unproven | | positive | [`TestParseFromOptionalParamsMultipleCapabilitiesParameters`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L285) | unit/verify | unproven | ### [`RFC5492-3-2`](#rfc5492-3-2) A BGP speaker MUST ignore unrecognized capability codes (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseRejectsMalformedKnownCapabilityLength`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L167) | unit/verify | unproven | | positive | [`TestParseUnknownCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L138) | unit/verify | unproven | ### [`RFC5492-4-3`](#rfc5492-4-3) Processing of multiple instances of the same Capability Code with different values MUST be described in the document introducing the new capability (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5492-4-3, so no unit is bound to it. ### [`RFC5492-3-3`](#rfc5492-3-3) The BGP session MUST NOT be terminated in response to reception of a capability that is not supported by the local speaker (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L98) | unit/verify | unproven | | positive | [`TestOptionalParamPreservesUnknownCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L222) | unit/verify | unproven | ### [`RFC5492-3-4`](#rfc5492-3-4) The Unsupported Capability NOTIFICATION message MUST NOT be generated in response to an unrecognized capability (§3, §5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L101) | unit/verify | unproven | | positive | [`TestOptionalParamPreservesUnknownCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L227) | unit/verify | unproven | ### [`RFC5492-5-2`](#rfc5492-5-2) Unsupported Capability NOTIFICATION MUST NOT be used when a BGP speaker receives a capability it does not understand; such capabilities MUST be ignored (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOpenRejectsMalformedKnownCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/session_handlers_test.go#L104) | unit/verify | unproven | | positive | [`TestOptionalParamPreservesUnknownCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L229) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase 3, rfc5492 | | Signed off | 2026-08-31 | | Register | prose | | Source | rfc/full/rfc5492.txt | | Source fingerprint | d97da179f6d6ed92 | | Record | rfc/extraction/rfc5492.json | | Mapped sentences | 8 | | Declined as scope | 3 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of This Memo, Copyright Notice and Abstract. The Abstract states what the document defines and that it obsoletes RFC 3392. It directs no speaker. | | `1` | Introduction | 2 | walked | Introduction. Two sentences, both indicative, and both excluded below. The first recounts what the base BGP-4 specification requires of a speaker that meets an unrecognized Optional Parameter. The second states what a pair of speakers supporting this document can do. Section 3 says of the same fact that it 'is a consequence of the base BGP-4 specification [RFC4271] and not a new requirement'. | | `2` | Requirements Language | 0 | walked | Requirements Language. The RFC 2119 key-words paragraph. It tells a reader how to read sections 3 to 5 and binds no speaker, which is why the derivation excludes it from the site inventory. | | `3` | Overview of Operations | 3 | walked | Overview of Operations. The document's main normative section: three sites, mapped below to RFC5492-3-1, RFC5492-3-2 and RFC5492-3-3. Site 3:3 states two MUST NOT obligations in one sentence, so RFC5492-3-4 is declared unsourced here rather than mapped. Its remaining directives carry no MUST-level keyword, so the site scan cannot see them: the SHOULD to re-establish without the Capabilities Optional Parameter after an Unsupported Optional Parameter NOTIFICATION (RFC5492-3-5), the SHOULD NOT to re-establish a peering terminated for a missing required capability (RFC5492-3-6), and the MAY to send a NOTIFICATION and terminate when the peer does not support a required capability (RFC5492-3-7). The section's opening MAY, that an OPEN message may include the Capabilities Optional Parameter, and its three indicative paragraphs on how a speaker learns a peer's capabilities, state no obligation. | | `4` | Capabilities Optional Parameter (Parameter Type 2) | 3 | walked | Capabilities Optional Parameter (Parameter Type 2). Assigns parameter type 2 and defines the <Capability Code, Capability Length, Capability Value> triple, which are value definitions carried by the Wire Formats and Constants tables of rfc/short/rfc5492.md. Three sites, mapped below to RFC5492-4-1, RFC5492-4-3 and RFC5492-4-2. Its three advisory sentences carry no MUST-level keyword and are the unsourced ids below: the SHOULD NOT against duplicate identical instances (RFC5492-4-4), the SHOULD to include the Capabilities Optional Parameter once (RFC5492-4-5), the SHOULD to list every capability as a TLV inside that one parameter (RFC5492-4-6), and the MAY to include several instances of one code with different values (RFC5492-4-7). The closing sentence, that the set of capabilities should be processed the same way whether it arrives in one parameter or several, is the lowercase 'should' the document's own section 2 gives no normative level; it restates the MUST at site 4:3 and the summary captures it under RFC5492-4-2 in its Decoding Rules. | | `5` | Extensions to Error Handling | 3 | walked | Extensions to Error Handling. Assigns Error Subcode 7, Unsupported Capability, as a value definition. Three sites: 5:1 and 5:3 are mapped to RFC5492-5-1 and RFC5492-5-2, and 5:2 is a recapitulation of section 3. The sentence 'Each such capability is encoded in the same way as it would be encoded in the OPEN message' carries no MUST-level keyword, so the scan cannot raise it; the summary folds it into RFC5492-5-1, whose row names it. | | `6` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records the Capability Code registry and its three assignment policies over codes 1-63, 64-127 and 128-255, the reserved code 0, and the 'BGP OPEN Optional Parameter Types' registry with parameter types 1 and 2. Binds IANA, not a speaker. | | `7` | Security Considerations | 0 | walked | Security Considerations. States that this extension does not change the security issues inherent in existing BGP. No countermeasure is directed at a speaker. | | `8` | Acknowledgments | 0 | skipped (acknowledgements) | Acknowledgments. | | `9` | References | 0 | skipped (references) | References. The heading over sections 9.1 and 9.2. | | `9.1` | Normative References: RFC 2119, RFC 4271 and RFC 5226 | 0 | skipped (references) | Normative References: RFC 2119, RFC 4271 and RFC 5226. | | `9.2` | Informative References: RFC 4272 and RFC 4760 | 0 | skipped (references) | Informative References: RFC 4272 and RFC 4760. | | `A` | Appendix A | 0 | skipped (appendix-non-normative) | Appendix A. Comparison between RFC 2842 and RFC 3392. Two lines of editorial history about a document this one obsoletes. No obligation. | | `B` | Appendix B | 0 | skipped (appendix-non-normative) | Appendix B. Comparison between RFC 3392 and This Document. Editorial history, including that this document 'clarifies requirements by changing a number of SHOULDs to MUSTs'. It names no obligation of its own; the changed levels are the MUSTs of sections 3, 4 and 5. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `1:1` | `cross-document` (never bound Ze): the obligation belongs to another document that this one only cites | The obligation belongs to RFC 4271, which the sentence cites: a speaker that receives an OPEN with an unrecognized Optional Parameter terminates the peering. This document recounts it as the problem it solves, and section 3 says of the same fact that it 'is a consequence of the base BGP-4 specification [RFC4271] and not a new requirement'. The lowercase 'must' is what the prose-register scan raised. | The base BGP-4 specification [RFC4271] requires that when a BGP speaker receives an OPEN message with one or more unrecognized Optional Parameters, the speaker must terminate the BGP peering. | | `1:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Indicative: it states what a pair of speakers supporting this document can do, not what either of them owes. The word the scan raised is 'required' in 'all capabilities required to support the peering', which is descriptive English rather than the RFC 2119 keyword. Section 2 binds the key words only in upper case, and the prose-register scan is case-insensitive. The behavior the sentence describes is stated normatively at site 3:2 and is captured as RFC5492-3-2. | A pair of BGP speakers that supports this specification can establish the peering even when presented with unrecognized capabilities, so long as all capabilities required to support the peering are supported. | | `5:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Indicative: it recapitulates what section 3 already established, that the Unsupported Capability NOTIFICATION is how a speaker complains about a missing capability the peering requires. The sentence says so itself: 'As explained in the "Overview of Operations" section'. The word the scan raised is 'required' in 'a required capability', which is descriptive English rather than the RFC 2119 keyword; section 2 binds the key words only in upper case. The same sentence's lowercase 'cannot', in 'without which the peering cannot proceed', describes the peer's situation rather than stating an obligation. The obligation of this paragraph is the next sentence, site 5:3. | As explained in the "Overview of Operations" section, the Unsupported Capability NOTIFICATION is a way for a BGP speaker to complain that its peer does not support a required capability without which the peering cannot proceed. | ## Superseded No document obsoletes RFC 5492, so its obligations are stated where they were written. --- ### Page: RFC 5549 - Advertising IPv4 Network Layer Reachability Information with an IPv6 Next Hop https://ze-software.net/quality/rfc-compliance/rfc5549/ # RFC 5549 - Advertising IPv4 Network Layer Reachability Information with an IPv6 Next Hop Supported. Every requirement this repository extracted from RFC 5549, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 50.0% | 3 of 6 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 50.0% | 3 of 6 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 6 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 6 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 17 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 6 | of 9 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 6 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 6 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 6 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 6 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 6 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Supported | | Enrolment | Enrolled | | Requirements | 9 | | Gated MUST-level | 6 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 17 | | Tagged units | 17 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5549.md` | | Requirement shard | `rfc/requirements/rfc5549.md` | | RFC text | `rfc/full/rfc5549.txt` | ## Enrolment Enrolled: Advertising IPv4 NLRI with an IPv6 Next Hop (legacy encoding, obsoleted by RFC 8950 which ze implements): six MUST-level requirements, all met by the shared extended-next-hop implementation. 4-1 (cross-family IPv6 next-hop honored only when the extended-next-hop capability is negotiated), 3-1 (a 16- or 32-octet next-hop is decoded as IPv6), and 4-4 (do not use a cross-family next-hop without the negotiated capability) carry positive+negative tags. 4-2 (use the RFC 5492 capability mechanism), 4-3 (capability code 5), and 5-1 (do not rewrite a reflected next-hop) are {single-polarity: positive}. Tags added alongside the sibling RFC 8950 tags on internal/core/bgp/capability, internal/core/bgp/attribute, and internal/component/bgp/reactor tests. ## What the public ledger says **Status:** Supported **What the ledger says is covered:** Backward-compatible parser for the older format superseded by RFC 8950. **What the ledger says remains:** Main public claim uses RFC 8950. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 3 | one part of the gated population | | Annotated instead of tested | 3 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **6** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (3):** [`RFC5549-4-1`](#rfc5549-4-1), [`RFC5549-3-1`](#rfc5549-3-1), [`RFC5549-4-4`](#rfc5549-4-4) **Annotated instead of tested (3):** [`RFC5549-4-2`](#rfc5549-4-2), [`RFC5549-4-3`](#rfc5549-4-3), [`RFC5549-5-1`](#rfc5549-5-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5549-4-1` | A BGP speaker MUST only advertise IPv4 or VPN-IPv4 NLRI with an IPv6 Next Hop if it has ascertained via Capability Advertisement that the peer supports Extended Next Hop Encoding for the relevant AFI/SAFI pair (§4) | MUST | 4 - Use of BGP Capability Advertisement | **positive:** `unit/verify` [`TestCanUseNextHopFor_ExtendedNH`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1147). **positive:** `unit/verify` [`TestNegotiateExtendedNextHop`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L287). **negative:** `unit/verify` [`TestCanUseNextHopFor_CrossFamilyNoCap`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1178). **negative:** `unit/verify` [`TestNegotiateExtendedNextHopMismatch`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L326) | | `RFC5549-4-2` | A BGP speaker that wishes to advertise an IPv6 Next Hop for IPv4 or VPN-IPv4 NLRI MUST use the Capability Advertisement procedures defined in RFC 5492 (§4) | MUST | 4 - Use of BGP Capability Advertisement | **positive:** `unit/verify` [`TestExtendedNextHopCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L506). **positive:** `unit/verify` [`TestExtendedNextHopRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L539). **negative:** no negative test. **{single-polarity}:** ze advertises and parses the Extended Next Hop Encoding capability exclusively through the RFC 5492 capability TLV framework (internal/core/bgp/capability/capability.go:644 WriteTo, :667 parseExtendedNextHop). There is no non-RFC-5492 signalling path in ze, so no wrong-procedure case exists to assert as a negative; the peer-support-not-ascertained negative is covered by RFC5549-4-1 | | `RFC5549-4-3` | The Capability Code field MUST be set to 5 (Extended Next Hop Encoding) (§4) | MUST | 4 - Use of BGP Capability Advertisement | **positive:** `unit/verify` [`TestCapabilityCodeConstants`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L17). **positive:** `unit/verify` [`TestExtendedNextHopCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L509). **positive:** `unit/verify` [`TestExtendedNextHopRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L542). **negative:** no negative test. **{single-polarity}:** the Extended Next Hop Encoding capability code is the fixed constant CodeExtendedNextHop = 5 (internal/core/bgp/capability/capability.go:70); Code() returns it (capability.go:640) and WriteTo emits it (capability.go:646). The code has no alternate-value code path, so there is no wrong-code case to reject as a negative | | `RFC5549-3-1` | The BGP speaker receiving the advertisement MUST use the Length of Next Hop Address field to determine which network-layer protocol the next hop address belongs to (§3) | MUST | 3 | **positive:** `unit/verify` [`TestParseMPReachNLRI_ExtendedNextHop`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L342). **positive:** `unit/verify` [`TestParseMPReachNLRI_ExtendedNextHop_DualStack`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L446). **positive:** `unit/verify` [`TestParseMPReachNLRI_ExtendedNextHop_VPN`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L400). **negative:** `unit/verify` [`TestParseMPReachNLRI_InvalidNextHopLength`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L493) | | `RFC5549-5-1` | When a next hop address needs to be passed along unchanged (e.g., Route Reflector), its encoding MUST NOT be changed (§5) | MUST NOT | 5 - Operations | **positive:** `unit/verify` [`TestReactorForwardRRPreservesExtendedNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L237). **negative:** no negative test. **{single-polarity}:** on the reflection path ze rewrites the next-hop only under an explicit next-hop-self/explicit override (nhMode != nhModeNone); the default nhModeNone leaves the next-hop untouched (internal/component/bgp/reactor/peer_forward_facts.go:226) and the MP re-encode changes an attribute only when the NLRI framing differs between encoding contexts (internal/component/bgp/reactor/forward_body.go:217), so a reflected next-hop is carried verbatim and there is no ze code path that rewrites an unchanged-passthrough next-hop to assert as a negative. The positive is proven byte-identical in TestReactorForwardRRPreservesExtendedNextHop | | `RFC5549-4-4` | MUST NOT send IPv6 Next Hop for IPv4 NLRI to peers that have not advertised Extended Next Hop Encoding capability (§4, Compatibility) | MUST NOT | 4 - Use of BGP Capability Advertisement | **positive:** `unit/verify` [`TestCanUseNextHopFor_ExtendedNH`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1150). **negative:** `unit/verify` [`TestCanUseNextHopFor_CrossFamilyNoCap`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1181). **negative:** `unit/verify` [`TestCanUseNextHopFor_NilSendCtx`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1202) | | `RFC5549-5-2` | By default, if a BGP session is running over IPvx, the next hop address SHOULD be specified as an IPvx address (§5) | SHOULD | 5 - Operations | **positive:** no positive test. **negative:** no negative test | | `RFC5549-4-5` | The Extended Next Hop Encoding capability MAY be dynamically updated through the Dynamic Capability capability (§4) | MAY | 4 - Use of BGP Capability Advertisement | **positive:** no positive test. **negative:** no negative test | | `RFC5549-5-3` | The default next-hop-address-family behavior may be overridden by policy (§5) | MAY | 5 - Operations | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 5549 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5549-4-1`](#rfc5549-4-1) A BGP speaker MUST only advertise IPv4 or VPN-IPv4 NLRI with an IPv6 Next Hop if it has ascertained via Capability Advertisement that the peer supports Extended Next Hop Encoding for the relevant AFI/SAFI pair (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCanUseNextHopFor_CrossFamilyNoCap`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1178) | unit/verify | unproven | | negative | [`TestNegotiateExtendedNextHopMismatch`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L326) | unit/verify | unproven | | positive | [`TestCanUseNextHopFor_ExtendedNH`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1147) | unit/verify | unproven | | positive | [`TestNegotiateExtendedNextHop`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/negotiated_test.go#L287) | unit/verify | unproven | ### [`RFC5549-4-2`](#rfc5549-4-2) A BGP speaker that wishes to advertise an IPv6 Next Hop for IPv4 or VPN-IPv4 NLRI MUST use the Capability Advertisement procedures defined in RFC 5492 (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestExtendedNextHopCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L506) | unit/verify | unproven | | positive | [`TestExtendedNextHopRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L539) | unit/verify | unproven | ### [`RFC5549-4-3`](#rfc5549-4-3) The Capability Code field MUST be set to 5 (Extended Next Hop Encoding) (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestCapabilityCodeConstants`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L17) | unit/verify | unproven | | positive | [`TestExtendedNextHopCapability`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L509) | unit/verify | unproven | | positive | [`TestExtendedNextHopRoundTrip`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/capability/capability_test.go#L542) | unit/verify | unproven | ### [`RFC5549-3-1`](#rfc5549-3-1) The BGP speaker receiving the advertisement MUST use the Length of Next Hop Address field to determine which network-layer protocol the next hop address belongs to (§3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseMPReachNLRI_InvalidNextHopLength`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L493) | unit/verify | unproven | | positive | [`TestParseMPReachNLRI_ExtendedNextHop`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L342) | unit/verify | unproven | | positive | [`TestParseMPReachNLRI_ExtendedNextHop_DualStack`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L446) | unit/verify | unproven | | positive | [`TestParseMPReachNLRI_ExtendedNextHop_VPN`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/mpnlri_test.go#L400) | unit/verify | unproven | ### [`RFC5549-5-1`](#rfc5549-5-1) When a next hop address needs to be passed along unchanged (e.g., Route Reflector), its encoding MUST NOT be changed (§5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestReactorForwardRRPreservesExtendedNextHop`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/forward_rr_test.go#L237) | unit/verify | unproven | ### [`RFC5549-4-4`](#rfc5549-4-4) MUST NOT send IPv6 Next Hop for IPv4 NLRI to peers that have not advertised Extended Next Hop Encoding capability (§4, Compatibility) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestCanUseNextHopFor_CrossFamilyNoCap`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1181) | unit/verify | unproven | | negative | [`TestCanUseNextHopFor_NilSendCtx`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1202) | unit/verify | unproven | | positive | [`TestCanUseNextHopFor_ExtendedNH`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/reactor/peer_test.go#L1150) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-rfcgate-6 phase 5, rfc5549 | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc5549.txt | | Source fingerprint | a4896c55c7192c94 | | Record | rfc/extraction/rfc5549.json | | Mapped sentences | 5 | | Declined as scope | 1 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Status of This Memo, Copyright Notice, Abstract and Table of Contents. The Abstract restates the Introduction: MP-BGP lets the AFI/SAFI decide the network-layer protocol of the Next Hop, the IPv4 AFI/SAFI definitions provide only for an IPv4 Next Hop, and this document adds the IPv6 case. It states no obligation. | | `1` | Introduction | 0 | walked | Introduction. Indicative prose: which AFI/SAFI pairs already let the Next Hop belong to another address family (<25/65> of L2VPN-SIG, <1/132> of RFC 4684), that RFC 4684 already decides the family from the Length of Next Hop Address field, that RFC 4798 and RFC 4659 solve IPv6 NLRI with an IPv4 Next Hop, and that no solution existed for IPv4 NLRI with an IPv6 Next Hop. The last paragraph states what this document adds. No sentence directs a speaker. | | `2` | Requirements Language | 0 | walked | Requirements Language. The RFC 2119 key-words paragraph. It tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. | | `3` | not stated | 1 | walked | Extension of AFI/SAFI Definitions for the IPv4 Address Family. One capitalised MUST-level site, mapped below to RFC5549-3-1. The rest is value assignment written as a bullet list: AFI 1, SAFI 1, 2, 4 or 128, a Length of Next Hop Address of 16 or 32, and a Next Hop Address that is an IPv6 address optionally followed by the link-local IPv6 address. Those bullets carry no RFC 2119 level, and the Wire Formats and Decoding Rules tables of rfc/short/rfc5549.md hold the values. One bullet delegates rather than obliges: 'This field is to be constructed as per Section 3 of [RFC2545]', which is RFC 2545's obligation and carries its own summary. The closing note that RFC 4684 and L2VPN-SIG use the same length method is a comparison. RFC 8950 Section 3 replaces the 16-or-32 length for SAFI 128 with 24 or 48 and a zero Route Distinguisher; that successor obligation belongs to RFC8950-3-2 and is not stated here, so nothing of it is unsourced against this document. | | `4` | Use of BGP Capability Advertisement | 4 | walked | Use of BGP Capability Advertisement. Four capitalised MUST-level sites: the RFC 5492 procedures (RFC5549-4-2), the lead-in to the field list (excluded as a duplicate below), Capability Code 5 (RFC5549-4-3), and the precondition that a speaker advertises an IPv6 Next Hop for IPv4 or VPN-IPv4 NLRI only after the Capability Advertisement has shown peer support (RFC5549-4-1). The Capability Value format, its <NLRI AFI, NLRI SAFI, Nexthop AFI> triples and the values this document allows (NLRI AFI 1, NLRI SAFI 1, 2, 4 or 128, Nexthop AFI 2) are value assignment, held by the Wire Formats and Constants tables of rfc/short/rfc5549.md. Three sentences the site scan cannot see are scoping rather than obligations: that this document does not propose the capability for any other combination, that new AFI/SAFIs are not expected to use it, and that the capability says how a Next Hop is encoded while the Multiprotocol Extensions capability of RFC 4760 decides whether the AFI/SAFI is allowed at all. Each is indicative, so section 2 gives it no normative level. Two ids the site scan cannot reach are listed below. | | `5` | Operations | 1 | walked | Operations. One capitalised MUST-level site, mapped below to RFC5549-5-1. The two remaining directives are the SHOULD to give the next hop as an IPvx address when the session runs over IPvx and the speaker puts its own address in, and the MAY that policy overrides that default. Both are the unsourced ids below. The closing sentences state a consequence rather than an obligation: an RR client that cannot handle an encoding, as determined by the BGP Capability Advertisement, does not get the NLRI, and sound routing in some designs needs every RR client to handle whatever encodings any of them generate. | | `6` | Usage Examples | 0 | walked | Usage Examples. A heading with no text of its own. Sections 6.1 and 6.2 carry the two examples. | | `6.1` | IPv4 over IPv6 Core | 0 | walked | IPv4 over IPv6 Core. An illustrative encoding for the interconnection of IPv4 islands over an IPv6 backbone as described in MESH-FMWK: AFI 1, SAFI 1, Length of Next Hop Network Address 16 or 32, an IPv6 Next Hop, IPv4 routes as NLRI, and the capability triple <NLRI AFI 1, NLRI SAFI 1, Nexthop AFI 2>. Written as 'may be used' and 'would include', so it directs no speaker; the values repeat sections 3 and 4. | | `6.2` | IPv4 VPN over IPv6 Core | 0 | walked | IPv4 VPN over IPv6 Core. The same illustrative shape for SAFI 128: Length of Next Hop Network Address 16 or 32, an IPv6 Next Hop, IPv4-VPN routes as NLRI, and the capability triple <NLRI AFI 1, NLRI SAFI 128, Nexthop AFI 2>. No directive. This is the section Erratum ID 5253 reports, because a VPN-IPv4 Next Hop carries an 8-octet Route Distinguisher and the lengths given here leave no room for one; RFC 8950 Section 2 answers the erratum with 24 or 48. The Errata section of rfc/short/rfc5549.md records both readings, and parseVPNNextHops in internal/core/bgp/attribute/mpnlri.go accepts 16 and 32 as the legacy form beside the 24 and 48 of RFC 8950. | | `7` | IANA Considerations | 0 | skipped (iana) | IANA Considerations. Records the allocation of Capability Code 5 for the Extended Next Hop Encoding capability from the IETF Review range of RFC 5226. Binds IANA, not a speaker. The value itself is an obligation on a speaker only through RFC5549-4-3, which site 4:3 maps. | | `8` | Security Considerations | 0 | walked | Security Considerations. States that the document raises no security issue beyond BGP-4 and the Multiprotocol Extensions, and that the IPv6 Next Hop Address can be an IPv4-mapped IPv6 address of RFC 4291. The last sentence addresses the network operator who configures next-hop security checks, states no capitalised keyword, and directs no speaker. | | `9` | Acknowledgments | 0 | skipped (acknowledgements) | Acknowledgments. Thanks Yakov Rekhter, Pranav Mehta and John Scudder. No obligation. | | `10` | References | 0 | skipped (references) | References. A heading whose two lists are sections 10.1 and 10.2. | | `10.1` | not stated | 0 | skipped (references) | Normative References: RFC 2119, RFC 2545, RFC 3107, RFC 4291, RFC 4364, RFC 4760 and RFC 5492. | | `10.2` | not stated | 0 | skipped (references) | Informative References: DYN-CAP, L2VPN-SIG, MESH-FMWK, RFC 4659, RFC 4684, RFC 4798, RFC 4925 and RFC 5226. The unnumbered Authors' Addresses that closes the document falls into this section, because the derivation opens a section only on a numbered heading at column 0. Neither states an obligation. | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `4:2` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The lead-in to the field list of the Capabilities Optional Parameter. Of the three bullets it introduces, only the Capability Code bullet carries an RFC 2119 keyword, and site 4:3 maps it to RFC5549-4-3; the Capability Length bullet and the Capability Value format bullet are written indicatively. The sentence therefore restates the obligation site 4:3 already carries and adds no separate one. | The fields in the Capabilities Optional Parameter MUST be set as follows: | ## Superseded RFC 5549 is obsoleted by RFC 8950. | Requirement | Disposition | Now stated at | Reason | |---|---|---|---| | [`RFC5549-4-1`](#rfc5549-4-1) A BGP speaker MUST only advertise IPv4 or VPN-IPv4 NLRI with an IPv6 Next Hop if it has ascertained via Capability Advertisement that the peer supports Extended Next Hop Encoding for the relevant AFI/SAFI pair (§4) | restated | RFC8950-4-1 | RFC 8950 Section 4 states the same obligation in the same words, that a speaker MUST only advertise IPv4 or VPN-IPv4 NLRI with an IPv6 next hop to a peer that has advertised the Extended Next Hop Encoding capability for the relevant AFI/SAFI pair | | [`RFC5549-4-2`](#rfc5549-4-2) A BGP speaker that wishes to advertise an IPv6 Next Hop for IPv4 or VPN-IPv4 NLRI MUST use the Capability Advertisement procedures defined in RFC 5492 (§4) | restated | RFC8950-4-2 | RFC 8950 Section 4 keeps the RFC 5492 Capability Advertisement procedures unchanged as the way to determine peer support | | [`RFC5549-4-3`](#rfc5549-4-3) The Capability Code field MUST be set to 5 (Extended Next Hop Encoding) (§4) | restated | RFC8950-4-3 | RFC 8950 Section 4 keeps Capability Code 5, and its Section 7 records that IANA moved the registration of that code point to RFC 8950 | | [`RFC5549-3-1`](#rfc5549-3-1) The BGP speaker receiving the advertisement MUST use the Length of Next Hop Address field to determine which network-layer protocol the next hop address belongs to (§3) | restated | RFC8950-3-1 | RFC 8950 Section 3 keeps the Length of Next Hop Address field as the field that tells a receiver which network-layer protocol the next-hop address belongs to | | [`RFC5549-5-1`](#rfc5549-5-1) When a next hop address needs to be passed along unchanged (e.g., Route Reflector), its encoding MUST NOT be changed (§5) | restated | RFC8950-5-1 | RFC 8950 Section 5 keeps the sentence unchanged, that an encoding passed along unchanged MUST NOT be changed | | [`RFC5549-4-4`](#rfc5549-4-4) MUST NOT send IPv6 Next Hop for IPv4 NLRI to peers that have not advertised Extended Next Hop Encoding capability (§4, Compatibility) | restated | RFC8950-4-1 | this is the negative spelling of the Section 4 sentence RFC5549-4-1 states positively, and RFC 8950 states it once, positively, as RFC8950-4-1. RFC 5549 has no section named Compatibility, so the cite on this line names a section the document does not contain | | [`RFC5549-5-2`](#rfc5549-5-2) By default, if a BGP session is running over IPvx, the next hop address SHOULD be specified as an IPvx address (§5) | restated | RFC8950-5-2 | RFC 8950 Section 5 keeps the default that a speaker putting its own address in as the next hop over an IPvx session SHOULD use an IPvx address | | [`RFC5549-4-5`](#rfc5549-4-5) The Extended Next Hop Encoding capability MAY be dynamically updated through the Dynamic Capability capability (§4) | dropped | not stated | RFC 8950 states no dynamic-capability permission. RFC 5549 Section 4 ends with a MAY to update the Extended Next Hop Encoding capability through the Dynamic Capability capability of the expired draft-ietf-idr-dynamic-cap, and RFC 8950 Section 4 ends instead with the paragraph that the capability does not influence whether an AFI/SAFI is allowed. The permission is gone, so no successor obligation exists to point at | | [`RFC5549-5-3`](#rfc5549-5-3) The default next-hop-address-family behavior may be overridden by policy (§5) | restated | RFC8950-5-3 | RFC 8950 Section 5 keeps the sentence that the default next-hop address family behavior may be overridden by policy | --- ### Page: RFC 5561 - LDP Capabilities https://ze-software.net/quality/rfc-compliance/rfc5561/ # RFC 5561 - LDP Capabilities No row in the public ledger. Every requirement this repository extracted from RFC 5561, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 0.0% | 0 of 5 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 5 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 5 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 5 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 0 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 5 | of 7 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 5 | of 5 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 100.0% | 5 of 5 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 5 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 5 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 5 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | No row in the public ledger | | Enrolment | Enrolled | | Requirements | 7 | | Gated MUST-level | 5 | | Not applicable, so out of scope | 5 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 0 | | Tagged units | 0 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5561.md` | | Requirement shard | `rfc/requirements/rfc5561.md` | | RFC text | `rfc/full/rfc5561.txt` | ## Enrolment Enrolled: ze implements base LDP (RFC 5036) only and has no RFC 5561 capability mechanism, so all 5 gated MUST-level requirements are {not-applicable} against the absent code path (no tests, per the annotation rule): RFC5561-3-1 (U/F bit set to 1), RFC5561-4-1 (include Dynamic Capability Announcement in Init), RFC5561-3-2 (silently ignore unknown capability TLV if U=1), RFC5561-x-1 (Reserved bits zero) and RFC5561-4-2 (MUST NOT send a Capability message) all cite the absence of any capability-TLV / Capability-message code path in internal/plugins/ldp/wire.go (EncodeInit:293, EncodeTLV:166, DecodeTLV:174, DecodeInit:324) and internal/plugins/ldp/session.go:419 (no 0x0202 encoder; no 0x0506 capability constant exists). No SHOULD/MAY requirements are gated. ## What the public ledger says No row in the public ledger, so its summary declares `| Support | - |` and docs/features/rfc-status.md carries no row for RFC 5561. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 0 | one part of the gated population | | Annotated instead of tested | 5 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **5** | every gated MUST falls in exactly one bucket above | **Annotated instead of tested (5):** [`RFC5561-3-1`](#rfc5561-3-1), [`RFC5561-4-1`](#rfc5561-4-1), [`RFC5561-3-2`](#rfc5561-3-2), [`RFC5561-x-1`](#rfc5561-x-1), [`RFC5561-4-2`](#rfc5561-4-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5561-3-1` | Capability TLVs MUST have the U-bit and F-bit set to 1 (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no LDP capability-TLV code path; the sole Init encoder EncodeInit emits only the Common Session Parameters TLV (internal/plugins/ldp/wire.go:293,313) and the generic EncodeTLV (internal/plugins/ldp/wire.go:166) writes a full-uint16 Type with no U/F bit fields | | `RFC5561-4-1` | An LSR MUST include the Dynamic Capability Announcement capability in its Initialization if it supports dynamic capability changes (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no dynamic-capability code path; the sole Init encoder EncodeInit (internal/plugins/ldp/wire.go:293) advertises no capability TLV and no 0x0506 constant exists, so the "if it supports dynamic capability changes" precondition is false for ze | | `RFC5561-3-2` | An LSR MUST silently ignore a capability TLV with an unknown type if U=1 (§3) | MUST | 3 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no LDP capability-TLV code path and never parses the U-bit; DecodeTLV reads Type as a full uint16 (internal/plugins/ldp/wire.go:174) and DecodeInit recognises only the Common Session Parameters TLV (internal/plugins/ldp/wire.go:324) | | `RFC5561-x-1` | Reserved bits in the capability TLV must be zero (Wire Format) | MUST | x | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no LDP capability-TLV code path; no S-bit or Reserved field is encoded anywhere, EncodeTLV writes only Type, Length and Value (internal/plugins/ldp/wire.go:166) | | `RFC5561-4-2` | An LSR MUST NOT send a Capability message unless the peer advertised support for Dynamic Capability Announcement (§4) | MUST NOT | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze has no Capability-message code path; there is no encoder for message type 0x0202 and the session read dispatch has no such case, treating it as an unknown message type (internal/plugins/ldp/session.go:419), so ze never sends one | | `RFC5561-4-3` | An LSR SHOULD support the Dynamic Capability Announcement capability for operational flexibility (§4) | SHOULD | 4 | **positive:** no positive test. **negative:** no negative test | | `RFC5561-3-3` | An LSR MAY tear down the session if a required capability is not supported by the peer (§3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5561-3-1`](#rfc5561-3-1) Capability TLVs MUST have the U-bit and F-bit set to 1 (§3) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no LDP capability-TLV code path; the sole Init encoder EncodeInit emits only the Common Session Parameters TLV (internal/plugins/ldp/wire.go:293,313) and the generic EncodeTLV (internal/plugins/ldp/wire.go:166) writes a full-uint16 Type with no U/F bit fields | | [`RFC5561-4-1`](#rfc5561-4-1) An LSR MUST include the Dynamic Capability Announcement capability in its Initialization if it supports dynamic capability changes (§4) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no dynamic-capability code path; the sole Init encoder EncodeInit (internal/plugins/ldp/wire.go:293) advertises no capability TLV and no 0x0506 constant exists, so the "if it supports dynamic capability changes" precondition is false for ze | | [`RFC5561-3-2`](#rfc5561-3-2) An LSR MUST silently ignore a capability TLV with an unknown type if U=1 (§3) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no LDP capability-TLV code path and never parses the U-bit; DecodeTLV reads Type as a full uint16 (internal/plugins/ldp/wire.go:174) and DecodeInit recognises only the Common Session Parameters TLV (internal/plugins/ldp/wire.go:324) | | [`RFC5561-x-1`](#rfc5561-x-1) Reserved bits in the capability TLV must be zero (Wire Format) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no LDP capability-TLV code path; no S-bit or Reserved field is encoded anywhere, EncodeTLV writes only Type, Length and Value (internal/plugins/ldp/wire.go:166) | | [`RFC5561-4-2`](#rfc5561-4-2) An LSR MUST NOT send a Capability message unless the peer advertised support for Dynamic Capability Announcement (§4) | no test | no test carries this requirement id; annotated {not-applicable}: ze has no Capability-message code path; there is no encoder for message type 0x0202 and the session read dispatch has no such case, treating it as an unknown message type (internal/plugins/ldp/session.go:419), so ze never sends one | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5561-3-1`](#rfc5561-3-1) Capability TLVs MUST have the U-bit and F-bit set to 1 (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5561-3-1, so no unit is bound to it. ### [`RFC5561-4-1`](#rfc5561-4-1) An LSR MUST include the Dynamic Capability Announcement capability in its Initialization if it supports dynamic capability changes (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5561-4-1, so no unit is bound to it. ### [`RFC5561-3-2`](#rfc5561-3-2) An LSR MUST silently ignore a capability TLV with an unknown type if U=1 (§3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5561-3-2, so no unit is bound to it. ### [`RFC5561-x-1`](#rfc5561-x-1) Reserved bits in the capability TLV must be zero (Wire Format) Audit verdict: not audited: no reader has judged these tests No test carries RFC5561-x-1, so no unit is bound to it. ### [`RFC5561-4-2`](#rfc5561-4-2) An LSR MUST NOT send a Capability message unless the peer advertised support for Dynamic Capability Announcement (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5561-4-2, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 5561, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5561, so its obligations are stated where they were written. --- ### Page: RFC 5575 - Dissemination of Flow Specification Rules https://ze-software.net/quality/rfc-compliance/rfc5575/ # RFC 5575 - Dissemination of Flow Specification Rules Partial. Every requirement this repository extracted from RFC 5575, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 16.7% | 2 of 12 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 50.0% | 6 of 12 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 12 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 12 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 12 | of 16 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 12 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 12 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 12 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 12 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 33.3% | 4 of 12 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 12 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 16 | | Gated MUST-level | 12 | | Not applicable, so out of scope | 0 | | Declared gaps | 4 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 12 | | Tagged units | 12 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5575.md` | | Requirement shard | `rfc/requirements/rfc5575.md` | | RFC text | `rfc/full/rfc5575.txt` | ## Enrolment Enrolled: BGP Flowspec (obsoleted by RFC 8955): 8 single-polarity positive (capability/family/component-ordering/reserved-bits) + 4 gap (Section 6 validation unimplemented, same root cause as RFC8956-5-1) ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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. **What the ledger says remains** Four Section 6 validation MUSTs unmet (same root cause as RFC8956-5-1): 6-1 no eBGP AS_PATH leftmost-neighbor enforcement; 6-2 no feasibility validation against the unicast RIB; 6-3 no flow-spec-vs-best-match originator comparison; 6-4 no more-specific/different-neighbor-AS check. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 2 | one part of the gated population | | Annotated instead of tested | 10 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **12** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (2):** [`RFC5575-4-3`](#rfc5575-4-3), [`RFC5575-4-4`](#rfc5575-4-4) **Annotated instead of tested (10):** [`RFC5575-4-1`](#rfc5575-4-1), [`RFC5575-4-2`](#rfc5575-4-2), [`RFC5575-6-1`](#rfc5575-6-1), [`RFC5575-6-2`](#rfc5575-6-2), [`RFC5575-6-3`](#rfc5575-6-3), [`RFC5575-6-4`](#rfc5575-6-4), [`RFC5575-4-5`](#rfc5575-4-5), [`RFC5575-4-6`](#rfc5575-4-6), [`RFC5575-4-7`](#rfc5575-4-7), [`RFC5575-7-1`](#rfc5575-7-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5575-4-1` | Implementations wishing to exchange flow specification rules MUST use BGP Capability Advertisement to exchange the Multiprotocol Extension Capability Code (Code 1) (§4) | MUST | 4 | **positive:** `unit/verify` [`TestIPv4FlowSpecNegotiatesMultiprotocolCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/plugin_test.go#L845). **negative:** no negative test. **{single-polarity}:** the flowspec plugin unconditionally maps its declared ipv4/flow family to a Multiprotocol (Code 1) capability during OPEN, and there is no wrong input the negotiation path rejects (internal/component/bgp/plugins/nlri/flowspec/register.go:19-22, types.go:47) | | `RFC5575-4-2` | The (AFI, SAFI) pair in the Multiprotocol Extension Capability MUST match the application using this NLRI-type (§4) | MUST | 4 | **positive:** `unit/verify` [`TestFlowSpecIPv4Basic`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L420). **positive:** `unit/verify` [`TestFlowSpecVPNFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L646). **negative:** no negative test. **{single-polarity}:** the (AFI 1, SAFI 133/134) assignment is a family-registration constant, not an input guard, so only the positive assignment is assertable (internal/component/bgp/plugins/nlri/flowspec/types.go:47-49, encode.go:79-95) | | `RFC5575-4-3` | Flow specification component types MUST appear in strictly ascending numeric order (§4) | MUST | 4 | **positive:** `unit/verify` [`TestFlowSpecComponentsAscendingOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1226). **positive:** `unit/verify` [`TestFlowSpecJoinsRepeatedTypeIntoOneComponent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1269). **negative:** `unit/verify` [`TestParseFlowSpecRefusesRepeatedComponentType`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1306) | | `RFC5575-4-4` | A present component MUST precede any component of higher numeric type value (§4) | MUST | 4 | **positive:** `unit/verify` [`TestFlowSpecComponentsAscendingOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1227). **negative:** `unit/verify` [`TestParseFlowSpecRefusesDescendingComponentType`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1334) | | `RFC5575-6-1` | BGP implementations MUST enforce that the AS_PATH attribute of a route received via eBGP contains the neighboring AS in the left-most position (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze runs no eBGP leftmost-neighbor-AS enforcement; the only AS_PATH ingress guard is RFC 4271 Section 9 loop detection and firstASInPath is used solely for MED neighbor comparison (internal/component/bgp/reactor/filter/loop_metrics.go:31, internal/component/bgp/plugins/rib/bestpath.go:544) | | `RFC5575-6-2` | Flow specification MUST be validated against unicast routing (feasibility check) (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze decodes a received flowspec NLRI and lowers it to the firewall with no feasibility validation against the unicast RIB, the same absence disclosed for RFC8956-5-1 (internal/component/bgp/plugins/nlri/flowspec/types.go:351, internal/plugins/flowspec-firewall/translate.go:166) | | `RFC5575-6-3` | Originator matching MUST be performed: the originator of the flow spec must match the originator of the best-match unicast route for the destination prefix (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** no code compares a flowspec's originator against the best-match unicast route's originator; this is part of the absent Section 6 validation procedure (internal/component/bgp/plugins/nlri/flowspec/) | | `RFC5575-6-4` | There must be no more-specific unicast routes from a different neighboring AS than the best-match route (§6) | MUST | 6 | **positive:** no positive test. **negative:** no negative test. **{gap}:** no code walks more-specific unicast routes to compare neighboring AS, because the Section 6 validation procedure is unimplemented (internal/component/bgp/plugins/nlri/flowspec/) | | `RFC5575-4-5` | Reserved bits in numeric operator format (bit 4) must be 0 (§4, Numeric Operator) | MUST | 4 | **positive:** `unit/verify` [`TestFlowSpecNumericOperatorReservedBitZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1421). **negative:** no negative test. **{single-polarity}:** the numeric operator byte is built as lenCode<<4 OR'd with only the comparison bits, so reserved bit 4 is never set on encode and decode ignores reserved bits (internal/component/bgp/plugins/nlri/flowspec/types_numeric.go:247-257) | | `RFC5575-4-6` | Reserved bits in bitmask operator format (bits 4-5) must be 0 (§4, Bitmask Operator) | MUST | 4 | **positive:** `unit/verify` [`TestFlowSpecBitmaskOperatorReservedBitsZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1449). **negative:** no negative test. **{single-polarity}:** bitmask components encode through the same operator builder using only match/not plus the len field, so reserved bits 4-5 are never set on encode (internal/component/bgp/plugins/nlri/flowspec/types_numeric.go:247-257) | | `RFC5575-4-7` | Reserved bits in Fragment bitmask (bits 0-3) must be zero (§4, Type 12) | MUST | 4 | **positive:** `unit/verify` [`TestFlowSpecFragmentReservedHighNibbleZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1478). **negative:** no negative test. **{single-polarity}:** the Fragment value is assembled from the four low-nibble flag constants, so the reserved high-nibble bits are never set on encode (internal/component/bgp/plugins/nlri/flowspec/types.go:201-206) | | `RFC5575-7-1` | Reserved bytes in Traffic-Marking extended community (bytes 2-6) must be zero (§7) | MUST | 7 | **positive:** `unit/verify` [`TestFlowSpecTrafficMarkingReservedBytesZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1510). **negative:** no negative test. **{single-polarity}:** the traffic-marking community is emitted with literal zero reserved bytes and only the trailing DSCP octet varies (internal/component/bgp/plugins/nlri/flowspec/encode.go:124-127) | | `RFC5575-3-1` | Standard BGP policy mechanisms (UPDATE filtering by NLRI prefix and community matching) SHOULD apply to flow specification NLRI (§3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5575-9-1` | Implementations SHOULD provide a mechanism to log the packet header of filtered traffic (§9) | SHOULD | 9 | **positive:** no positive test. **negative:** no negative test | | `RFC5575-9-2` | Implementations SHOULD provide a mechanism to count the number of matches for a given flow specification rule (§9) | SHOULD | 9 | **positive:** no positive test. **negative:** no negative test | | `RFC5575-7-2` | User-defined community mappings MAY be used for platform-specific behaviors (§7) | MAY | 7 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5575-6-1`](#rfc5575-6-1) BGP implementations MUST enforce that the AS_PATH attribute of a route received via eBGP contains the neighboring AS in the left-most position (§6) | {gap}, no test | ze runs no eBGP leftmost-neighbor-AS enforcement; the only AS_PATH ingress guard is RFC 4271 Section 9 loop detection and firstASInPath is used solely for MED neighbor comparison (internal/component/bgp/reactor/filter/loop_metrics.go:31, internal/component/bgp/plugins/rib/bestpath.go:544) | | [`RFC5575-6-2`](#rfc5575-6-2) Flow specification MUST be validated against unicast routing (feasibility check) (§6) | {gap}, no test | ze decodes a received flowspec NLRI and lowers it to the firewall with no feasibility validation against the unicast RIB, the same absence disclosed for RFC8956-5-1 (internal/component/bgp/plugins/nlri/flowspec/types.go:351, internal/plugins/flowspec-firewall/translate.go:166) | | [`RFC5575-6-3`](#rfc5575-6-3) Originator matching MUST be performed: the originator of the flow spec must match the originator of the best-match unicast route for the destination prefix (§6) | {gap}, no test | no code compares a flowspec's originator against the best-match unicast route's originator; this is part of the absent Section 6 validation procedure (internal/component/bgp/plugins/nlri/flowspec/) | | [`RFC5575-6-4`](#rfc5575-6-4) There must be no more-specific unicast routes from a different neighboring AS than the best-match route (§6) | {gap}, no test | no code walks more-specific unicast routes to compare neighboring AS, because the Section 6 validation procedure is unimplemented (internal/component/bgp/plugins/nlri/flowspec/) | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5575-4-1`](#rfc5575-4-1) Implementations wishing to exchange flow specification rules MUST use BGP Capability Advertisement to exchange the Multiprotocol Extension Capability Code (Code 1) (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestIPv4FlowSpecNegotiatesMultiprotocolCapability`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/plugin_test.go#L845) | unit/verify | unproven | ### [`RFC5575-4-2`](#rfc5575-4-2) The (AFI, SAFI) pair in the Multiprotocol Extension Capability MUST match the application using this NLRI-type (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestFlowSpecIPv4Basic`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L420) | unit/verify | unproven | | positive | [`TestFlowSpecVPNFamily`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L646) | unit/verify | unproven | ### [`RFC5575-4-3`](#rfc5575-4-3) Flow specification component types MUST appear in strictly ascending numeric order (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseFlowSpecRefusesRepeatedComponentType`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1306) | unit/verify | unproven | | positive | [`TestFlowSpecComponentsAscendingOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1226) | unit/verify | unproven | | positive | [`TestFlowSpecJoinsRepeatedTypeIntoOneComponent`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1269) | unit/verify | unproven | ### [`RFC5575-4-4`](#rfc5575-4-4) A present component MUST precede any component of higher numeric type value (§4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestParseFlowSpecRefusesDescendingComponentType`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1334) | unit/verify | unproven | | positive | [`TestFlowSpecComponentsAscendingOrder`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1227) | unit/verify | unproven | ### [`RFC5575-6-1`](#rfc5575-6-1) BGP implementations MUST enforce that the AS_PATH attribute of a route received via eBGP contains the neighboring AS in the left-most position (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC5575-6-1, so no unit is bound to it. ### [`RFC5575-6-2`](#rfc5575-6-2) Flow specification MUST be validated against unicast routing (feasibility check) (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC5575-6-2, so no unit is bound to it. ### [`RFC5575-6-3`](#rfc5575-6-3) Originator matching MUST be performed: the originator of the flow spec must match the originator of the best-match unicast route for the destination prefix (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC5575-6-3, so no unit is bound to it. ### [`RFC5575-6-4`](#rfc5575-6-4) There must be no more-specific unicast routes from a different neighboring AS than the best-match route (§6) Audit verdict: not audited: no reader has judged these tests No test carries RFC5575-6-4, so no unit is bound to it. ### [`RFC5575-4-5`](#rfc5575-4-5) Reserved bits in numeric operator format (bit 4) must be 0 (§4, Numeric Operator) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestFlowSpecNumericOperatorReservedBitZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1421) | unit/verify | unproven | ### [`RFC5575-4-6`](#rfc5575-4-6) Reserved bits in bitmask operator format (bits 4-5) must be 0 (§4, Bitmask Operator) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestFlowSpecBitmaskOperatorReservedBitsZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1449) | unit/verify | unproven | ### [`RFC5575-4-7`](#rfc5575-4-7) Reserved bits in Fragment bitmask (bits 0-3) must be zero (§4, Type 12) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestFlowSpecFragmentReservedHighNibbleZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1478) | unit/verify | unproven | ### [`RFC5575-7-1`](#rfc5575-7-1) Reserved bytes in Traffic-Marking extended community (bytes 2-6) must be zero (§7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestFlowSpecTrafficMarkingReservedBytesZero`](https://github.com/ze-software/ze/blob/main/internal/component/bgp/plugins/nlri/flowspec/types_test.go#L1510) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5575, so no reviewer has walked its text sentence by sentence. ## Superseded RFC 5575 is obsoleted by RFC 8955. | Requirement | Disposition | Now stated at | Reason | |---|---|---|---| | [`RFC5575-4-1`](#rfc5575-4-1) Implementations wishing to exchange flow specification rules MUST use BGP Capability Advertisement to exchange the Multiprotocol Extension Capability Code (Code 1) (§4) | restated | RFC8955-4-1 | RFC 8955 Section 4 keeps the sentence, that implementations wishing to exchange Flow Specification MUST use BGP's Capability Advertisement facility to exchange the Multiprotocol Extension Capability Code (Code 1) | | [`RFC5575-4-2`](#rfc5575-4-2) The (AFI, SAFI) pair in the Multiprotocol Extension Capability MUST match the application using this NLRI-type (§4) | restated | RFC8955-4-2 | RFC 8955 Section 4 makes the pair explicit rather than leaving it to the application: (AFI 1, SAFI 133) for IPv4 Flow Specification and (AFI 1, SAFI 134) for VPNv4 Flow Specification | | [`RFC5575-4-3`](#rfc5575-4-3) Flow specification component types MUST appear in strictly ascending numeric order (§4) | restated | RFC8955-4.2-1 | the NLRI value encoding moved from Section 4 to Section 4.2, which keeps the strict type ordering by increasing numerical order | | [`RFC5575-4-4`](#rfc5575-4-4) A present component MUST precede any component of higher numeric type value (§4) | restated | RFC8955-4.2-2 | RFC 8955 Section 4.2 keeps the rule that a component, if present, MUST precede any component of higher numeric type value | | [`RFC5575-6-1`](#rfc5575-6-1) BGP implementations MUST enforce that the AS_PATH attribute of a route received via eBGP contains the neighboring AS in the left-most position (§6) | restated | RFC8955-6-2 | RFC 8955 Section 6 keeps the eBGP leftmost-AS enforcement in the same words, and keeps the reason, that the rule is optional in the BGP specification and necessary here for security | | [`RFC5575-6-2`](#rfc5575-6-2) Flow specification MUST be validated against unicast routing (feasibility check) (§6) | restated | RFC8955-6-1 | RFC 8955 Section 6 keeps the feasibility rule and scopes it, adding that it applies in the absence of explicit configuration and that SAFI 133 validates against SAFI 1 while SAFI 134 validates against SAFI 128 | | [`RFC5575-6-3`](#rfc5575-6-3) Originator matching MUST be performed: the originator of the flow spec must match the originator of the best-match unicast route for the destination prefix (§6) | restated | RFC8955-6-1 | originator matching is condition (b) of the RFC 8955 Section 6 list, in the same words, and RFC 8955 states the three conditions as one feasibility rule rather than three. Condition (b) is moot when condition (a) is relaxed by explicit configuration | | [`RFC5575-6-4`](#rfc5575-6-4) There must be no more-specific unicast routes from a different neighboring AS than the best-match route (§6) | restated | RFC8955-6-1 | the more-specific-route rule is condition (c) of the RFC 8955 Section 6 list, in the same words, and RFC 8955 states the three conditions as one feasibility rule rather than three. Condition (c) is moot when condition (a) is relaxed by explicit configuration | | [`RFC5575-4-5`](#rfc5575-4-5) Reserved bits in numeric operator format (bit 4) must be 0 (§4, Numeric Operator) | restated | RFC8955-4.2.1.1-3 | RFC 8955 Section 4.2.1.1 keeps the numeric operator reserved bit at 0 on encoding and adds the receive half, that it MUST be ignored during decoding | | [`RFC5575-4-6`](#rfc5575-4-6) Reserved bits in bitmask operator format (bits 4-5) must be 0 (§4, Bitmask Operator) | restated | RFC8955-4.2.1.2-1 | RFC 8955 Section 4.2.1.2 keeps the bitmask operator reserved bits at 0 on encoding and adds the receive half, that they MUST be ignored during decoding | | [`RFC5575-4-7`](#rfc5575-4-7) Reserved bits in Fragment bitmask (bits 0-3) must be zero (§4, Type 12) | restated | RFC8955-4.2.2.12-2 | RFC 8955 Section 4.2.2.12 keeps the Fragment bitmask reserved bits at 0 on encoding and adds the receive half, that they MUST be ignored during decoding | | [`RFC5575-7-1`](#rfc5575-7-1) Reserved bytes in Traffic-Marking extended community (bytes 2-6) must be zero (§7) | restated | RFC8955-7.5-1 | the traffic-marking action moved from Section 7 to Section 7.5, which keeps the reserved bits at 0 on encoding and adds the receive half, that they MUST be ignored during decoding | | [`RFC5575-3-1`](#rfc5575-3-1) Standard BGP policy mechanisms (UPDATE filtering by NLRI prefix and community matching) SHOULD apply to flow specification NLRI (§3) | unextracted | §3 | RFC 8955 Section 3 states the obligation with a lowercase keyword, that standard BGP policy mechanisms such as UPDATE filtering by NLRI prefix and community matching must apply to the Flow specification defined NLRI-type. rfc/short/rfc8955.md declares no row for it | | [`RFC5575-9-1`](#rfc5575-9-1) Implementations SHOULD provide a mechanism to log the packet header of filtered traffic (§9) | restated | RFC8955-9-1 | RFC 8955 Section 9 keeps the SHOULD to provide a mechanism to log the packet header of filtered traffic | | [`RFC5575-9-2`](#rfc5575-9-2) Implementations SHOULD provide a mechanism to count the number of matches for a given flow specification rule (§9) | restated | RFC8955-9-2 | RFC 8955 Section 9 keeps the SHOULD to provide a mechanism to count the number of matches for a given Flow Specification rule | | [`RFC5575-7-2`](#rfc5575-7-2) User-defined community mappings MAY be used for platform-specific behaviors (§7) | unextracted | §7.6 | RFC 8955 gives the paragraph its own section, 7.6, and keeps it word for word, that a user-defined community value can be mapped to platform-specific or network-specific behavior via user configuration. rfc/short/rfc8955.md declares no row for it | --- ### Page: RFC 5701 - IPv6 Address Specific BGP Extended Community Attribute https://ze-software.net/quality/rfc-compliance/rfc5701/ # RFC 5701 - IPv6 Address Specific BGP Extended Community Attribute Partial. Every requirement this repository extracted from RFC 5701, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 75.0% | 3 of 4 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 4 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 4 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 6 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 4 | of 6 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 4 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 4 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 4 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 4 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 25.0% | 1 of 4 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 4 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 6 | | Gated MUST-level | 4 | | Not applicable, so out of scope | 0 | | Declared gaps | 1 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 6 | | Tagged units | 6 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5701.md` | | Requirement shard | `rfc/requirements/rfc5701.md` | | RFC text | `rfc/full/rfc5701.txt` | ## Enrolment Enrolled: IPv6 Address Specific BGP Extended Community Attribute (code 25): four MUST-level requirements over the codec (internal/core/bgp/attribute/community.go). RFC5701-2-1 (optional transitive, O=1 T=1) has both polarities in TestRFC5701IPv6ExtCommunityFlags: Flags() (community.go:483) returns exactly FlagOptional\|FlagTransitive, never the non-transitive 0x80 or a well-known/partial form. RFC5701-2-2 (each community exactly 20 octets) has both polarities in TestRFC5701IPv6ExtCommunityTwentyOctets: the [20]byte type, Len()=20*count and WriteTo lay each community on a 20-octet stride, and the second community starts at offset 20 not the 8-octet RFC 4360 stride. RFC5701-2-3 (length a multiple of 20) has both polarities in TestRFC5701IPv6ExtCommunityLengthMultipleOf20: a 40-octet buffer parses to 2 communities while 19/21/8/39/41 are rejected with ErrInvalidLength (community.go:515). RFC5701-4-1 (RFC 4271/7606 malformed optional-transitive handling) is {gap}: code 25 has no RFC 7606 structural validator (attrValidators[25] unset), so a malformed code-25 attribute passes structural validation rather than being treat-as-withdrawn; the lazy parser rejects a bad length only on access. Disclosed in the docs/features/rfc-status.md RFC 5701 row (Partial); same code-25 omission tracked under RFC 7606. The 2-4 SHOULD (non-transitive not propagated to external peers) and 2-5 MAY are not gated. ## What the public ledger says **Status:** Partial **What the ledger says is covered:** 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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc5701.md). **What the ledger says remains** One MUST gap, gated in [`rfc/short/rfc5701.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5701.md): 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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 3 | one part of the gated population | | Annotated instead of tested | 1 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **4** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (3):** [`RFC5701-2-1`](#rfc5701-2-1), [`RFC5701-2-2`](#rfc5701-2-2), [`RFC5701-2-3`](#rfc5701-2-3) **Annotated instead of tested (1):** [`RFC5701-4-1`](#rfc5701-4-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5701-2-1` | Attribute is optional, transitive (O=1 T=1) per BGP-4 attribute handling (§2) | MUST | 2 | **positive:** `unit/verify` [`TestRFC5701IPv6ExtCommunityFlags`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L14). **negative:** `unit/verify` [`TestRFC5701IPv6ExtCommunityFlags`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L19) | | `RFC5701-2-2` | Each community is encoded as exactly 20 octets (§2) | MUST | 2 | **positive:** `unit/verify` [`TestRFC5701IPv6ExtCommunityTwentyOctets`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L46). **negative:** `unit/verify` [`TestRFC5701IPv6ExtCommunityTwentyOctets`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L50) | | `RFC5701-2-3` | Attribute length must be a multiple of 20 octets (§2, Validation) | MUST | 2 | **positive:** `unit/verify` [`TestRFC5701IPv6ExtCommunityLengthMultipleOf20`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L80). **negative:** `unit/verify` [`TestRFC5701IPv6ExtCommunityLengthMultipleOf20`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L83) | | `RFC5701-4-1` | Follow RFC 4271 transitive attribute handling for malformed optional transitive attributes (§4, referencing OPT_TRANS/RFC 7606) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{gap}:** Ze does not apply RFC 7606 malformed-attribute handling (treat-as-withdraw) to a malformed IPv6 Address Specific Extended Community (attribute code 25). The RFC 7606 structural validation pass has no per-attribute validator for code 25 (internal/component/bgp/message/rfc7606.go attrValidators[25] is unset), so a code-25 attribute whose length is not a multiple of 20 passes structural validation instead of being treat-as-withdrawn. The lazy value parser ParseIPv6ExtendedCommunities (internal/core/bgp/attribute/community.go:515) does reject a bad length with ErrInvalidLength, but only when the attribute is later accessed, which is not the RFC 4271/7606-mandated optional-transitive treat-as-withdraw at ingest. This is the same code-25 omission already disclosed under RFC 7606 (§7.15). Disclosed in docs/features/rfc-status.md | | `RFC5701-2-4` | Non-transitive communities (Type=0x40) should not be propagated to external peers (§2) | SHOULD | 2 | **positive:** no positive test. **negative:** no negative test | | `RFC5701-2-5` | Organization assigned the IPv6 address can encode any information in Local Administrator field (§2) | MAY | 2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5701-4-1`](#rfc5701-4-1) Follow RFC 4271 transitive attribute handling for malformed optional transitive attributes (§4, referencing OPT_TRANS/RFC 7606) | {gap}, no test | Ze does not apply RFC 7606 malformed-attribute handling (treat-as-withdraw) to a malformed IPv6 Address Specific Extended Community (attribute code 25). The RFC 7606 structural validation pass has no per-attribute validator for code 25 (internal/component/bgp/message/rfc7606.go attrValidators[25] is unset), so a code-25 attribute whose length is not a multiple of 20 passes structural validation instead of being treat-as-withdrawn. The lazy value parser ParseIPv6ExtendedCommunities (internal/core/bgp/attribute/community.go:515) does reject a bad length with ErrInvalidLength, but only when the attribute is later accessed, which is not the RFC 4271/7606-mandated optional-transitive treat-as-withdraw at ingest. This is the same code-25 omission already disclosed under RFC 7606 (§7.15). Disclosed in docs/features/rfc-status.md | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5701-2-1`](#rfc5701-2-1) Attribute is optional, transitive (O=1 T=1) per BGP-4 attribute handling (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5701IPv6ExtCommunityFlags`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L19) | unit/verify | unproven | | positive | [`TestRFC5701IPv6ExtCommunityFlags`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L14) | unit/verify | unproven | ### [`RFC5701-2-2`](#rfc5701-2-2) Each community is encoded as exactly 20 octets (§2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5701IPv6ExtCommunityTwentyOctets`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L50) | unit/verify | unproven | | positive | [`TestRFC5701IPv6ExtCommunityTwentyOctets`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L46) | unit/verify | unproven | ### [`RFC5701-2-3`](#rfc5701-2-3) Attribute length must be a multiple of 20 octets (§2, Validation) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5701IPv6ExtCommunityLengthMultipleOf20`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L83) | unit/verify | unproven | | positive | [`TestRFC5701IPv6ExtCommunityLengthMultipleOf20`](https://github.com/ze-software/ze/blob/main/internal/core/bgp/attribute/rfc5701_ipv6_extcommunity_test.go#L80) | unit/verify | unproven | ### [`RFC5701-4-1`](#rfc5701-4-1) Follow RFC 4271 transitive attribute handling for malformed optional transitive attributes (§4, referencing OPT_TRANS/RFC 7606) Audit verdict: not audited: no reader has judged these tests No test carries RFC5701-4-1, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 5701, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5701, so its obligations are stated where they were written. --- ### Page: RFC 5709 - OSPFv2 HMAC-SHA Cryptographic Authentication https://ze-software.net/quality/rfc-compliance/rfc5709/ # RFC 5709 - OSPFv2 HMAC-SHA Cryptographic Authentication Experimental. Every requirement this repository extracted from RFC 5709, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 86.7% | 13 of 15 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 13.3% | 2 of 15 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 15 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 0.0% | 0 of 15 gated MUSTs | no test carries the requirement id, whether or not a gap states why | | Proven by a recorded break | 0.0% | 0 of 28 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 15 | of 20 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 15 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 15 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 15 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 15 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | The 7 shares marked as a part above are the whole of the 15 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | ok | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 20 | | Gated MUST-level | 15 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 28 | | Tagged units | 28 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5709.md` | | Requirement shard | `rfc/requirements/rfc5709.md` | | RFC text | `rfc/full/rfc5709.txt` | ## Enrolment Enrolled: OSPFv2 HMAC-SHA Cryptographic Authentication (RFC 5709, AuType 2): 13 MET (HMAC-SHA-256, AuType=2, Auth-Data-Length, crypto sequence, trailer digest, Apad fill, Ko derivation, Ipad/Opad, checksum-zero, receive recompute, Key-ID selection, key rollover) + 2 single-polarity positive (per-KeyID algorithm config, no revert-to-unauthenticated on expiry). Shares RFC 7474 Sign/Verify backend ## What the public ledger says **Status:** Experimental **What the ledger says is covered:** OSPFv2 cryptographic authentication path. **What the ledger says remains:** Same OSPF experimental status. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 13 | one part of the gated population | | Annotated instead of tested | 2 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **15** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (13):** [`RFC5709-3-1`](#rfc5709-3-1), [`RFC5709-3.1-1`](#rfc5709-3.1-1), [`RFC5709-3.1-2`](#rfc5709-3.1-2), [`RFC5709-3.1-3`](#rfc5709-3.1-3), [`RFC5709-3.1-4`](#rfc5709-3.1-4), [`RFC5709-3.3-1`](#rfc5709-3.3-1), [`RFC5709-3.3-2`](#rfc5709-3.3-2), [`RFC5709-3.3-3`](#rfc5709-3.3-3), [`RFC5709-3.3-4`](#rfc5709-3.3-4), [`RFC5709-3.3-5`](#rfc5709-3.3-5), [`RFC5709-3.4-1`](#rfc5709-3.4-1), [`RFC5709-3.4-2`](#rfc5709-3.4-2), [`RFC5709-3.2-1`](#rfc5709-3.2-1) **Annotated instead of tested (2):** [`RFC5709-3-5`](#rfc5709-3-5), [`RFC5709-3.2-2`](#rfc5709-3.2-2) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5709-3-1` | Implement HMAC-SHA-256 for OSPFv2 Cryptographic Authentication (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L72). **negative:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L82) | | `RFC5709-3-2` | Implement HMAC-SHA-1 (Section 3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5709-3-3` | Implement Keyed-MD5 for backwards compatibility with RFC 2328 deployments (Section 3) | SHOULD | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5709-3-4` | Implement HMAC-SHA-384 and HMAC-SHA-512 (Section 3) | MAY | 3 | **positive:** no positive test. **negative:** no negative test | | `RFC5709-3-5` | Allow operators to configure any supported algorithm for any given Key ID value (Section 3) | MUST | 3 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L77). **negative:** no negative test. **{single-polarity}:** keyConfig binds KeyID and Algorithm independently and resolveChainKeys/signKey honor each per-key algorithm with no fixed algorithm-per-KeyID mapping, so this is a permissive config capability with no forbidden (supported-algorithm, KeyID) pairing to reject (internal/plugins/ospf/auth_keystore.go:239-253, :292-324) | | `RFC5709-3.1-1` | Set AuType to 2 (Cryptographic Authentication) for SHA/HMAC-authenticated packets (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestEngineSignPacketCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_wiring_test.go#L56). **negative:** `unit/verify` [`TestOSPFAuthStoreSignVerify`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L70) | | `RFC5709-3.1-2` | Set the Authentication Data Length field to the hash length in bytes (20/32/48/64) (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L60). **negative:** `unit/verify` [`TestOSPFAuthCryptoRejectsExtraTrailerBytes`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L224) | | `RFC5709-3.1-3` | Set the 32-bit Cryptographic Sequence Number per RFC 2328 Appendix D (Section 3.1) | MUST | 3.1 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L62). **negative:** `unit/verify` [`TestOSPFAuthReplay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L137) | | `RFC5709-3.1-4` | Append the computed digest after the OSPF packet (Authentication Trailer), not inside the 8-byte auth field (Section 3.1, Section 3.3) | MUST | 3.1 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L67). **negative:** `unit/verify` [`TestOSPFAuthCryptoRejectsExtraTrailerBytes`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L225) | | `RFC5709-3.3-1` | Fill the Authentication Trailer with Apad (0x878FE1F3 repeated L/4 times) before computing the hash (Section 3.3) | MUST | 3.3 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L73). **negative:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L91) | | `RFC5709-3.3-2` | Derive Ko to length L: Ko = K, H(K), or K zero-padded to L (Section 3.3) | MUST | 3.3 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L74). **negative:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L83) | | `RFC5709-3.3-3` | Compute First-Hash = H(Ko XOR Ipad \|\| OSPFv2 Packet) and Second-Hash = H(Ko XOR Opad \|\| First-Hash) (Section 3.3) | MUST | 3.3 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L75). **negative:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L84) | | `RFC5709-3.3-4` | Place Second-Hash as the Authentication Data of length L in the trailer (Section 3.3) | MUST | 3.3 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L69). **negative:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L92) | | `RFC5709-3.3-5` | Set the OSPF header Checksum field to 0 for AuType 2 packets, per RFC 2328 D.4.3 (Section 3.3, RFC 2328 D.4.3) | MUST | 3.3 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L65). **negative:** `unit/verify` [`TestOSPFAuthCryptoChecksumOctetAuthenticated`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L354) | | `RFC5709-3.4-1` | On receive, save the wire digest, replace the trailer with Apad, recompute, and compare (Section 3.4) | MUST | 3.4 | **positive:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L76). **negative:** `unit/verify` [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L93) | | `RFC5709-3.4-2` | Select algorithm/key on receive implicitly from the packet's Key ID (Section 3.4) | MUST | 3.4 | **positive:** `unit/verify` [`TestOSPFAuthCryptoRejectsKeyIDMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L245). **negative:** `unit/verify` [`TestOSPFAuthCryptoRejectsKeyIDMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L242) | | `RFC5709-3.2-1` | Ensure a new key's KeyStartGenerate <= the old key's KeyStopGenerate on rollover (Section 3.2) | MUST | 3.2 | **positive:** `unit/verify` [`TestKeyRolloverOverlapAccepted`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_test.go#L558). **negative:** `unit/verify` [`TestKeyRolloverGapRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_test.go#L571) | | `RFC5709-3.2-2` | Revert to an unauthenticated condition when the last key expires (Section 3.2) | MUST NOT | 3.2 | **positive:** `unit/verify` [`TestSignKeyNoRevertWhenAllExpired`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L298). **negative:** no negative test. **{single-polarity}:** selectSendKey returns the most-recently-starting key when every send-lifetime has expired and signKey never yields AuTypeNull for a resolved chain, so the forbidden revert-to-unauthenticated transition is structurally absent and there is no packet-reject direction (internal/plugins/ospf/auth_keystore.go:263-287) | | `RFC5709-3.2-3` | Set KeyStartAccept < KeyStartGenerate and KeyStopGenerate < KeyStopAccept (Section 3.2) | SHOULD | 3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5709-3.2-4` | Never send the Authentication Key or Algorithm over the wire in cleartext; persist key storage across restart (Section 3.2) | SHOULD | 3.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs RFC 5709 declares no gap, and every gated MUST it carries has a test bound to it. ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5709-3-1`](#rfc5709-3-1) Implement HMAC-SHA-256 for OSPFv2 Cryptographic Authentication (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L82) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L72) | unit/verify | unproven | ### [`RFC5709-3-5`](#rfc5709-3-5) Allow operators to configure any supported algorithm for any given Key ID value (Section 3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L77) | unit/verify | unproven | ### [`RFC5709-3.1-1`](#rfc5709-3.1-1) Set AuType to 2 (Cryptographic Authentication) for SHA/HMAC-authenticated packets (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthStoreSignVerify`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L70) | unit/verify | unproven | | positive | [`TestEngineSignPacketCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_wiring_test.go#L56) | unit/verify | unproven | ### [`RFC5709-3.1-2`](#rfc5709-3.1-2) Set the Authentication Data Length field to the hash length in bytes (20/32/48/64) (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthCryptoRejectsExtraTrailerBytes`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L224) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L60) | unit/verify | unproven | ### [`RFC5709-3.1-3`](#rfc5709-3.1-3) Set the 32-bit Cryptographic Sequence Number per RFC 2328 Appendix D (Section 3.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthReplay`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L137) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L62) | unit/verify | unproven | ### [`RFC5709-3.1-4`](#rfc5709-3.1-4) Append the computed digest after the OSPF packet (Authentication Trailer), not inside the 8-byte auth field (Section 3.1, Section 3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthCryptoRejectsExtraTrailerBytes`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L225) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L67) | unit/verify | unproven | ### [`RFC5709-3.3-1`](#rfc5709-3.3-1) Fill the Authentication Trailer with Apad (0x878FE1F3 repeated L/4 times) before computing the hash (Section 3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L91) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L73) | unit/verify | unproven | ### [`RFC5709-3.3-2`](#rfc5709-3.3-2) Derive Ko to length L: Ko = K, H(K), or K zero-padded to L (Section 3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L83) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L74) | unit/verify | unproven | ### [`RFC5709-3.3-3`](#rfc5709-3.3-3) Compute First-Hash = H(Ko XOR Ipad || OSPFv2 Packet) and Second-Hash = H(Ko XOR Opad || First-Hash) (Section 3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L84) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L75) | unit/verify | unproven | ### [`RFC5709-3.3-4`](#rfc5709-3.3-4) Place Second-Hash as the Authentication Data of length L in the trailer (Section 3.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L92) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L69) | unit/verify | unproven | ### [`RFC5709-3.3-5`](#rfc5709-3.3-5) Set the OSPF header Checksum field to 0 for AuType 2 packets, per RFC 2328 D.4.3 (Section 3.3, RFC 2328 D.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthCryptoChecksumOctetAuthenticated`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L354) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L65) | unit/verify | unproven | ### [`RFC5709-3.4-1`](#rfc5709-3.4-1) On receive, save the wire digest, replace the trailer with Apad, recompute, and compare (Section 3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L93) | unit/verify | unproven | | positive | [`TestOSPFAuthSignVerifyCrypto`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L76) | unit/verify | unproven | ### [`RFC5709-3.4-2`](#rfc5709-3.4-2) Select algorithm/key on receive implicitly from the packet's Key ID (Section 3.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOSPFAuthCryptoRejectsKeyIDMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L242) | unit/verify | unproven | | positive | [`TestOSPFAuthCryptoRejectsKeyIDMismatch`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/packet/auth_verify_test.go#L245) | unit/verify | unproven | ### [`RFC5709-3.2-1`](#rfc5709-3.2-1) Ensure a new key's KeyStartGenerate <= the old key's KeyStopGenerate on rollover (Section 3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestKeyRolloverGapRejected`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_test.go#L571) | unit/verify | unproven | | positive | [`TestKeyRolloverOverlapAccepted`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/config_test.go#L558) | unit/verify | unproven | ### [`RFC5709-3.2-2`](#rfc5709-3.2-2) Revert to an unauthenticated condition when the last key expires (Section 3.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSignKeyNoRevertWhenAllExpired`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/auth_keystore_test.go#L298) | unit/verify | unproven | ## Extraction sign-off No extraction sign-off exists for RFC 5709, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5709, so its obligations are stated where they were written. --- ### Page: RFC 5798 - Virtual Router Redundancy Protocol (VRRP) Version 3 for IPv4 and IPv6 https://ze-software.net/quality/rfc-compliance/rfc5798/ # RFC 5798 - Virtual Router Redundancy Protocol (VRRP) Version 3 for IPv4 and IPv6 Partial. Every requirement this repository extracted from RFC 5798, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 60.0% | 33 of 55 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 0.0% | 0 of 55 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | Proven by a recorded break | 100.0% | 73 of 73 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 55 | of 80 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 0 | of 55 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 0.0% | 0 of 55 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 55 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 55 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | One polarity, unexcused | 12.7% | 7 of 55 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | No test at all | 27.3% | 15 of 55 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 55 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | bad | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 80 | | Gated MUST-level | 55 | | Not applicable, so out of scope | 0 | | Declared gaps | 0 | | Gated with no test | 15 | | Nightly-only evidence | 0 | | Test tags | 73 | | Tagged units | 73 | | Recorded audit verdicts | 0 | | Discrimination records | 73 | | Summary | `rfc/short/rfc5798.md` | | Requirement shard | `rfc/requirements/rfc5798.md` | | RFC text | `rfc/full/rfc5798.txt` | ## Enrolment Enrolled: VRRP Version 3 for IPv4 and IPv6 (RFC 5798, obsoleted by RFC 9568): the VRRPv3 document ze actually speaks on the IPv4 wire. Every one of its 55 gated requirements carries a {superseded} marker naming where the obligation lives in RFC 9568, and 51 of them are restated there unchanged, so ze implements them through the same producers rfc/short/rfc9568.md gates: internal/plugins/vrrp/packet (advert encode/decode, checksum), internal/plugins/vrrp/fsm (Initialize/Backup/Master), internal/plugins/vrrp/transport (proto 112, GTSM, GARP/NA). The row that is NOT a restatement is RFC5798-5.2.8-1: Section 5.2.8 puts a pseudo-header under the checksum for both families, RFC 9568 Section 5.2.8 removes it for IPv4, and ze transmits this document form because keepalived and the pre-RFC-9568 base require it (pseudoSumV4Legacy and FillChecksum, internal/plugins/vrrp/packet/checksum.go). Tagged under RFC5798 ids on 2026-09-05: 33 of the 55 gated MUSTs carry a positive and a negative test with a discrimination record for each, and 7 more carry a recorded positive proof whose obligation has no negative case, because the producer writes a constant. ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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. **What the ledger says remains** 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:** [`rfc/short/rfc5798.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5798.md) declares 80 requirements, 55 of them MUST-level, and every one carries a `{superseded}` marker naming where RFC 9568 states it. 33 of those 55 are proven under RFC5798 ids in both polarities, each polarity carrying a discrimination record. 7 more carry a recorded positive proof and no negative case, because the producer writes a constant no input changes: [`RFC5798-5.1.1.3-1`](#rfc5798-5.1.1.3-1), [`RFC5798-6.4.3-2`](#rfc5798-6.4.3-2), [`RFC5798-7.2-1`](#rfc5798-7.2-1) through [`RFC5798-7.2-4`](#rfc5798-7.2-4), and [`RFC5798-8.2.2-1`](#rfc5798-8.2.2-1). The 15 that carry no tag are 9 whose RFC 9568 twin is recorded `{not-applicable}`, 2 whose twin is recorded `{gap}` ([`RFC5798-6.4.3-3`](#rfc5798-6.4.3-3) and [`RFC5798-6.4.3-4`](#rfc5798-6.4.3-4), the IPv6 Neighbor Advertisement Router flag and the Router Advertisement a Master owes), 2 that RFC 9568 dropped rather than restated ([`RFC5798-7.4-1`](#rfc5798-7.4-1) and the Token Ring appendix RFC5798-A.2-1), [`RFC5798-5.1.2.3-1`](#rfc5798-5.1.2.3-1), whose only evidence is an integration-gated test the privileged QEMU suite runs and `./le rfc discriminate-record` cannot reach, and [`RFC5798-7.1-3`](#rfc5798-7.1-3), which ze does not fully implement: Section 7.1 makes the receiver discard an advertisement when the local router is the IPvX address owner, and `Decode` ([`internal/plugins/vrrp/packet/validate.go`](https://github.com/ze-software/ze/blob/main/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`](#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`, [`internal/plugins/vrrp/packet/checksum.go`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/checksum.go)) rather than the shape that sentence names. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 33 | one part of the gated population | | Annotated instead of tested | 0 | one part of the gated population | | One polarity only | 7 | one part of the gated population | | No test and no annotation | 15 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **55** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (33):** [`RFC5798-5.1.1.3-2`](#rfc5798-5.1.1.3-2), [`RFC5798-5.1.2.3-2`](#rfc5798-5.1.2.3-2), [`RFC5798-5.2.2-1`](#rfc5798-5.2.2-1), [`RFC5798-5.2.4-1`](#rfc5798-5.2.4-1), [`RFC5798-5.2.4-2`](#rfc5798-5.2.4-2), [`RFC5798-5.2.6-1`](#rfc5798-5.2.6-1), [`RFC5798-5.2.8-1`](#rfc5798-5.2.8-1), [`RFC5798-5.2.9-1`](#rfc5798-5.2.9-1), [`RFC5798-5.2.9-2`](#rfc5798-5.2.9-2), [`RFC5798-6.1-1`](#rfc5798-6.1-1), [`RFC5798-6.4.2-1`](#rfc5798-6.4.2-1), [`RFC5798-6.4.2-2`](#rfc5798-6.4.2-2), [`RFC5798-6.4.2-4`](#rfc5798-6.4.2-4), [`RFC5798-6.4.2-5`](#rfc5798-6.4.2-5), [`RFC5798-6.4.2-6`](#rfc5798-6.4.2-6), [`RFC5798-6.4.2-7`](#rfc5798-6.4.2-7), [`RFC5798-6.4.2-8`](#rfc5798-6.4.2-8), [`RFC5798-6.4.2-9`](#rfc5798-6.4.2-9), [`RFC5798-6.4.2-10`](#rfc5798-6.4.2-10), [`RFC5798-6.4.3-1`](#rfc5798-6.4.3-1), [`RFC5798-6.4.3-5`](#rfc5798-6.4.3-5), [`RFC5798-6.4.3-6`](#rfc5798-6.4.3-6), [`RFC5798-6.4.3-7`](#rfc5798-6.4.3-7), [`RFC5798-6.4.3-8`](#rfc5798-6.4.3-8), [`RFC5798-6.4.3-9`](#rfc5798-6.4.3-9), [`RFC5798-6.4.3-10`](#rfc5798-6.4.3-10), [`RFC5798-6.4.3-11`](#rfc5798-6.4.3-11), [`RFC5798-6.4.3-12`](#rfc5798-6.4.3-12), [`RFC5798-7.1-1`](#rfc5798-7.1-1), [`RFC5798-7.1-2`](#rfc5798-7.1-2), [`RFC5798-7.1-4`](#rfc5798-7.1-4), [`RFC5798-8.1.2-1`](#rfc5798-8.1.2-1), [`RFC5798-8.2.2-4`](#rfc5798-8.2.2-4) **One polarity only (7):** [`RFC5798-5.1.1.3-1`](#rfc5798-5.1.1.3-1), [`RFC5798-6.4.3-2`](#rfc5798-6.4.3-2), [`RFC5798-7.2-1`](#rfc5798-7.2-1), [`RFC5798-7.2-2`](#rfc5798-7.2-2), [`RFC5798-7.2-3`](#rfc5798-7.2-3), [`RFC5798-7.2-4`](#rfc5798-7.2-4), [`RFC5798-8.2.2-1`](#rfc5798-8.2.2-1) **No test and no annotation (15):** [`RFC5798-5.1.1.2-1`](#rfc5798-5.1.1.2-1), [`RFC5798-5.1.2.2-1`](#rfc5798-5.1.2.2-1), [`RFC5798-5.1.2.3-1`](#rfc5798-5.1.2.3-1), [`RFC5798-6.4.2-3`](#rfc5798-6.4.2-3), [`RFC5798-6.4.3-3`](#rfc5798-6.4.3-3), [`RFC5798-6.4.3-4`](#rfc5798-6.4.3-4), [`RFC5798-7.1-3`](#rfc5798-7.1-3), [`RFC5798-7.4-1`](#rfc5798-7.4-1), [`RFC5798-7.4-2`](#rfc5798-7.4-2), [`RFC5798-8.1.3-1`](#rfc5798-8.1.3-1), [`RFC5798-8.2.2-2`](#rfc5798-8.2.2-2), [`RFC5798-8.2.2-3`](#rfc5798-8.2.2-3), [`RFC5798-8.2.3-1`](#rfc5798-8.2.3-1), [`RFC5798-8.4.2-1`](#rfc5798-8.4.2-1), [`RFC5798-A.2-1`](#rfc5798-a.2-1) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5798-5.1.1.2-1` | Never forward a datagram destined to 224.0.0.18, regardless of its TTL (§5.1.1.2) | MUST NOT | 5.1.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-5.1.1.3-1` | Set the IPv4 TTL of transmitted VRRP packets to 255 (§5.1.1.3) | MUST | 5.1.1.3 | **positive:** `unit/verify` [`TestSendAdvertV3IPv4HeaderTTLProtoDst`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L301). **negative:** no negative test | | `RFC5798-5.1.1.3-2` | Discard a received IPv4 VRRP packet whose TTL is not 255 (§5.1.1.3, §7.1) | MUST | 5.1.1.3 | **positive:** `unit/verify` [`TestDecodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L75). **negative:** `unit/verify` [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L535) | | `RFC5798-5.1.2.2-1` | Never forward a datagram destined to FF02:0:0:0:0:0:0:12, regardless of its Hop Limit (§5.1.2.2) | MUST NOT | 5.1.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-5.1.2.3-1` | Set the IPv6 Hop Limit of transmitted VRRP packets to 255 (§5.1.2.3) | MUST | 5.1.2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-5.1.2.3-2` | Discard a received IPv6 VRRP packet whose Hop Limit is not 255 (§5.1.2.3, §7.1) | MUST | 5.1.2.3 | **positive:** `unit/verify` [`TestDecodeGoldenV3IPv6`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L104). **negative:** `unit/verify` [`TestDecodeV3IPv6ChecksumAndHopLimit`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L665) | | `RFC5798-5.2.2-1` | Discard a packet with unknown Type; 1 = ADVERTISEMENT is the only type defined (§5.2.2) | MUST | 5.2.2 | **positive:** `unit/verify` [`TestDecodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L77). **negative:** `unit/verify` [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L152) | | `RFC5798-5.2.4-1` | Use Priority 255 for the VRRP router that owns the IPvX address associated with the virtual router (§5.2.4) | MUST | 5.2.4 | **positive:** `unit/verify` [`TestOwnerAutoDetection`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L774). **negative:** `unit/verify` [`TestOwnerAutoDetection`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L776) | | `RFC5798-5.2.4-2` | Use Priority values 1-254 for VRRP routers backing up a virtual router (§5.2.4) | MUST | 5.2.4 | **positive:** `unit/verify` [`TestBoundaryPriority`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L319). **negative:** `unit/verify` [`TestBoundaryPriority`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L321) | | `RFC5798-5.2.6-1` | Set the rsvd field to zero on transmission and ignore it on reception (§5.2.6) | MUST | 5.2.6 | **positive:** `unit/verify` [`TestEncodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L107). **negative:** `unit/verify` [`TestDecodeV3ReserveIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L639) | | `RFC5798-5.2.8-1` | Compute and verify the checksum as the 16-bit one's complement of the one's complement sum of the entire VRRP message starting with the version field and a pseudo-header defined by RFC 2460, with next header 112 and the checksum field zeroed, for both address families (§5.2.8, §7.1) | MUST | 5.2.8 | **positive:** `unit/verify` [`TestFillChecksumFamilies`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/checksum_test.go#L68). **negative:** `unit/verify` [`TestDecodeV3IPv6ChecksumAndHopLimit`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L663) | | `RFC5798-5.2.9-1` | Send the IPv6 link-local address associated with the virtual router as the first address in the list (§5.2.9, §6.1; lowercase "must" in the RFC) | MUST | 5.2.9 | **positive:** `unit/verify` [`TestValidateIPv6LinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L524). **negative:** `unit/verify` [`TestValidateIPv6LinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L526) | | `RFC5798-5.2.9-2` | Never carry IPv4 and IPv6 addresses together in one IPvX Address field (§5.2.9) | MUST NOT | 5.2.9 | **positive:** `unit/verify` [`TestValidateVIPFamilyMatchesGroupFamily`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L548). **negative:** `unit/verify` [`TestValidateVIPFamilyMatchesGroupFamily`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L550) | | `RFC5798-6.1-1` | Never drop IPv6 Neighbor Solicitations and Neighbor Advertisements when Accept_Mode is False (§6.1, §6.4.3) | MUST NOT | 6.1 | **positive:** `unit/verify` [`TestAcceptFilterAcceptsNeighborDiscoveryBeforeAnyDrop`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L110). **negative:** `unit/verify` [`TestAcceptFilterAcceptsNeighborDiscoveryBeforeAnyDrop`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L112) | | `RFC5798-6.4.2-1` | Backup: never respond to ARP requests for the IPv4 address(es) associated with the virtual router (§6.4.2) | MUST NOT | 6.4.2 | **positive:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L324). **negative:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L369) | | `RFC5798-6.4.2-2` | Backup: never respond to ND Neighbor Solicitation messages for the IPv6 address(es) associated with the virtual router (§6.4.2) | MUST NOT | 6.4.2 | **positive:** `unit/verify` [`TestInstanceIPv6VIPLivesOnVirtualMACDevice`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L648). **negative:** `unit/verify` [`TestInstanceIPv6VIPLivesOnVirtualMACDevice`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L650) | | `RFC5798-6.4.2-3` | Backup: never send ND Router Advertisement messages for the virtual router (§6.4.2) | MUST NOT | 6.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-6.4.2-4` | Backup: discard packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L326). **negative:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L371) | | `RFC5798-6.4.2-5` | Backup: never accept packets addressed to the IPvX address(es) associated with the virtual router (§6.4.2) | MUST NOT | 6.4.2 | **positive:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L328). **negative:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L373) | | `RFC5798-6.4.2-6` | Backup: on a Shutdown event, cancel the Master_Down_Timer and transition to Initialize (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L110). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L112) | | `RFC5798-6.4.2-7` | Backup: when the Master_Down_Timer fires, send an ADVERTISEMENT, broadcast a gratuitous ARP carrying the virtual router MAC for each IPv4 address or, for IPv6, join the Solicited-Node multicast address and send an unsolicited Neighbor Advertisement for each address, set the Adver_Timer to Advertisement_Interval, and transition to Master (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMMasterDownPromotion`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L448). **negative:** `unit/verify` [`TestFSMStaleTimerGenerationIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L639) | | `RFC5798-6.4.2-8` | Backup: on an ADVERTISEMENT with Priority 0, set the Master_Down_Timer to Skew_Time (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L114). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L116) | | `RFC5798-6.4.2-9` | Backup: on a non-zero-priority ADVERTISEMENT, when Preempt_Mode is False or the advertised Priority is greater than or equal to the local Priority, set Master_Adver_Interval to the Adver Interval in the advertisement, recompute Master_Down_Interval, and reset the Master_Down_Timer to it (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L118). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L120) | | `RFC5798-6.4.2-10` | Backup: on a non-zero-priority ADVERTISEMENT with Preempt_Mode True and an advertised Priority lower than the local Priority, discard the ADVERTISEMENT (§6.4.2) | MUST | 6.4.2 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L122). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L124) | | `RFC5798-6.4.3-1` | Master: respond to ARP requests for the IPv4 address(es) associated with the virtual router (§6.4.3, §8.1.2) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestDataplaneApplyIPv4SetsRecipe`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L64). **negative:** `unit/verify` [`TestDataplaneRestoreOnLastGroup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L125) | | `RFC5798-6.4.3-2` | Master: be a member of the Solicited-Node multicast address for the IPv6 address(es) associated with the virtual router (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestInstanceIPv6VIPLivesOnVirtualMACDevice`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L652). **negative:** no negative test | | `RFC5798-6.4.3-3` | Master: respond to ND Neighbor Solicitation messages for the IPv6 address(es) associated with the virtual router (§6.4.3, §8.2.2) | MUST | 6.4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-6.4.3-4` | Master: send ND Router Advertisements for the virtual router (§6.4.3) | MUST | 6.4.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-6.4.3-5` | Master: forward packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L366). **negative:** `unit/verify` [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L330) | | `RFC5798-6.4.3-6` | Master: accept packets addressed to the IPvX address(es) associated with the virtual router when it is the address owner or when Accept_Mode is True (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestActiveNonOwnerWithAcceptModeTrueAcceptsLocalDelivery`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L242). **negative:** `unit/verify` [`TestActiveNonOwnerWithAcceptModeFalseSuppressesLocalDelivery`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L206) | | `RFC5798-6.4.3-7` | Master: never accept those packets when it is neither the address owner nor configured with Accept_Mode True (§6.4.3) | MUST NOT | 6.4.3 | **positive:** `unit/verify` [`TestActiveNonOwnerWithAcceptModeFalseSuppressesLocalDelivery`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L204). **negative:** `unit/verify` [`TestActiveNonOwnerWithAcceptModeTrueAcceptsLocalDelivery`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L244) | | `RFC5798-6.4.3-8` | Master: on a Shutdown event, cancel the Adver_Timer, send an ADVERTISEMENT with Priority = 0, and transition to Initialize (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L126). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L128) | | `RFC5798-6.4.3-9` | Master: when the Adver_Timer fires, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L130). **negative:** `unit/verify` [`TestFSMStaleTimerGenerationIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L641) | | `RFC5798-6.4.3-10` | Master: on an ADVERTISEMENT with Priority 0, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L132). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L134) | | `RFC5798-6.4.3-11` | Master: on an ADVERTISEMENT with a higher Priority, or an equal Priority with a greater sender primary IPvX address, cancel the Adver_Timer, set Master_Adver_Interval to the advertised Adver Interval, recompute Skew_Time and Master_Down_Interval, set the Master_Down_Timer, and transition to Backup (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L136). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L138) | | `RFC5798-6.4.3-12` | Master: on a losing ADVERTISEMENT, one with a lower Priority or an equal Priority with a smaller sender address, discard it (§6.4.3) | MUST | 6.4.3 | **positive:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L140). **negative:** `unit/verify` [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L142) | | `RFC5798-7.1-1` | Rx: verify that the VRRP version is 3 (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestDecodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L80). **negative:** `unit/verify` [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L611) | | `RFC5798-7.1-2` | Rx: verify that the received packet contains the complete VRRP packet, the fixed fields and the IPvX address list (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestDecodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L83). **negative:** `unit/verify` [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L511) | | `RFC5798-7.1-3` | Rx: verify that the VRID is configured on the receiving interface and that the local router is not the IPvX address owner (§7.1) | MUST | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-7.1-4` | Rx: discard the packet when any mandatory receive check fails (§7.1) | MUST | 7.1 | **positive:** `unit/verify` [`TestInstanceRxValidAdvertReachesFSM`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L485). **negative:** `unit/verify` [`TestInstanceRxDecodeErrorMapsReason`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L465) | | `RFC5798-7.2-1` | Tx: fill in the VRRP packet fields from the virtual router configuration state and compute the VRRP checksum (§7.2) | MUST | 7.2 | **positive:** `unit/verify` [`TestEncodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L109). **negative:** no negative test | | `RFC5798-7.2-2` | Tx: set the source MAC address to the virtual router MAC address (§7.2) | MUST | 7.2 | **positive:** `unit/verify` [`TestConstants`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L387). **negative:** no negative test | | `RFC5798-7.2-3` | Tx: set the source IPv4 address to the interface primary IPv4 address, or the source IPv6 address to the interface link-local IPv6 address (§7.2) | MUST | 7.2 | **positive:** `unit/verify` [`TestSendAdvertUsesParentPrimaryV4Source`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L204). **negative:** no negative test | | `RFC5798-7.2-4` | Tx: set the IPvX protocol to VRRP and send the packet to the VRRP IPvX multicast group (§7.2) | MUST | 7.2 | **positive:** `unit/verify` [`TestSendAdvertV3IPv4HeaderTTLProtoDst`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L303). **negative:** no negative test | | `RFC5798-7.4-1` | Create the Interface Identifiers of an IPv6 router running VRRP in the normal manner, as in Transmission of IPv6 Packets over Ethernet Networks (§7.4) | MUST | 7.4 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-7.4-2` | Never use the virtual router MAC address to create the Modified Extended Unique Identifier (EUI)-64 identifiers (§7.4) | MUST NOT | 7.4 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.1.2-1` | Master: never respond to a host ARP request for a virtual router IPv4 address with its physical MAC address (§8.1.2) | MUST NOT | 8.1.2 | **positive:** `unit/verify` [`TestDataplaneApplyIPv4SetsRecipe`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L66). **negative:** `unit/verify` [`TestDataplaneRestoreOnLastGroup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L127) | | `RFC5798-8.1.3-1` | Advertise the virtual router MAC address in the Proxy ARP message when Proxy ARP is used on a VRRP router (§8.1.3; lowercase "must" in the RFC) | MUST | 8.1.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.2.2-1` | Master: never respond to an ND Neighbor Solicitation for a virtual router IPv6 address with its physical MAC address (§8.2.2) | MUST NOT | 8.2.2 | **positive:** `unit/verify` [`TestInstanceIPv6VIPLivesOnVirtualMACDevice`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L654). **negative:** no negative test | | `RFC5798-8.2.2-2` | Master: include the virtual router MAC address in the source link-layer address option of a Neighbor Solicitation it sends for a host's IPv6 address, when it sends that option (§8.2.2) | MUST | 8.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.2.2-3` | Master: never use its physical MAC address in that source link-layer address option (§8.2.2) | MUST NOT | 8.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.2.2-4` | At system boot, delay every ND Router Advertisement, Neighbor Advertisement and Neighbor Solicitation until both the IPv6 address and the virtual router MAC address are configured (§8.2.2; lowercase "must" in the RFC) | MUST | 8.2.2 | **positive:** `unit/verify` [`TestInstanceDelaysAnnounceUntilParentUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L697). **negative:** `unit/verify` [`TestInstanceDelaysAnnounceUntilParentUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L699) | | `RFC5798-8.2.3-1` | Configure Backup routers to send the same Router Advertisement options as the address owner (§8.2.3; lowercase "must" in the RFC) | MUST | 8.2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.4.2-1` | Interop mode, Master: send both VRRPv2 and VRRPv3 advertisements at the configured rate, even when it is sub-second (§8.4.2) | MUST | 8.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-A.2-1` | Token Ring: implement the functional-address mode of operation when supporting VRRP on Token Ring (§A.2) | MUST | A.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-7.1-5` | Log the event when a mandatory receive check fails (§7.1) | SHOULD | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-7.1-8` | Log the event when the optional address-list check fails (§7.1) | SHOULD | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.1.1-1` | Set the IPv4 source address of an ICMP redirect to the address the end-host used when making its next-hop routing decision (§8.1.1; lowercase "should" in the RFC) | SHOULD | 8.1.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.1.2-2` | After a restart or boot, send no ARP message using the physical MAC address for an owned virtual IPv4 address (§8.1.2) | SHOULD NOT | 8.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.1.2-3` | When configuring an interface, broadcast a gratuitous ARP request carrying the virtual router MAC address for each IPv4 address on that interface (§8.1.2; lowercase "should" in the RFC) | SHOULD | 8.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.1.2-4` | At system boot, delay gratuitous ARP requests and ARP responses until both the IPv4 address and the virtual router MAC address are configured (§8.1.2) | SHOULD | 8.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.1.2-5` | Use an IP address known to belong to a particular router when direct access to that router is required, for example ssh (§8.1.2; lowercase "must" inside a SHOULD-level bullet list) | SHOULD | 8.1.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.2.1-1` | Set the IPv6 source address of an ICMPv6 redirect to the address the end-host used when making its next-hop routing decision (§8.2.1; lowercase "should" in the RFC) | SHOULD | 8.2.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.2.2-5` | After a restart or boot, send no ND message using the physical MAC address for an owned virtual IPv6 address (§8.2.2) | SHOULD NOT | 8.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.2.2-6` | When configuring an interface, send an unsolicited ND Neighbor Advertisement carrying the virtual router MAC address for the IPv6 address on that interface (§8.2.2; lowercase "should" in the RFC) | SHOULD | 8.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.2.3-2` | Send Router Advertisement options that advertise special services from the address owner unless the Backup routers can assume those services in full with a complete and synchronized database (§8.2.3; lowercase "should not" in the RFC) | SHOULD NOT | 8.2.3 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.3.1-1` | Never forward packets addressed to the IPvX address a router becomes Master for when it is not the address owner (§8.3.1) | SHOULD NOT | 8.3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.3.2-1` | Configure no more than one VRRP router on the link with priority 255 for a single VRID (§8.3.2; lowercase in the RFC) | SHOULD | 8.3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.3.2-2` | Distribute the priority values of multiple Backup routers uniformly to speed convergence (§8.3.2; lowercase in the RFC) | SHOULD | 8.3.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.4.2-2` | Do not run VRRPv2 and VRRPv3 mixed operation as a permanent deployment; it is an upgrade path (§8.4.2) | NOT RECOMMENDED | 8.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.4.2-3` | Interop mode, Backup: time out from the rate the Master advertises, translating a VRRPv2 Master's seconds into centiseconds (§8.4.2) | SHOULD | 8.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.4.2-4` | Interop mode, Backup: ignore VRRPv2 advertisements from the current Master when VRRPv3 packets are also being received from it (§8.4.2) | SHOULD | 8.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.4.3.1-1` | Never give a VRRPv2 implementation a higher priority than the VRRPv2/VRRPv3 implementation it interacts with when that peer advertises at a sub-second rate (§8.4.3.1) | SHOULD NOT | 8.4.3.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-A.1-1` | FDDI: configure the virtual router MAC address by adding a unicast MAC filter in the FDDI device rather than changing its hardware MAC address (§A.1) | SHOULD | A.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-7.1-6` | Indicate through network management that a receive error, or a detected misconfiguration, occurred (§7.1) | MAY | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-7.1-7` | Verify that "Count IPvX Addrs" and the list of IPvX addresses match the addresses configured for the VRID (§7.1) | MAY | 7.1 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.2.2-7` | Answer Duplicate Address Detection for an owned address from the Backup router while the Master restarts; one solution is not to run DAD in that case (§8.2.2) | MAY | 8.2.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.4.2-5` | Implement a configuration flag that tells the router to listen for and send both VRRPv2 and VRRPv3 advertisements (§8.4.2) | MAY | 8.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-8.4.2-6` | Report when a VRRPv3 Master is not sending VRRPv2 packets while interop mode is configured (§8.4.2) | MAY | 8.4.2 | **positive:** no positive test. **negative:** no negative test | | `RFC5798-A.2-2` | Token Ring: support the unicast mode of operation beside the functional-address mode (§A.2) | MAY | A.2 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5798-5.1.1.2-1`](#rfc5798-5.1.1.2-1) Never forward a datagram destined to 224.0.0.18, regardless of its TTL (§5.1.1.2) | no test | no test carries this requirement id | | [`RFC5798-5.1.2.2-1`](#rfc5798-5.1.2.2-1) Never forward a datagram destined to FF02:0:0:0:0:0:0:12, regardless of its Hop Limit (§5.1.2.2) | no test | no test carries this requirement id | | [`RFC5798-5.1.2.3-1`](#rfc5798-5.1.2.3-1) Set the IPv6 Hop Limit of transmitted VRRP packets to 255 (§5.1.2.3) | no test | no test carries this requirement id | | [`RFC5798-6.4.2-3`](#rfc5798-6.4.2-3) Backup: never send ND Router Advertisement messages for the virtual router (§6.4.2) | no test | no test carries this requirement id | | [`RFC5798-6.4.3-3`](#rfc5798-6.4.3-3) Master: respond to ND Neighbor Solicitation messages for the IPv6 address(es) associated with the virtual router (§6.4.3, §8.2.2) | no test | no test carries this requirement id | | [`RFC5798-6.4.3-4`](#rfc5798-6.4.3-4) Master: send ND Router Advertisements for the virtual router (§6.4.3) | no test | no test carries this requirement id | | [`RFC5798-7.1-3`](#rfc5798-7.1-3) Rx: verify that the VRID is configured on the receiving interface and that the local router is not the IPvX address owner (§7.1) | no test | no test carries this requirement id | | [`RFC5798-7.4-1`](#rfc5798-7.4-1) Create the Interface Identifiers of an IPv6 router running VRRP in the normal manner, as in Transmission of IPv6 Packets over Ethernet Networks (§7.4) | no test | no test carries this requirement id | | [`RFC5798-7.4-2`](#rfc5798-7.4-2) Never use the virtual router MAC address to create the Modified Extended Unique Identifier (EUI)-64 identifiers (§7.4) | no test | no test carries this requirement id | | [`RFC5798-8.1.3-1`](#rfc5798-8.1.3-1) Advertise the virtual router MAC address in the Proxy ARP message when Proxy ARP is used on a VRRP router (§8.1.3; lowercase "must" in the RFC) | no test | no test carries this requirement id | | [`RFC5798-8.2.2-2`](#rfc5798-8.2.2-2) Master: include the virtual router MAC address in the source link-layer address option of a Neighbor Solicitation it sends for a host's IPv6 address, when it sends that option (§8.2.2) | no test | no test carries this requirement id | | [`RFC5798-8.2.2-3`](#rfc5798-8.2.2-3) Master: never use its physical MAC address in that source link-layer address option (§8.2.2) | no test | no test carries this requirement id | | [`RFC5798-8.2.3-1`](#rfc5798-8.2.3-1) Configure Backup routers to send the same Router Advertisement options as the address owner (§8.2.3; lowercase "must" in the RFC) | no test | no test carries this requirement id | | [`RFC5798-8.4.2-1`](#rfc5798-8.4.2-1) Interop mode, Master: send both VRRPv2 and VRRPv3 advertisements at the configured rate, even when it is sub-second (§8.4.2) | no test | no test carries this requirement id | | [`RFC5798-A.2-1`](#rfc5798-a.2-1) Token Ring: implement the functional-address mode of operation when supporting VRRP on Token Ring (§A.2) | no test | no test carries this requirement id | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5798-5.1.1.2-1`](#rfc5798-5.1.1.2-1) Never forward a datagram destined to 224.0.0.18, regardless of its TTL (§5.1.1.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-5.1.1.2-1, so no unit is bound to it. ### [`RFC5798-5.1.1.3-1`](#rfc5798-5.1.1.3-1) Set the IPv4 TTL of transmitted VRRP packets to 255 (§5.1.1.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSendAdvertV3IPv4HeaderTTLProtoDst`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L301) | unit/verify | revert, verified | ### [`RFC5798-5.1.1.3-2`](#rfc5798-5.1.1.3-2) Discard a received IPv4 VRRP packet whose TTL is not 255 (§5.1.1.3, §7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L535) | unit/verify | revert, verified | | positive | [`TestDecodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L75) | unit/verify | revert, verified | ### [`RFC5798-5.1.2.2-1`](#rfc5798-5.1.2.2-1) Never forward a datagram destined to FF02:0:0:0:0:0:0:12, regardless of its Hop Limit (§5.1.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-5.1.2.2-1, so no unit is bound to it. ### [`RFC5798-5.1.2.3-1`](#rfc5798-5.1.2.3-1) Set the IPv6 Hop Limit of transmitted VRRP packets to 255 (§5.1.2.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-5.1.2.3-1, so no unit is bound to it. ### [`RFC5798-5.1.2.3-2`](#rfc5798-5.1.2.3-2) Discard a received IPv6 VRRP packet whose Hop Limit is not 255 (§5.1.2.3, §7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDecodeV3IPv6ChecksumAndHopLimit`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L665) | unit/verify | revert, verified | | positive | [`TestDecodeGoldenV3IPv6`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L104) | unit/verify | revert, verified | ### [`RFC5798-5.2.2-1`](#rfc5798-5.2.2-1) Discard a packet with unknown Type; 1 = ADVERTISEMENT is the only type defined (§5.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestValidationOrder`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L152) | unit/verify | revert, verified | | positive | [`TestDecodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L77) | unit/verify | revert, verified | ### [`RFC5798-5.2.4-1`](#rfc5798-5.2.4-1) Use Priority 255 for the VRRP router that owns the IPvX address associated with the virtual router (§5.2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestOwnerAutoDetection`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L776) | unit/verify | revert, verified | | positive | [`TestOwnerAutoDetection`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L774) | unit/verify | revert, verified | ### [`RFC5798-5.2.4-2`](#rfc5798-5.2.4-2) Use Priority values 1-254 for VRRP routers backing up a virtual router (§5.2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestBoundaryPriority`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L321) | unit/verify | revert, verified | | positive | [`TestBoundaryPriority`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L319) | unit/verify | revert, verified | ### [`RFC5798-5.2.6-1`](#rfc5798-5.2.6-1) Set the rsvd field to zero on transmission and ignore it on reception (§5.2.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDecodeV3ReserveIgnoredOnReceive`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L639) | unit/verify | revert, verified | | positive | [`TestEncodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L107) | unit/verify | revert, verified | ### [`RFC5798-5.2.8-1`](#rfc5798-5.2.8-1) Compute and verify the checksum as the 16-bit one's complement of the one's complement sum of the entire VRRP message starting with the version field and a pseudo-header defined by RFC 2460, with next header 112 and the checksum field zeroed, for both address families (§5.2.8, §7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDecodeV3IPv6ChecksumAndHopLimit`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L663) | unit/verify | revert, verified | | positive | [`TestFillChecksumFamilies`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/checksum_test.go#L68) | unit/verify | revert, verified | ### [`RFC5798-5.2.9-1`](#rfc5798-5.2.9-1) Send the IPv6 link-local address associated with the virtual router as the first address in the list (§5.2.9, §6.1; lowercase "must" in the RFC) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestValidateIPv6LinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L526) | unit/verify | revert, verified | | positive | [`TestValidateIPv6LinkLocal`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L524) | unit/verify | revert, verified | ### [`RFC5798-5.2.9-2`](#rfc5798-5.2.9-2) Never carry IPv4 and IPv6 addresses together in one IPvX Address field (§5.2.9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestValidateVIPFamilyMatchesGroupFamily`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L550) | unit/verify | revert, verified | | positive | [`TestValidateVIPFamilyMatchesGroupFamily`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/groups_test.go#L548) | unit/verify | revert, verified | ### [`RFC5798-6.1-1`](#rfc5798-6.1-1) Never drop IPv6 Neighbor Solicitations and Neighbor Advertisements when Accept_Mode is False (§6.1, §6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAcceptFilterAcceptsNeighborDiscoveryBeforeAnyDrop`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L112) | unit/verify | revert, verified | | positive | [`TestAcceptFilterAcceptsNeighborDiscoveryBeforeAnyDrop`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L110) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-1`](#rfc5798-6.4.2-1) Backup: never respond to ARP requests for the IPv4 address(es) associated with the virtual router (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L369) | unit/verify | revert, verified | | positive | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L324) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-2`](#rfc5798-6.4.2-2) Backup: never respond to ND Neighbor Solicitation messages for the IPv6 address(es) associated with the virtual router (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceIPv6VIPLivesOnVirtualMACDevice`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L650) | unit/verify | revert, verified | | positive | [`TestInstanceIPv6VIPLivesOnVirtualMACDevice`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L648) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-3`](#rfc5798-6.4.2-3) Backup: never send ND Router Advertisement messages for the virtual router (§6.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-6.4.2-3, so no unit is bound to it. ### [`RFC5798-6.4.2-4`](#rfc5798-6.4.2-4) Backup: discard packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L371) | unit/verify | revert, verified | | positive | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L326) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-5`](#rfc5798-6.4.2-5) Backup: never accept packets addressed to the IPvX address(es) associated with the virtual router (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L373) | unit/verify | revert, verified | | positive | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L328) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-6`](#rfc5798-6.4.2-6) Backup: on a Shutdown event, cancel the Master_Down_Timer and transition to Initialize (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L112) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L110) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-7`](#rfc5798-6.4.2-7) Backup: when the Master_Down_Timer fires, send an ADVERTISEMENT, broadcast a gratuitous ARP carrying the virtual router MAC for each IPv4 address or, for IPv6, join the Solicited-Node multicast address and send an unsolicited Neighbor Advertisement for each address, set the Adver_Timer to Advertisement_Interval, and transition to Master (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMStaleTimerGenerationIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L639) | unit/verify | revert, verified | | positive | [`TestFSMMasterDownPromotion`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L448) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-8`](#rfc5798-6.4.2-8) Backup: on an ADVERTISEMENT with Priority 0, set the Master_Down_Timer to Skew_Time (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L116) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L114) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-9`](#rfc5798-6.4.2-9) Backup: on a non-zero-priority ADVERTISEMENT, when Preempt_Mode is False or the advertised Priority is greater than or equal to the local Priority, set Master_Adver_Interval to the Adver Interval in the advertisement, recompute Master_Down_Interval, and reset the Master_Down_Timer to it (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L120) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L118) | unit/verify | revert, verified | ### [`RFC5798-6.4.2-10`](#rfc5798-6.4.2-10) Backup: on a non-zero-priority ADVERTISEMENT with Preempt_Mode True and an advertised Priority lower than the local Priority, discard the ADVERTISEMENT (§6.4.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L124) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L122) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-1`](#rfc5798-6.4.3-1) Master: respond to ARP requests for the IPv4 address(es) associated with the virtual router (§6.4.3, §8.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDataplaneRestoreOnLastGroup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L125) | unit/verify | revert, verified | | positive | [`TestDataplaneApplyIPv4SetsRecipe`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L64) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-2`](#rfc5798-6.4.3-2) Master: be a member of the Solicited-Node multicast address for the IPv6 address(es) associated with the virtual router (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInstanceIPv6VIPLivesOnVirtualMACDevice`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L652) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-3`](#rfc5798-6.4.3-3) Master: respond to ND Neighbor Solicitation messages for the IPv6 address(es) associated with the virtual router (§6.4.3, §8.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-6.4.3-3, so no unit is bound to it. ### [`RFC5798-6.4.3-4`](#rfc5798-6.4.3-4) Master: send ND Router Advertisements for the virtual router (§6.4.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-6.4.3-4, so no unit is bound to it. ### [`RFC5798-6.4.3-5`](#rfc5798-6.4.3-5) Master: forward packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceStartupNonOwnerGoesBackup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L330) | unit/verify | revert, verified | | positive | [`TestInstanceOwnerStartupGoesMaster`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L366) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-6`](#rfc5798-6.4.3-6) Master: accept packets addressed to the IPvX address(es) associated with the virtual router when it is the address owner or when Accept_Mode is True (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestActiveNonOwnerWithAcceptModeFalseSuppressesLocalDelivery`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L206) | unit/verify | revert, verified | | positive | [`TestActiveNonOwnerWithAcceptModeTrueAcceptsLocalDelivery`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L242) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-7`](#rfc5798-6.4.3-7) Master: never accept those packets when it is neither the address owner nor configured with Accept_Mode True (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestActiveNonOwnerWithAcceptModeTrueAcceptsLocalDelivery`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L244) | unit/verify | revert, verified | | positive | [`TestActiveNonOwnerWithAcceptModeFalseSuppressesLocalDelivery`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/acceptfilter_test.go#L204) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-8`](#rfc5798-6.4.3-8) Master: on a Shutdown event, cancel the Adver_Timer, send an ADVERTISEMENT with Priority = 0, and transition to Initialize (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L128) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L126) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-9`](#rfc5798-6.4.3-9) Master: when the Adver_Timer fires, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMStaleTimerGenerationIgnored`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L641) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L130) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-10`](#rfc5798-6.4.3-10) Master: on an ADVERTISEMENT with Priority 0, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L134) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L132) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-11`](#rfc5798-6.4.3-11) Master: on an ADVERTISEMENT with a higher Priority, or an equal Priority with a greater sender primary IPvX address, cancel the Adver_Timer, set Master_Adver_Interval to the advertised Adver Interval, recompute Skew_Time and Master_Down_Interval, set the Master_Down_Timer, and transition to Backup (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L138) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L136) | unit/verify | revert, verified | ### [`RFC5798-6.4.3-12`](#rfc5798-6.4.3-12) Master: on a losing ADVERTISEMENT, one with a lower Priority or an equal Priority with a smaller sender address, discard it (§6.4.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L142) | unit/verify | revert, verified | | positive | [`TestFSMTransitionMatrix`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/fsm/fsm_test.go#L140) | unit/verify | revert, verified | ### [`RFC5798-7.1-1`](#rfc5798-7.1-1) Rx: verify that the VRRP version is 3 (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L611) | unit/verify | revert, verified | | positive | [`TestDecodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L80) | unit/verify | revert, verified | ### [`RFC5798-7.1-2`](#rfc5798-7.1-2) Rx: verify that the received packet contains the complete VRRP packet, the fixed fields and the IPvX address list (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestNegativeReferenceBugs`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L511) | unit/verify | revert, verified | | positive | [`TestDecodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/validate_test.go#L83) | unit/verify | revert, verified | ### [`RFC5798-7.1-3`](#rfc5798-7.1-3) Rx: verify that the VRID is configured on the receiving interface and that the local router is not the IPvX address owner (§7.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-7.1-3, so no unit is bound to it. ### [`RFC5798-7.1-4`](#rfc5798-7.1-4) Rx: discard the packet when any mandatory receive check fails (§7.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceRxDecodeErrorMapsReason`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L465) | unit/verify | revert, verified | | positive | [`TestInstanceRxValidAdvertReachesFSM`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L485) | unit/verify | revert, verified | ### [`RFC5798-7.2-1`](#rfc5798-7.2-1) Tx: fill in the VRRP packet fields from the virtual router configuration state and compute the VRRP checksum (§7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestEncodeGoldenV3IPv4`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L109) | unit/verify | revert, verified | ### [`RFC5798-7.2-2`](#rfc5798-7.2-2) Tx: set the source MAC address to the virtual router MAC address (§7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestConstants`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/packet/packet_test.go#L387) | unit/verify | revert, verified | ### [`RFC5798-7.2-3`](#rfc5798-7.2-3) Tx: set the source IPv4 address to the interface primary IPv4 address, or the source IPv6 address to the interface link-local IPv6 address (§7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSendAdvertUsesParentPrimaryV4Source`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L204) | unit/verify | revert, verified | ### [`RFC5798-7.2-4`](#rfc5798-7.2-4) Tx: set the IPvX protocol to VRRP and send the packet to the VRRP IPvX multicast group (§7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestSendAdvertV3IPv4HeaderTTLProtoDst`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/transport/transport_test.go#L303) | unit/verify | revert, verified | ### [`RFC5798-7.4-1`](#rfc5798-7.4-1) Create the Interface Identifiers of an IPv6 router running VRRP in the normal manner, as in Transmission of IPv6 Packets over Ethernet Networks (§7.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-7.4-1, so no unit is bound to it. ### [`RFC5798-7.4-2`](#rfc5798-7.4-2) Never use the virtual router MAC address to create the Modified Extended Unique Identifier (EUI)-64 identifiers (§7.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-7.4-2, so no unit is bound to it. ### [`RFC5798-8.1.2-1`](#rfc5798-8.1.2-1) Master: never respond to a host ARP request for a virtual router IPv4 address with its physical MAC address (§8.1.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestDataplaneRestoreOnLastGroup`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L127) | unit/verify | revert, verified | | positive | [`TestDataplaneApplyIPv4SetsRecipe`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/dataplane_linux_test.go#L66) | unit/verify | revert, verified | ### [`RFC5798-8.1.3-1`](#rfc5798-8.1.3-1) Advertise the virtual router MAC address in the Proxy ARP message when Proxy ARP is used on a VRRP router (§8.1.3; lowercase "must" in the RFC) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-8.1.3-1, so no unit is bound to it. ### [`RFC5798-8.2.2-1`](#rfc5798-8.2.2-1) Master: never respond to an ND Neighbor Solicitation for a virtual router IPv6 address with its physical MAC address (§8.2.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestInstanceIPv6VIPLivesOnVirtualMACDevice`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L654) | unit/verify | revert, verified | ### [`RFC5798-8.2.2-2`](#rfc5798-8.2.2-2) Master: include the virtual router MAC address in the source link-layer address option of a Neighbor Solicitation it sends for a host's IPv6 address, when it sends that option (§8.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-8.2.2-2, so no unit is bound to it. ### [`RFC5798-8.2.2-3`](#rfc5798-8.2.2-3) Master: never use its physical MAC address in that source link-layer address option (§8.2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-8.2.2-3, so no unit is bound to it. ### [`RFC5798-8.2.2-4`](#rfc5798-8.2.2-4) At system boot, delay every ND Router Advertisement, Neighbor Advertisement and Neighbor Solicitation until both the IPv6 address and the virtual router MAC address are configured (§8.2.2; lowercase "must" in the RFC) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestInstanceDelaysAnnounceUntilParentUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L699) | unit/verify | revert, verified | | positive | [`TestInstanceDelaysAnnounceUntilParentUsable`](https://github.com/ze-software/ze/blob/main/internal/plugins/vrrp/instance_test.go#L697) | unit/verify | revert, verified | ### [`RFC5798-8.2.3-1`](#rfc5798-8.2.3-1) Configure Backup routers to send the same Router Advertisement options as the address owner (§8.2.3; lowercase "must" in the RFC) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-8.2.3-1, so no unit is bound to it. ### [`RFC5798-8.4.2-1`](#rfc5798-8.4.2-1) Interop mode, Master: send both VRRPv2 and VRRPv3 advertisements at the configured rate, even when it is sub-second (§8.4.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-8.4.2-1, so no unit is bound to it. ### [`RFC5798-A.2-1`](#rfc5798-a.2-1) Token Ring: implement the functional-address mode of operation when supporting VRRP on Token Ring (§A.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5798-A.2-1, so no unit is bound to it. ## Extraction sign-off | Field | Value | |---|---| | Reviewer | claude-opus-5 (rfcgate-6 phase, rfc5798 walk) | | Signed off | 2026-09-01 | | Register | prose | | Source | rfc/full/rfc5798.txt | | Source fingerprint | e28505fbe523b3a8 | | Record | rfc/extraction/rfc5798.json | | Mapped sentences | 49 | | Declined as scope | 13 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 2 | skipped (front-matter) | Title block, abstract, status of this memo, copyright notice and table of contents. No obligation is stated before Section 1. | | `1` | not stated | 0 | walked | not stated | | `1.1` | not stated | 0 | walked | not stated | | `1.2` | not stated | 0 | walked | not stated | | `1.3` | not stated | 0 | walked | not stated | | `1.4` | not stated | 0 | walked | not stated | | `1.5` | not stated | 0 | walked | not stated | | `1.6` | not stated | 0 | walked | not stated | | `2` | not stated | 1 | walked | not stated | | `2.1` | not stated | 0 | walked | not stated | | `2.2` | not stated | 0 | walked | not stated | | `2.3` | not stated | 0 | walked | not stated | | `2.4` | not stated | 0 | walked | not stated | | `2.5` | not stated | 0 | walked | not stated | | `3` | not stated | 1 | walked | not stated | | `4` | not stated | 0 | walked | not stated | | `4.1` | not stated | 1 | walked | not stated | | `4.2` | not stated | 0 | walked | not stated | | `5` | not stated | 0 | walked | not stated | | `5.1` | not stated | 0 | walked | not stated | | `5.1.1` | not stated | 0 | walked | not stated | | `5.1.1.1` | not stated | 0 | walked | not stated | | `5.1.1.2` | not stated | 1 | walked | not stated | | `5.1.1.3` | not stated | 2 | walked | not stated | | `5.1.1.4` | not stated | 0 | walked | not stated | | `5.1.2` | not stated | 0 | walked | not stated | | `5.1.2.1` | not stated | 0 | walked | not stated | | `5.1.2.2` | not stated | 1 | walked | not stated | | `5.1.2.3` | not stated | 2 | walked | not stated | | `5.1.2.4` | not stated | 0 | walked | not stated | | `5.2` | not stated | 0 | walked | not stated | | `5.2.1` | not stated | 0 | walked | not stated | | `5.2.2` | not stated | 1 | walked | not stated | | `5.2.3` | not stated | 0 | walked | not stated | | `5.2.4` | not stated | 2 | walked | not stated | | `5.2.5` | not stated | 0 | walked | not stated | | `5.2.6` | not stated | 1 | walked | not stated | | `5.2.7` | not stated | 0 | walked | not stated | | `5.2.8` | not stated | 0 | walked | not stated | | `5.2.9` | not stated | 2 | walked | not stated | | `6` | not stated | 0 | walked | not stated | | `6.1` | not stated | 2 | walked | not stated | | `6.2` | not stated | 0 | walked | not stated | | `6.3` | not stated | 0 | walked | not stated | | `6.4` | not stated | 0 | walked | not stated | | `6.4.1` | not stated | 0 | walked | not stated | | `6.4.2` | not stated | 6 | walked | not stated | | `6.4.3` | not stated | 9 | walked | not stated | | `7` | not stated | 0 | walked | not stated | | `7.1` | not stated | 7 | walked | not stated | | `7.2` | not stated | 1 | walked | not stated | | `7.3` | not stated | 0 | walked | not stated | | `7.4` | not stated | 2 | walked | not stated | | `8` | not stated | 0 | walked | not stated | | `8.1` | not stated | 0 | walked | not stated | | `8.1.1` | not stated | 1 | walked | not stated | | `8.1.2` | not stated | 3 | walked | not stated | | `8.1.3` | not stated | 1 | walked | not stated | | `8.2` | not stated | 0 | walked | not stated | | `8.2.1` | not stated | 1 | walked | not stated | | `8.2.2` | not stated | 5 | walked | not stated | | `8.2.3` | not stated | 1 | walked | not stated | | `8.3` | not stated | 0 | walked | not stated | | `8.3.1` | not stated | 0 | walked | not stated | | `8.3.2` | not stated | 1 | walked | not stated | | `8.4` | not stated | 0 | walked | not stated | | `8.4.1` | not stated | 0 | walked | not stated | | `8.4.2` | not stated | 2 | walked | not stated | | `8.4.3` | not stated | 0 | walked | not stated | | `8.4.3.1` | not stated | 0 | walked | not stated | | `8.4.3.2` | not stated | 0 | walked | not stated | | `9` | not stated | 1 | walked | not stated | | `10` | Section 10, Contributors and Acknowledgments | 0 | skipped (acknowledgements) | Section 10, Contributors and Acknowledgments. It names the people who wrote the merged source documents. | | `11` | Section 11, IANA Considerations | 0 | skipped (iana) | Section 11, IANA Considerations. It records the IPv4 and IPv6 multicast assignments and the protocol number already made for VRRP, and binds IANA rather than a speaker. | | `12` | Section 12, References, its heading only | 0 | skipped (references) | Section 12, References, its heading only. | | `12.1` | Normative reference list | 0 | skipped (references) | Normative reference list. | | `12.2` | Informative reference list | 0 | skipped (references) | Informative reference list. | | `A` | not stated | 0 | walked | not stated | | `A.1` | not stated | 0 | walked | not stated | | `A.2` | not stated | 2 | walked | not stated | | `A.3` | not stated | 0 | walked | not stated | ### Excluded sentences | Site | Excluded kind | Reason | Quote | |---|---|---|---| | `front:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The sentence is the IETF Trust Legal Provisions boilerplate that opens every RFC. It binds a party redistributing the document text, states no protocol behaviour, and the extractor did not strip it. | Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License. | | `front:2` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The site is a Table of Contents line, "Required Features ... 8 2.1.", captured by the prose scan because the heading it names contains the word Required. A contents line states nothing. | Required Features ...............................................8 2.1. | | `2:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The site is the section heading "Required Features" itself, matched on the word Required. A heading states no obligation; every feature it introduces is stated in Sections 2.1 to 2.5 and, normatively, in Sections 5 to 8. | Required Features | | `3:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Section 3 is the protocol overview and the sentence states a deployment precondition, that an operator gives every VRRP router on the LAN the same VRID-to-address mapping. It is not a behaviour a speaker performs on a packet: no VRRP message carries or negotiates the mapping, and ze reads its own VRID and virtual addresses from operator configuration (applyGroupLeaves, internal/plugins/vrrp/groups.go, reading the vrid and virtual-address leaves of ze-vrrp-conf.yang). No normative section of RFC 5798 restates it. | The mapping between the VRID and its IPvX address(es) must be coordinated among all VRRP routers on a LAN. | | `4.1:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Section 4.1 is a worked sample configuration. The sentence describes what that example needs, "In order to back up IPvX B, a second virtual router must be configured", and states no obligation on an implementation. | In order to back up IPvX B, a second virtual router must be configured. | | `6.1:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The IPv6_Addresses parameter description restates the address-ordering rule of Section 5.2.9, which site 5.2.9:1 maps. | The first address must be the Link-Local address associated with the virtual router. | | `6.4.2:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The sentence is the head of the Backup state list, "(300) While in this state, a VRRP router MUST do the following:". It states no behaviour of its own: it sets the obligation level for the bullets under it, each of which repeats MUST or MUST NOT and is a site of its own at 6.4.2:2 to 6.4.2:6. The steps it also binds that carry no keyword, (345) to (475), are declared in this section unsourced-ids. | (300) While in this state, a VRRP router MUST do the following: | | `6.4.3:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | The sentence is the head of the Master state list, "(600) While in this state, a VRRP router MUST do the following:", and states no behaviour of its own, exactly as 6.4.2:1 does for Backup. Its keyworded bullets are sites 6.4.3:2 to 6.4.3:9, and the steps it binds that carry no keyword, (655) to (780), are declared in this section unsourced-ids. | (600) While in this state, a VRRP router MUST do the following: | | `6.4.3:6` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Step (635) restates the Accept_Mode note of Section 6.1, which site 6.1:2 maps. | (635) ++ If Accept_Mode is False: MUST NOT drop IPv6 Neighbor Solicitations and Neighbor Advertisements. | | `8.1.2:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Section 8.1.2 restates the Master ARP obligation of Section 6.4.3 step (610), which site 6.4.3:2 maps. The half that is NEW here, the prohibition on answering with the physical MAC, is the separate site 8.1.2:2. | When a host sends an ARP request for one of the virtual router IPv4 addresses, the Virtual Router Master MUST respond to the ARP request with an ARP response that indicates the virtual MAC address for the virtual router. | | `8.2.2:1` | `duplicate-of` (never bound Ze): the same obligation is already captured under another requirement id | Section 8.2.2 restates the Master Neighbor Solicitation obligation of Section 6.4.3 step (625), which site 6.4.3:4 maps. The half that is NEW here, the prohibition on answering with the physical MAC, is the separate site 8.2.2:2. | When a host sends an ND Neighbor Solicitation message for the virtual router IPv6 address, the Virtual Router Master MUST respond to the ND Neighbor Solicitation message with the virtual MAC address for the virtual router. | | `9:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | Section 9 states that VRRP needs no confidentiality: "there is no information in the VRRP messages that must be kept secret from other nodes on the LAN". The word sits inside a negative existential describing the absence of a need, not an obligation on a speaker. | Confidentiality is not necessary for the correct operation of VRRP, and there is no information in the VRRP messages that must be kept secret from other nodes on the LAN. | | `A.2:1` | `not-a-requirement` (never bound Ze): the sentence states a fact or describes another document, and directs no implementation | The bullet explains why VRRP over Token Ring is difficult, that source-route bridges need cached source-route information updated when the Master moves. It describes a property of that medium and states no obligation on a VRRP implementation; the one Token Ring obligation is site A.2:2. | o In order to switch to a new Master located on a different bridge Token-Ring segment from the previous Master when using source- route bridges, a mechanism is required to update cached source- route information. | ## Superseded RFC 5798 is obsoleted by RFC 9568. | Requirement | Disposition | Now stated at | Reason | |---|---|---|---| | [`RFC5798-5.1.1.2-1`](#rfc5798-5.1.1.2-1) Never forward a datagram destined to 224.0.0.18, regardless of its TTL (§5.1.1.2) | restated | RFC9568-5.1.1.2-1 | RFC 9568 Section 5.1.1.2 keeps the rule for IPv4 word for word | | [`RFC5798-5.1.1.3-1`](#rfc5798-5.1.1.3-1) Set the IPv4 TTL of transmitted VRRP packets to 255 (§5.1.1.3) | restated | RFC9568-5.1.1.3-1 | RFC 9568 Section 5.1.1.3 keeps the IPv4 TTL of 255 on transmit | | [`RFC5798-5.1.1.3-2`](#rfc5798-5.1.1.3-2) Discard a received IPv4 VRRP packet whose TTL is not 255 (§5.1.1.3, §7.1) | restated | RFC9568-5.1.1.3-2 | RFC 9568 Section 5.1.1.3 keeps the discard rule and Section 7.1 keeps the matching receive check | | [`RFC5798-5.1.2.2-1`](#rfc5798-5.1.2.2-1) Never forward a datagram destined to FF02:0:0:0:0:0:0:12, regardless of its Hop Limit (§5.1.2.2) | restated | RFC9568-5.1.2.2-1 | RFC 9568 Section 5.1.2.2 keeps the rule for the IPv6 group | | [`RFC5798-5.1.2.3-1`](#rfc5798-5.1.2.3-1) Set the IPv6 Hop Limit of transmitted VRRP packets to 255 (§5.1.2.3) | restated | RFC9568-5.1.2.3-1 | RFC 9568 Section 5.1.2.3 keeps the Hop Limit of 255 on transmit | | [`RFC5798-5.1.2.3-2`](#rfc5798-5.1.2.3-2) Discard a received IPv6 VRRP packet whose Hop Limit is not 255 (§5.1.2.3, §7.1) | restated | RFC9568-5.1.2.3-2 | RFC 9568 Section 5.1.2.3 keeps the discard rule and Section 7.1 keeps the matching receive check | | [`RFC5798-5.2.2-1`](#rfc5798-5.2.2-1) Discard a packet with unknown Type; 1 = ADVERTISEMENT is the only type defined (§5.2.2) | restated | RFC9568-5.2.2-1 | RFC 9568 Section 5.2.2 keeps ADVERTISEMENT as the only defined type and keeps the discard rule, and Section 7.1 adds an explicit receive check for it | | [`RFC5798-5.2.4-1`](#rfc5798-5.2.4-1) Use Priority 255 for the VRRP router that owns the IPvX address associated with the virtual router (§5.2.4) | restated | RFC9568-5.2.4-1 | RFC 9568 Section 5.2.4 keeps Priority 255 for the address owner | | [`RFC5798-5.2.4-2`](#rfc5798-5.2.4-2) Use Priority values 1-254 for VRRP routers backing up a virtual router (§5.2.4) | restated | RFC9568-5.2.4-2 | RFC 9568 Section 5.2.4 keeps the 1 to 254 range for backup routers | | [`RFC5798-5.2.6-1`](#rfc5798-5.2.6-1) Set the rsvd field to zero on transmission and ignore it on reception (§5.2.6) | restated | RFC9568-5.2.6-1 | RFC 9568 Section 5.2.6 renames the field to Reserve and keeps both halves of the rule | | [`RFC5798-5.2.8-1`](#rfc5798-5.2.8-1) Compute and verify the checksum as the 16-bit one's complement of the one's complement sum of the entire VRRP message starting with the version field and a pseudo-header defined by RFC 2460, with next header 112 and the checksum field zeroed, for both address families (§5.2.8, §7.1) | restated | RFC9568-5.2.8-1 | RFC 9568 Section 5.2.8 keeps the ones-complement arithmetic and CHANGES the coverage, stating the IPv4 checksum over the VRRP message alone and keeping the RFC 8200 Section 8.1 pseudo-header for IPv6 only. RFC 9568 Section 1.2 lists that as change 4. The two documents therefore disagree about the IPv4 checksum, and the deployed base computes this one | | [`RFC5798-5.2.9-1`](#rfc5798-5.2.9-1) Send the IPv6 link-local address associated with the virtual router as the first address in the list (§5.2.9, §6.1; lowercase "must" in the RFC) | restated | RFC9568-5.2.9-1 | RFC 9568 Section 5.2.9 states the same rule with an uppercase MUST, and erratum 8300 adds the matching receive check | | [`RFC5798-5.2.9-2`](#rfc5798-5.2.9-2) Never carry IPv4 and IPv6 addresses together in one IPvX Address field (§5.2.9) | restated | RFC9568-5.2.9-2 | RFC 9568 Section 5.2.9 states the rule as an address family that MUST be the same as the packet's IPvX header address family | | [`RFC5798-6.1-1`](#rfc5798-6.1-1) Never drop IPv6 Neighbor Solicitations and Neighbor Advertisements when Accept_Mode is False (§6.1, §6.4.3) | restated | RFC9568-6.1-1 | RFC 9568 Section 6.1 keeps the Accept_Mode note verbatim | | [`RFC5798-6.4.2-1`](#rfc5798-6.4.2-1) Backup: never respond to ARP requests for the IPv4 address(es) associated with the virtual router (§6.4.2) | restated | RFC9568-6.4.2-1 | RFC 9568 Section 6.4.2 keeps the rule and renames the state to Backup Router | | [`RFC5798-6.4.2-2`](#rfc5798-6.4.2-2) Backup: never respond to ND Neighbor Solicitation messages for the IPv6 address(es) associated with the virtual router (§6.4.2) | restated | RFC9568-6.4.2-2 | RFC 9568 Section 6.4.2 keeps the rule unchanged | | [`RFC5798-6.4.2-3`](#rfc5798-6.4.2-3) Backup: never send ND Router Advertisement messages for the virtual router (§6.4.2) | restated | RFC9568-6.4.2-3 | RFC 9568 Section 6.4.2 keeps the rule unchanged | | [`RFC5798-6.4.2-4`](#rfc5798-6.4.2-4) Backup: discard packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.2) | restated | RFC9568-6.4.2-4 | RFC 9568 Section 6.4.2 keeps the discard unchanged | | [`RFC5798-6.4.2-5`](#rfc5798-6.4.2-5) Backup: never accept packets addressed to the IPvX address(es) associated with the virtual router (§6.4.2) | restated | RFC9568-6.4.2-5 | RFC 9568 Section 6.4.2 keeps the rule unchanged | | [`RFC5798-6.4.2-6`](#rfc5798-6.4.2-6) Backup: on a Shutdown event, cancel the Master_Down_Timer and transition to Initialize (§6.4.2) | restated | RFC9568-6.4.2-6 | RFC 9568 Section 6.4.2 keeps the Shutdown transition and renames the timer to Active_Down_Timer | | [`RFC5798-6.4.2-7`](#rfc5798-6.4.2-7) Backup: when the Master_Down_Timer fires, send an ADVERTISEMENT, broadcast a gratuitous ARP carrying the virtual router MAC for each IPv4 address or, for IPv6, join the Solicited-Node multicast address and send an unsolicited Neighbor Advertisement for each address, set the Adver_Timer to Advertisement_Interval, and transition to Master (§6.4.2) | restated | RFC9568-6.4.2-7 | RFC 9568 Section 6.4.2 keeps the whole sequence, renames the timer and the state, and erratum 7949 corrects the gratuitous ARP to carry the virtual router IPv4 address with the virtual router MAC as the target link-layer address | | [`RFC5798-6.4.2-8`](#rfc5798-6.4.2-8) Backup: on an ADVERTISEMENT with Priority 0, set the Master_Down_Timer to Skew_Time (§6.4.2) | restated | RFC9568-6.4.2-8 | RFC 9568 Section 6.4.2 keeps the Skew_Time rule for a Priority 0 advertisement | | [`RFC5798-6.4.2-9`](#rfc5798-6.4.2-9) Backup: on a non-zero-priority ADVERTISEMENT, when Preempt_Mode is False or the advertised Priority is greater than or equal to the local Priority, set Master_Adver_Interval to the Adver Interval in the advertisement, recompute Master_Down_Interval, and reset the Master_Down_Timer to it (§6.4.2) | restated | RFC9568-6.4.2-9 | RFC 9568 Section 6.4.2 keeps the rule and ADDS one step, recomputing Skew_Time beside Active_Down_Interval, which this document omits | | [`RFC5798-6.4.2-10`](#rfc5798-6.4.2-10) Backup: on a non-zero-priority ADVERTISEMENT with Preempt_Mode True and an advertised Priority lower than the local Priority, discard the ADVERTISEMENT (§6.4.2) | restated | RFC9568-6.4.2-10 | RFC 9568 Section 6.4.2 keeps the discard unchanged | | [`RFC5798-6.4.3-1`](#rfc5798-6.4.3-1) Master: respond to ARP requests for the IPv4 address(es) associated with the virtual router (§6.4.3, §8.1.2) | restated | RFC9568-6.4.3-1 | RFC 9568 Section 6.4.3 keeps the ARP obligation and renames the state to Active Router | | [`RFC5798-6.4.3-2`](#rfc5798-6.4.3-2) Master: be a member of the Solicited-Node multicast address for the IPv6 address(es) associated with the virtual router (§6.4.3) | restated | RFC9568-6.4.3-2 | RFC 9568 Section 6.4.3 keeps the membership obligation unchanged | | [`RFC5798-6.4.3-3`](#rfc5798-6.4.3-3) Master: respond to ND Neighbor Solicitation messages for the IPv6 address(es) associated with the virtual router (§6.4.3, §8.2.2) | restated | RFC9568-6.4.3-3 | RFC 9568 Section 6.4.3 keeps the obligation and ADDS that the Neighbor Advertisement carries the Router Flag set | | [`RFC5798-6.4.3-4`](#rfc5798-6.4.3-4) Master: send ND Router Advertisements for the virtual router (§6.4.3) | restated | RFC9568-6.4.3-4 | RFC 9568 Section 6.4.3 keeps the obligation unchanged | | [`RFC5798-6.4.3-5`](#rfc5798-6.4.3-5) Master: forward packets with a destination link-layer MAC address equal to the virtual router MAC address (§6.4.3) | restated | RFC9568-6.4.3-5 | RFC 9568 Section 6.4.3 keeps the forwarding obligation unchanged | | [`RFC5798-6.4.3-6`](#rfc5798-6.4.3-6) Master: accept packets addressed to the IPvX address(es) associated with the virtual router when it is the address owner or when Accept_Mode is True (§6.4.3) | restated | RFC9568-6.4.3-6 | RFC 9568 Section 6.4.3 keeps both admitting conditions unchanged | | [`RFC5798-6.4.3-7`](#rfc5798-6.4.3-7) Master: never accept those packets when it is neither the address owner nor configured with Accept_Mode True (§6.4.3) | restated | RFC9568-6.4.3-7 | RFC 9568 Section 6.4.3 keeps the refusal unchanged | | [`RFC5798-6.4.3-8`](#rfc5798-6.4.3-8) Master: on a Shutdown event, cancel the Adver_Timer, send an ADVERTISEMENT with Priority = 0, and transition to Initialize (§6.4.3) | restated | RFC9568-6.4.3-8 | RFC 9568 Section 6.4.3 keeps the Shutdown sequence unchanged | | [`RFC5798-6.4.3-9`](#rfc5798-6.4.3-9) Master: when the Adver_Timer fires, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) | restated | RFC9568-6.4.3-9 | RFC 9568 Section 6.4.3 keeps the timer-expiry behaviour unchanged | | [`RFC5798-6.4.3-10`](#rfc5798-6.4.3-10) Master: on an ADVERTISEMENT with Priority 0, send an ADVERTISEMENT and reset the Adver_Timer to Advertisement_Interval (§6.4.3) | restated | RFC9568-6.4.3-10 | RFC 9568 Section 6.4.3 keeps the response to a Priority 0 advertisement unchanged | | [`RFC5798-6.4.3-11`](#rfc5798-6.4.3-11) Master: on an ADVERTISEMENT with a higher Priority, or an equal Priority with a greater sender primary IPvX address, cancel the Adver_Timer, set Master_Adver_Interval to the advertised Adver Interval, recompute Skew_Time and Master_Down_Interval, set the Master_Down_Timer, and transition to Backup (§6.4.3) | restated | RFC9568-6.4.3-11 | RFC 9568 Section 6.4.3 keeps the whole sequence and states that the sender address comparison is an unsigned integer comparison in network byte order | | [`RFC5798-6.4.3-12`](#rfc5798-6.4.3-12) Master: on a losing ADVERTISEMENT, one with a lower Priority or an equal Priority with a smaller sender address, discard it (§6.4.3) | restated | RFC9568-6.4.3-12 | RFC 9568 Section 6.4.3 keeps the discard and ADDS a step this document does not have, sending an advertisement immediately so learning bridges relearn the segment. RFC 9568 Section 1.2 lists that as change 5 | | [`RFC5798-7.1-1`](#rfc5798-7.1-1) Rx: verify that the VRRP version is 3 (§7.1) | restated | RFC9568-7.1-1 | RFC 9568 Section 7.1 keeps the version check | | [`RFC5798-7.1-2`](#rfc5798-7.1-2) Rx: verify that the received packet contains the complete VRRP packet, the fixed fields and the IPvX address list (§7.1) | restated | RFC9568-7.1-3 | RFC 9568 Section 7.1 keeps the completeness check | | [`RFC5798-7.1-3`](#rfc5798-7.1-3) Rx: verify that the VRID is configured on the receiving interface and that the local router is not the IPvX address owner (§7.1) | restated | RFC9568-7.1-4 | RFC 9568 Section 7.1 keeps both halves as published, and erratum 8298 splits them, lowering the address-owner half to a SHOULD that logs rather than discards | | [`RFC5798-7.1-4`](#rfc5798-7.1-4) Rx: discard the packet when any mandatory receive check fails (§7.1) | restated | RFC9568-7.1-6 | RFC 9568 Section 7.1 keeps the discard on a failed mandatory check, with the SHOULD log and the MAY network-management indication beside it | | [`RFC5798-7.2-1`](#rfc5798-7.2-1) Tx: fill in the VRRP packet fields from the virtual router configuration state and compute the VRRP checksum (§7.2) | restated | RFC9568-7.2-1 | RFC 9568 Section 7.2 keeps the fill-and-checksum step unchanged | | [`RFC5798-7.2-2`](#rfc5798-7.2-2) Tx: set the source MAC address to the virtual router MAC address (§7.2) | restated | RFC9568-7.2-2 | RFC 9568 Section 7.2 keeps the virtual router MAC as the source link-layer address | | [`RFC5798-7.2-3`](#rfc5798-7.2-3) Tx: set the source IPv4 address to the interface primary IPv4 address, or the source IPv6 address to the interface link-local IPv6 address (§7.2) | restated | RFC9568-7.2-3 | RFC 9568 Section 7.2 keeps both source-address rules unchanged | | [`RFC5798-7.2-4`](#rfc5798-7.2-4) Tx: set the IPvX protocol to VRRP and send the packet to the VRRP IPvX multicast group (§7.2) | restated | RFC9568-7.2-4 | RFC 9568 Section 7.2 keeps protocol 112 and the IPvX multicast destination | | [`RFC5798-7.4-1`](#rfc5798-7.4-1) Create the Interface Identifiers of an IPv6 router running VRRP in the normal manner, as in Transmission of IPv6 Packets over Ethernet Networks (§7.4) | dropped | not stated | RFC 9568 Section 7.4 replaces the sentence rather than restating it. It cites RFC 8064 and RFC 7217 as the default scheme for a stable SLAAC address and states no "normal manner" obligation, so no equivalent requirement remains | | [`RFC5798-7.4-2`](#rfc5798-7.4-2) Never use the virtual router MAC address to create the Modified Extended Unique Identifier (EUI)-64 identifiers (§7.4) | restated | RFC9568-7.4-1 | RFC 9568 Section 7.4 keeps the prohibition and restates it over the Net_Iface parameter of the RFC 7217 and RFC 8981 derivation algorithms | | [`RFC5798-8.1.2-1`](#rfc5798-8.1.2-1) Master: never respond to a host ARP request for a virtual router IPv4 address with its physical MAC address (§8.1.2) | restated | RFC9568-8.1.2-1 | RFC 9568 Section 8.1.2 keeps the prohibition unchanged | | [`RFC5798-8.1.3-1`](#rfc5798-8.1.3-1) Advertise the virtual router MAC address in the Proxy ARP message when Proxy ARP is used on a VRRP router (§8.1.3; lowercase "must" in the RFC) | restated | RFC9568-8.1.3-1 | RFC 9568 Section 8.1.3 states the same obligation with an uppercase MUST | | [`RFC5798-8.2.2-1`](#rfc5798-8.2.2-1) Master: never respond to an ND Neighbor Solicitation for a virtual router IPv6 address with its physical MAC address (§8.2.2) | restated | RFC9568-8.2.2-1 | RFC 9568 Section 8.2.2 keeps the prohibition unchanged | | [`RFC5798-8.2.2-2`](#rfc5798-8.2.2-2) Master: include the virtual router MAC address in the source link-layer address option of a Neighbor Solicitation it sends for a host's IPv6 address, when it sends that option (§8.2.2) | restated | RFC9568-8.2.2-2 | RFC 9568 Section 8.2.2 keeps the obligation unchanged | | [`RFC5798-8.2.2-3`](#rfc5798-8.2.2-3) Master: never use its physical MAC address in that source link-layer address option (§8.2.2) | restated | RFC9568-8.2.2-3 | RFC 9568 Section 8.2.2 keeps the prohibition unchanged | | [`RFC5798-8.2.2-4`](#rfc5798-8.2.2-4) At system boot, delay every ND Router Advertisement, Neighbor Advertisement and Neighbor Solicitation until both the IPv6 address and the virtual router MAC address are configured (§8.2.2; lowercase "must" in the RFC) | restated | RFC9568-8.2.2-4 | RFC 9568 Section 8.2.2 states the same delay with an uppercase MUST | | [`RFC5798-8.2.3-1`](#rfc5798-8.2.3-1) Configure Backup routers to send the same Router Advertisement options as the address owner (§8.2.3; lowercase "must" in the RFC) | restated | RFC9568-8.2.3-1 | RFC 9568 Section 8.2.3 states the same obligation with an uppercase MUST | | [`RFC5798-8.4.2-1`](#rfc5798-8.4.2-1) Interop mode, Master: send both VRRPv2 and VRRPv3 advertisements at the configured rate, even when it is sub-second (§8.4.2) | restated | RFC9568-8.4.2-1 | RFC 9568 Section 8.4.2 keeps the obligation unchanged | | [`RFC5798-A.2-1`](#rfc5798-a.2-1) Token Ring: implement the functional-address mode of operation when supporting VRRP on Token Ring (§A.2) | dropped | not stated | RFC 9568 removes the legacy-media appendices. Its Section 1.2 lists as change 6 that the appendices describing operation over FDDI, Token Ring and ATM LAN Emulation were removed, so RFC 9568 states no functional-address obligation | | [`RFC5798-7.1-5`](#rfc5798-7.1-5) Log the event when a mandatory receive check fails (§7.1) | restated | RFC9568-7.1-7 | RFC 9568 Section 7.1 keeps the log and adds rate-limiting to it | | [`RFC5798-7.1-8`](#rfc5798-7.1-8) Log the event when the optional address-list check fails (§7.1) | restated | RFC9568-7.1-9 | RFC 9568 Section 7.1 keeps the log, adds rate-limiting, and states it in one sentence covering the Max Advertise Interval mismatch as well | | [`RFC5798-8.1.1-1`](#rfc5798-8.1.1-1) Set the IPv4 source address of an ICMP redirect to the address the end-host used when making its next-hop routing decision (§8.1.1; lowercase "should" in the RFC) | unextracted | §8.1.1 | RFC 9568 Section 8.1.1 keeps the sentence for IPv4, and rfc/short/rfc9568.md declares a row for the IPv6 counterpart at RFC9568-8.2.1-1 only. The IPv4 half of that pair is an extraction hole in the successor's summary | | [`RFC5798-8.1.2-2`](#rfc5798-8.1.2-2) After a restart or boot, send no ARP message using the physical MAC address for an owned virtual IPv4 address (§8.1.2) | restated | RFC9568-8.1.2-3 | RFC 9568 Section 8.1.2 keeps the rule unchanged | | [`RFC5798-8.1.2-3`](#rfc5798-8.1.2-3) When configuring an interface, broadcast a gratuitous ARP request carrying the virtual router MAC address for each IPv4 address on that interface (§8.1.2; lowercase "should" in the RFC) | restated | RFC9568-8.1.2-4 | RFC 9568 Section 8.1.2 keeps this half at SHOULD | | [`RFC5798-8.1.2-4`](#rfc5798-8.1.2-4) At system boot, delay gratuitous ARP requests and ARP responses until both the IPv4 address and the virtual router MAC address are configured (§8.1.2) | restated | RFC9568-8.1.2-2 | RFC 9568 Section 8.1.2 splits the boot bullet out and RAISES it to a MUST | | [`RFC5798-8.1.2-5`](#rfc5798-8.1.2-5) Use an IP address known to belong to a particular router when direct access to that router is required, for example ssh (§8.1.2; lowercase "must" inside a SHOULD-level bullet list) | restated | RFC9568-8.1.2-5 | RFC 9568 Section 8.1.2 keeps the recommendation | | [`RFC5798-8.2.1-1`](#rfc5798-8.2.1-1) Set the IPv6 source address of an ICMPv6 redirect to the address the end-host used when making its next-hop routing decision (§8.2.1; lowercase "should" in the RFC) | restated | RFC9568-8.2.1-1 | RFC 9568 Section 8.2.1 keeps the recommendation | | [`RFC5798-8.2.2-5`](#rfc5798-8.2.2-5) After a restart or boot, send no ND message using the physical MAC address for an owned virtual IPv6 address (§8.2.2) | restated | RFC9568-8.2.2-5 | RFC 9568 Section 8.2.2 keeps the rule unchanged | | [`RFC5798-8.2.2-6`](#rfc5798-8.2.2-6) When configuring an interface, send an unsolicited ND Neighbor Advertisement carrying the virtual router MAC address for the IPv6 address on that interface (§8.2.2; lowercase "should" in the RFC) | restated | RFC9568-8.2.2-6 | RFC 9568 Section 8.2.2 keeps the recommendation | | [`RFC5798-8.2.3-2`](#rfc5798-8.2.3-2) Send Router Advertisement options that advertise special services from the address owner unless the Backup routers can assume those services in full with a complete and synchronized database (§8.2.3; lowercase "should not" in the RFC) | restated | RFC9568-8.2.3-2 | RFC 9568 Section 8.2.3 keeps the recommendation | | [`RFC5798-8.3.1-1`](#rfc5798-8.3.1-1) Never forward packets addressed to the IPvX address a router becomes Master for when it is not the address owner (§8.3.1) | restated | RFC9568-8.3.1-1 | RFC 9568 Section 8.3.1 keeps the rule and states it over both address families | | [`RFC5798-8.3.2-1`](#rfc5798-8.3.2-1) Configure no more than one VRRP router on the link with priority 255 for a single VRID (§8.3.2; lowercase in the RFC) | restated | RFC9568-8.3.2-1 | RFC 9568 Section 8.3.2 keeps the recommendation and adds a rate-limited log when several priority-255 advertisers are seen | | [`RFC5798-8.3.2-2`](#rfc5798-8.3.2-2) Distribute the priority values of multiple Backup routers uniformly to speed convergence (§8.3.2; lowercase in the RFC) | restated | RFC9568-8.3.2-3 | RFC 9568 Section 8.3.2 keeps the recommendation and states it as sufficiently different priorities | | [`RFC5798-8.4.2-2`](#rfc5798-8.4.2-2) Do not run VRRPv2 and VRRPv3 mixed operation as a permanent deployment; it is an upgrade path (§8.4.2) | restated | RFC9568-8.4.2-5 | RFC 9568 Section 8.4.2 keeps the same statement | | [`RFC5798-8.4.2-3`](#rfc5798-8.4.2-3) Interop mode, Backup: time out from the rate the Master advertises, translating a VRRPv2 Master's seconds into centiseconds (§8.4.2) | restated | RFC9568-8.4.2-2 | RFC 9568 Section 8.4.2 RAISES both halves to MUST and splits them, keeping the timeout at RFC9568-8.4.2-2 and the seconds-to-centiseconds translation at RFC9568-8.4.2-3 | | [`RFC5798-8.4.2-4`](#rfc5798-8.4.2-4) Interop mode, Backup: ignore VRRPv2 advertisements from the current Master when VRRPv3 packets are also being received from it (§8.4.2) | restated | RFC9568-8.4.2-4 | RFC 9568 Section 8.4.2 keeps the recommendation | | [`RFC5798-8.4.3.1-1`](#rfc5798-8.4.3.1-1) Never give a VRRPv2 implementation a higher priority than the VRRPv2/VRRPv3 implementation it interacts with when that peer advertises at a sub-second rate (§8.4.3.1) | restated | RFC9568-8.4.2.1.1-1 | RFC 9568 renumbers the subsection to 8.4.2.1.1 and keeps the recommendation | | [`RFC5798-A.1-1`](#rfc5798-a.1-1) FDDI: configure the virtual router MAC address by adding a unicast MAC filter in the FDDI device rather than changing its hardware MAC address (§A.1) | dropped | not stated | RFC 9568 removes the legacy-media appendices. Its Section 1.2 lists as change 6 that the appendices describing operation over FDDI, Token Ring and ATM LAN Emulation were removed, so RFC 9568 states no unicast-MAC-filter recommendation | | [`RFC5798-7.1-6`](#rfc5798-7.1-6) Indicate through network management that a receive error, or a detected misconfiguration, occurred (§7.1) | restated | RFC9568-7.1-10 | RFC 9568 Section 7.1 keeps the network-management indication as a MAY | | [`RFC5798-7.1-7`](#rfc5798-7.1-7) Verify that "Count IPvX Addrs" and the list of IPvX addresses match the addresses configured for the VRID (§7.1) | restated | RFC9568-7.1-11 | RFC 9568 Section 7.1 keeps the address-list check as a MAY and renames the field to IPvX Addr Count | | [`RFC5798-8.2.2-7`](#rfc5798-8.2.2-7) Answer Duplicate Address Detection for an owned address from the Backup router while the Master restarts; one solution is not to run DAD in that case (§8.2.2) | restated | RFC9568-8.2.2-7 | RFC 9568 Section 8.2.2 keeps the note | | [`RFC5798-8.4.2-5`](#rfc5798-8.4.2-5) Implement a configuration flag that tells the router to listen for and send both VRRPv2 and VRRPv3 advertisements (§8.4.2) | restated | RFC9568-8.4.2-6 | RFC 9568 Section 8.4.2 keeps the flag as a MAY | | [`RFC5798-8.4.2-6`](#rfc5798-8.4.2-6) Report when a VRRPv3 Master is not sending VRRPv2 packets while interop mode is configured (§8.4.2) | restated | RFC9568-8.4.2-7 | RFC 9568 Section 8.4.2 keeps the report as a MAY | | [`RFC5798-A.2-2`](#rfc5798-a.2-2) Token Ring: support the unicast mode of operation beside the functional-address mode (§A.2) | dropped | not stated | RFC 9568 removes the legacy-media appendices. Its Section 1.2 lists as change 6 that the appendices describing operation over FDDI, Token Ring and ATM LAN Emulation were removed, so RFC 9568 states no unicast-mode permission | --- ### Page: RFC 5838 - Support of Address Families in OSPFv3 https://ze-software.net/quality/rfc-compliance/rfc5838/ # RFC 5838 - Support of Address Families in OSPFv3 Experimental. Every requirement this repository extracted from RFC 5838, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 25.0% | 4 of 16 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 12.5% | 2 of 16 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 16 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 10 tagged units | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 16 | of 27 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 2 | of 16 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 12.5% | 2 of 16 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 16 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 16 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 50.0% | 8 of 16 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 16 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Experimental | | Enrolment | Enrolled | | Requirements | 27 | | Gated MUST-level | 16 | | Not applicable, so out of scope | 2 | | Declared gaps | 8 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 10 | | Tagged units | 10 | | Recorded audit verdicts | 0 | | Discrimination records | 0 | | Summary | `rfc/short/rfc5838.md` | | Requirement shard | `rfc/requirements/rfc5838.md` | | RFC text | `rfc/full/rfc5838.txt` | ## Enrolment Enrolled: Support of Address Families in OSPFv3 (RFC 5838): 4 MET (non-default-AF AF-bit discard + base-AF ignore, AF-conformance route-computation gate, IPv4 forwarding-address AF-width, global-IPv6 virtual-link endpoints) + 2 single-polarity positive (base-AF no-reject path, IPv4 forwarding-address remaining-bits-zero) + 8 gap (AF-bit not set in LSAs, IPv4 Link-LSA link-local, section 2.7 per-AF MTU / M6-bit machinery) + 2 not-applicable (IPsec restricted to default IPv6-unicast AF, IANA 128-255 Standards-Action process) ## What the public ledger says **Status:** Experimental **What the ledger says is covered** - 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 [`rfc/short/rfc5838.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5838.md) via `./le rfc check`. **What the ledger says remains** Eight MUST gaps annotated in [`rfc/short/rfc5838.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5838.md): the AF-bit is not set in originated LSAs ([`RFC5838-2.2-1`](#rfc5838-2.2-1)); an IPv4-AF Link-LSA carries no IPv4 link-local address ([`RFC5838-2.5-1`](#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`](#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. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 4 | one part of the gated population | | Annotated instead of tested | 12 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **16** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (4):** [`RFC5838-2.3-1`](#rfc5838-2.3-1), [`RFC5838-2.4-1`](#rfc5838-2.4-1), [`RFC5838-2.6-1`](#rfc5838-2.6-1), [`RFC5838-2.8-1`](#rfc5838-2.8-1) **Annotated instead of tested (12):** [`RFC5838-2.2-1`](#rfc5838-2.2-1), [`RFC5838-2.4-2`](#rfc5838-2.4-2), [`RFC5838-2.5-1`](#rfc5838-2.5-1), [`RFC5838-2.6-2`](#rfc5838-2.6-2), [`RFC5838-2.7-1`](#rfc5838-2.7-1), [`RFC5838-2.7-2`](#rfc5838-2.7-2), [`RFC5838-2.7-3`](#rfc5838-2.7-3), [`RFC5838-2.7-4`](#rfc5838-2.7-4), [`RFC5838-2.7-8`](#rfc5838-2.7-8), [`RFC5838-2.7-11`](#rfc5838-2.7-11), [`RFC5838-4-1`](#rfc5838-4-1), [`RFC5838-5-3`](#rfc5838-5-3) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5838-2.2-1` | A router supporting AFs "MUST set the AF-bit in the OSPFv3 Options field of Hello packets, Database Description packets, and LSAs" (§2.2) | MUST | 2.2 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the AF-bit is set in Hello and DD Options only; LSA origination never sets it -- encoder_v6.go:33 applies SetAF to the Hello/DD path only, and origination_v6.go:254 with origination_v6_link.go:51 build Router/Network/Link-LSA Options via neutralToV6Options, which omits OptAF | | `RFC5838-2.3-1` | Prefixes that don't conform to an instance's AF "MUST NOT be used in the route computation for that instance" (§2.3) | MUST NOT | 2.3 | **positive:** `unit/verify` [`TestIPv4OverV3BuildRoutes`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L278). **negative:** `unit/verify` [`TestV6PrefixToNetipAFWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L251) | | `RFC5838-2.4-1` | A router participating in an AF (AF-bit set) "MUST discard Hello packets having the AF-bit clear in the Options field" (§2.4) | MUST | 2.4 | **positive:** `unit/verify` [`TestAFBitGatesFullNonDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/multiaf_engine_test.go#L150). **negative:** `unit/verify` [`TestAFBitGatesFullNonDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/multiaf_engine_test.go#L149) | | `RFC5838-2.4-2` | For the Base IPv6 unicast AF the AF-bit check "MUST NOT be done (for backward compatibility)" (§2.4) | MUST NOT | 2.4 | **positive:** `unit/verify` [`TestAFBitIgnoredDefaultAF`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/multiaf_engine_test.go#L175). **negative:** no negative test. **{single-polarity}:** the default IPv6-unicast AF has no reject path -- afBitAccepted at multiaf.go:181 returns true immediately for e.af.isDefault, so a base-AF Hello is never dropped for a missing AF-bit and there is no negative behavior to exercise | | `RFC5838-2.5-1` | After placing the link's IPv4 address in the first 32 bits of the Link-LSA "link local address" field, "The remaining bits MUST be set to zero" (§2.5) | MUST | 2.5 | **positive:** no positive test. **negative:** no negative test. **{gap}:** v6OriginateLinkLSA at origination_v6_link.go:42-48 always encodes an IPv6 link-local address in the Link-LSA link-local field and returns false without one, so an IPv4-AF Link-LSA never carries the interface IPv4 address in the leading 32 bits | | `RFC5838-2.6-1` | For IPv4 unicast and IPv4 multicast AFs "the Forwarding Address in AS-external-LSAs and NSSA-LSAs MUST encode an IPv4 address" (§2.6) | MUST | 2.6 | **positive:** `unit/verify` [`TestV6ForwardingAddrAFWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L331). **negative:** `unit/verify` [`TestV6ForwardingAddrAFWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L332) | | `RFC5838-2.6-2` | After placing the IPv4 Forwarding Address in the first 32 bits of the Forwarding Address field, "The remaining bits MUST be set to zero" (§2.6) | MUST | 2.6 | **positive:** `unit/verify` [`TestV6ForwardingAddrAFWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L333). **negative:** no negative test. **{single-polarity}:** forwardingAddressForAF at origination_v6_nssa.go:29-31 zero-initialises the 16-byte field and writes only the leading 4 IPv4 octets, so the remaining bits are structurally zero and no non-zero-trailing path exists to reject | | `RFC5838-2.7-1` | For non-IPv6 AFs "both the MTU for the instance address family and the IPv6 MTU used for OSPFv3 maximum packet determination MUST be considered" (§2.7) | MUST | 2.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze tracks a single per-interface MTU -- neighbor/dd.go:170 sends cfg.InterfaceMTU and neighbor/dd.go:45 checks it -- with no separate address-family MTU versus IPv6 MTU, so the two are not considered independently | | `RFC5838-2.7-2` | "The MTU in the Database Description packet MUST always contain the MTU corresponding to the advertised address family" (§2.7) | MUST | 2.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the Database Description always carries the single cfg.InterfaceMTU at neighbor/dd.go:170 with no per-address-family MTU, so for a non-IPv6 AF whose MTU differs the DD does not carry the AF-specific MTU | | `RFC5838-2.7-3` | For an IPv4-address-family instance "the IPv4 MTU for the interface MUST be specified in the interface MTU field" (§2.7) | MUST | 2.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze has no IPv4-address-family interface MTU; the DD MTU is the one configured interface MTU at neighbor/dd.go:170, never an IPv4-specific value | | `RFC5838-2.7-4` | "The value used for OSPFv3 maximum packet size determination MUST also be compatible for an adjacency to be established" (§2.7) | MUST | 2.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** the adjacency MTU-compatibility gate at neighbor/dd.go:45 compares the single interface MTU only; ze does not derive an IPv6-MTU-based maximum packet size distinct from the AF MTU per RFC 5838 §2.7 | | `RFC5838-2.7-8` | "If the IPv6 and IPv4 MTUs differ, the M6-bit MUST be set for non-IPv6 address families" (§2.7) | MUST | 2.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze implements no M6-bit -- a grep for m6 across internal/plugins/ospf returns nothing -- so the DD encoder at encoder_v6.go:90 never sets it for a non-IPv6 AF | | `RFC5838-2.7-11` | If the M6-bit is set in a received DD packet for a non-IPv6 AF, "the receiving router MUST NOT check the Interface MTU in the Database Description packet against the receiving interface's IPv6 MTU" (§2.7) | MUST NOT | 2.7 | **positive:** no positive test. **negative:** no negative test. **{gap}:** ze neither sets nor reads the M6-bit and performs only the single-MTU check at neighbor/dd.go:45, so the M6-conditioned suppression of an IPv6-MTU comparison is unimplemented | | `RFC5838-2.8-1` | For a virtual link "there MUST be a global IPv6 address associated with the virtual link so that OSPFv3 control packets are forwarded correctly by the intermediate hops" (§2.8) | MUST | 2.8 | **positive:** `unit/verify` [`TestV6VirtualEndpointResolvesGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L438). **negative:** `unit/verify` [`TestV6VirtualEndpointRequiresGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L474) | | `RFC5838-4-1` | When multiple OSPFv3 instances use the same interface "they all MUST use the same Security Association (SA)" (§4) | MUST | 4 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze runs OSPFv3 IPsec on the default IPv6-unicast AF only -- validateConfigAF at config.go:907-909 rejects an ipsec block on any non-IPv6 family -- so multiple AF instances never share an interface SA and the requirement's precondition never arises | | `RFC5838-5-3` | Before assignments in the 128-255 range "there MUST be a Standards Track RFC including an IANA Considerations section explicitly specifying the AF Instance IDs being assigned" (§5) | MUST | 5 | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** this binds the IANA registration process, not an implementation; ze has no code path that assigns AF Instance IDs and it rejects Instance IDs above 127 for AF use at multiaf.go:71 and via ErrInstanceIDRange in config.go | | `RFC5838-2.5-2` | "An implementation SHOULD resolve layer 3 to layer 2 mappings via the Address Resolution Protocol (ARP) or Neighbor Discovery (ND) for a DIA even if the IPv4 address is not on the same subnet as the router's interface IP address" (§2.5) | SHOULD | 2.5 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-2.7-5` | "If the M6-bit is clear, the specified MTU SHOULD also be checked against the IPv6 MTU" (§2.7) | SHOULD | 2.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-2.7-6` | When the M6-bit is clear, "the Database Description packet SHOULD be rejected if the MTU is larger than the receiving interface's IPv6 MTU" (§2.7) | SHOULD | 2.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-2.7-7` | "An OSPFv3 router SHOULD NOT set the M6-bit if its IPv6 MTU and address family specific MTU are the same" (§2.7) | SHOULD NOT | 2.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-2.7-9` | When the IPv6 MTU TLV is present, it carries the IPv6 MTU "that SHOULD be compared with the local IPv6 MTU" (§2.7) | SHOULD | 2.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-2.7-10` | When the IPv6 MTU TLV is absent, "the minimum IPv6 MTU of 1280 octets SHOULD be used for the comparison" (§2.7) | SHOULD | 2.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-2.7-12` | "The Interface MTU SHOULD be set to 0 in Database Description packets sent over virtual links" (§2.7) | SHOULD | 2.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-2.7-14` | For IPv6 MTU TLV instances subsequent to the first, "the LLS inconsistency SHOULD be logged" (§2.7) | SHOULD | 2.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-5-2` | When the Instance ID field is used for address families "the assignments herein SHOULD be honored" (§5) | SHOULD | 5 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-2.7-13` | "Only one instance of the IPv6 MTU TLV MAY appear in the LLS block" (§2.7) | MAY | 2.7 | **positive:** no positive test. **negative:** no negative test | | `RFC5838-5-1` | "the Instance ID field MAY be used for applications other than the support of multiple address families" (§5) | MAY | 5 | **positive:** no positive test. **negative:** no negative test | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5838-2.2-1`](#rfc5838-2.2-1) A router supporting AFs "MUST set the AF-bit in the OSPFv3 Options field of Hello packets, Database Description packets, and LSAs" (§2.2) | {gap}, no test | the AF-bit is set in Hello and DD Options only; LSA origination never sets it -- encoder_v6.go:33 applies SetAF to the Hello/DD path only, and origination_v6.go:254 with origination_v6_link.go:51 build Router/Network/Link-LSA Options via neutralToV6Options, which omits OptAF | | [`RFC5838-2.5-1`](#rfc5838-2.5-1) After placing the link's IPv4 address in the first 32 bits of the Link-LSA "link local address" field, "The remaining bits MUST be set to zero" (§2.5) | {gap}, no test | v6OriginateLinkLSA at origination_v6_link.go:42-48 always encodes an IPv6 link-local address in the Link-LSA link-local field and returns false without one, so an IPv4-AF Link-LSA never carries the interface IPv4 address in the leading 32 bits | | [`RFC5838-2.7-1`](#rfc5838-2.7-1) For non-IPv6 AFs "both the MTU for the instance address family and the IPv6 MTU used for OSPFv3 maximum packet determination MUST be considered" (§2.7) | {gap}, no test | ze tracks a single per-interface MTU -- neighbor/dd.go:170 sends cfg.InterfaceMTU and neighbor/dd.go:45 checks it -- with no separate address-family MTU versus IPv6 MTU, so the two are not considered independently | | [`RFC5838-2.7-2`](#rfc5838-2.7-2) "The MTU in the Database Description packet MUST always contain the MTU corresponding to the advertised address family" (§2.7) | {gap}, no test | the Database Description always carries the single cfg.InterfaceMTU at neighbor/dd.go:170 with no per-address-family MTU, so for a non-IPv6 AF whose MTU differs the DD does not carry the AF-specific MTU | | [`RFC5838-2.7-3`](#rfc5838-2.7-3) For an IPv4-address-family instance "the IPv4 MTU for the interface MUST be specified in the interface MTU field" (§2.7) | {gap}, no test | ze has no IPv4-address-family interface MTU; the DD MTU is the one configured interface MTU at neighbor/dd.go:170, never an IPv4-specific value | | [`RFC5838-2.7-4`](#rfc5838-2.7-4) "The value used for OSPFv3 maximum packet size determination MUST also be compatible for an adjacency to be established" (§2.7) | {gap}, no test | the adjacency MTU-compatibility gate at neighbor/dd.go:45 compares the single interface MTU only; ze does not derive an IPv6-MTU-based maximum packet size distinct from the AF MTU per RFC 5838 §2.7 | | [`RFC5838-2.7-8`](#rfc5838-2.7-8) "If the IPv6 and IPv4 MTUs differ, the M6-bit MUST be set for non-IPv6 address families" (§2.7) | {gap}, no test | ze implements no M6-bit -- a grep for m6 across internal/plugins/ospf returns nothing -- so the DD encoder at encoder_v6.go:90 never sets it for a non-IPv6 AF | | [`RFC5838-2.7-11`](#rfc5838-2.7-11) If the M6-bit is set in a received DD packet for a non-IPv6 AF, "the receiving router MUST NOT check the Interface MTU in the Database Description packet against the receiving interface's IPv6 MTU" (§2.7) | {gap}, no test | ze neither sets nor reads the M6-bit and performs only the single-MTU check at neighbor/dd.go:45, so the M6-conditioned suppression of an IPv6-MTU comparison is unimplemented | | [`RFC5838-4-1`](#rfc5838-4-1) When multiple OSPFv3 instances use the same interface "they all MUST use the same Security Association (SA)" (§4) | no test | no test carries this requirement id; annotated {not-applicable}: ze runs OSPFv3 IPsec on the default IPv6-unicast AF only -- validateConfigAF at config.go:907-909 rejects an ipsec block on any non-IPv6 family -- so multiple AF instances never share an interface SA and the requirement's precondition never arises | | [`RFC5838-5-3`](#rfc5838-5-3) Before assignments in the 128-255 range "there MUST be a Standards Track RFC including an IANA Considerations section explicitly specifying the AF Instance IDs being assigned" (§5) | no test | no test carries this requirement id; annotated {not-applicable}: this binds the IANA registration process, not an implementation; ze has no code path that assigns AF Instance IDs and it rejects Instance IDs above 127 for AF use at multiaf.go:71 and via ErrInstanceIDRange in config.go | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5838-2.2-1`](#rfc5838-2.2-1) A router supporting AFs "MUST set the AF-bit in the OSPFv3 Options field of Hello packets, Database Description packets, and LSAs" (§2.2) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-2.2-1, so no unit is bound to it. ### [`RFC5838-2.3-1`](#rfc5838-2.3-1) Prefixes that don't conform to an instance's AF "MUST NOT be used in the route computation for that instance" (§2.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestV6PrefixToNetipAFWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L251) | unit/verify | unproven | | positive | [`TestIPv4OverV3BuildRoutes`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L278) | unit/verify | unproven | ### [`RFC5838-2.4-1`](#rfc5838-2.4-1) A router participating in an AF (AF-bit set) "MUST discard Hello packets having the AF-bit clear in the Options field" (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestAFBitGatesFullNonDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/multiaf_engine_test.go#L149) | unit/verify | unproven | | positive | [`TestAFBitGatesFullNonDefault`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/multiaf_engine_test.go#L150) | unit/verify | unproven | ### [`RFC5838-2.4-2`](#rfc5838-2.4-2) For the Base IPv6 unicast AF the AF-bit check "MUST NOT be done (for backward compatibility)" (§2.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestAFBitIgnoredDefaultAF`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/multiaf_engine_test.go#L175) | unit/verify | unproven | ### [`RFC5838-2.5-1`](#rfc5838-2.5-1) After placing the link's IPv4 address in the first 32 bits of the Link-LSA "link local address" field, "The remaining bits MUST be set to zero" (§2.5) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-2.5-1, so no unit is bound to it. ### [`RFC5838-2.6-1`](#rfc5838-2.6-1) For IPv4 unicast and IPv4 multicast AFs "the Forwarding Address in AS-external-LSAs and NSSA-LSAs MUST encode an IPv4 address" (§2.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestV6ForwardingAddrAFWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L332) | unit/verify | unproven | | positive | [`TestV6ForwardingAddrAFWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L331) | unit/verify | unproven | ### [`RFC5838-2.6-2`](#rfc5838-2.6-2) After placing the IPv4 Forwarding Address in the first 32 bits of the Forwarding Address field, "The remaining bits MUST be set to zero" (§2.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestV6ForwardingAddrAFWidth`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/afstrategy_v6_test.go#L333) | unit/verify | unproven | ### [`RFC5838-2.7-1`](#rfc5838-2.7-1) For non-IPv6 AFs "both the MTU for the instance address family and the IPv6 MTU used for OSPFv3 maximum packet determination MUST be considered" (§2.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-2.7-1, so no unit is bound to it. ### [`RFC5838-2.7-2`](#rfc5838-2.7-2) "The MTU in the Database Description packet MUST always contain the MTU corresponding to the advertised address family" (§2.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-2.7-2, so no unit is bound to it. ### [`RFC5838-2.7-3`](#rfc5838-2.7-3) For an IPv4-address-family instance "the IPv4 MTU for the interface MUST be specified in the interface MTU field" (§2.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-2.7-3, so no unit is bound to it. ### [`RFC5838-2.7-4`](#rfc5838-2.7-4) "The value used for OSPFv3 maximum packet size determination MUST also be compatible for an adjacency to be established" (§2.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-2.7-4, so no unit is bound to it. ### [`RFC5838-2.7-8`](#rfc5838-2.7-8) "If the IPv6 and IPv4 MTUs differ, the M6-bit MUST be set for non-IPv6 address families" (§2.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-2.7-8, so no unit is bound to it. ### [`RFC5838-2.7-11`](#rfc5838-2.7-11) If the M6-bit is set in a received DD packet for a non-IPv6 AF, "the receiving router MUST NOT check the Interface MTU in the Database Description packet against the receiving interface's IPv6 MTU" (§2.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-2.7-11, so no unit is bound to it. ### [`RFC5838-2.8-1`](#rfc5838-2.8-1) For a virtual link "there MUST be a global IPv6 address associated with the virtual link so that OSPFv3 control packets are forwarded correctly by the intermediate hops" (§2.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestV6VirtualEndpointRequiresGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L474) | unit/verify | unproven | | positive | [`TestV6VirtualEndpointResolvesGlobalAddress`](https://github.com/ze-software/ze/blob/main/internal/plugins/ospf/virtual_link_test.go#L438) | unit/verify | unproven | ### [`RFC5838-4-1`](#rfc5838-4-1) When multiple OSPFv3 instances use the same interface "they all MUST use the same Security Association (SA)" (§4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-4-1, so no unit is bound to it. ### [`RFC5838-5-3`](#rfc5838-5-3) Before assignments in the 128-255 range "there MUST be a Standards Track RFC including an IANA Considerations section explicitly specifying the AF Instance IDs being assigned" (§5) Audit verdict: not audited: no reader has judged these tests No test carries RFC5838-5-3, so no unit is bound to it. ## Extraction sign-off No extraction sign-off exists for RFC 5838, so no reviewer has walked its text sentence by sentence. ## Superseded No document obsoletes RFC 5838, so its obligations are stated where they were written. --- ### Page: RFC 5880 - Bidirectional Forwarding Detection (BFD) https://ze-software.net/quality/rfc-compliance/rfc5880/ # RFC 5880 - Bidirectional Forwarding Detection (BFD) Partial. Every requirement this repository extracted from RFC 5880, the tests bound to it, and what a reader has verified about them. This summary is enrolled and gated by ./le rfc check. ## Overview ### Positive what Ze has | Measure | Value | Count | What it means | |---|---:|---|---| | Tested both ways | 76.0% | 73 of 96 gated MUSTs | a positive test proves Ze does what the requirement demands and a negative one proves it refuses what the requirement forbids | | One polarity plus reason | 8.3% | 8 of 96 gated MUSTs | the requirement admits no counter-case, so one polarity plus a recorded reason is the whole proof available for it | | One polarity, unexcused | 0.0% | 0 of 96 gated MUSTs | one direction is tested, the other is neither tested nor excused, and nothing states which | | Proven by a recorded break | 0.0% | 0 of 160 tagged units, 0 escaped and 1 lapsed | a red was observed once under a recorded procedure, and the unit, the claim and the producer it rested on still hash to what was recorded. The break is not re-run. A test pair is not a proof until one has been observed | ### Neutral measures that are neither good news nor bad | Measure | Value | Count | What it means | |---|---:|---|---| | Gated MUSTs | 96 | of 119 this summary declares | MUST-level requirements the gate HOLDS. A population, not a result: the shares beside it are what says how Ze stands | | Out of scope | 1 | of 96 gated MUSTs | an obligation that does not bind Ze. A {not-applicable} annotation says it never bound; a {feature-declined} annotation says its condition is an optional feature Ze does not offer, and quotes the RFC sentence that makes it optional. Scope, not coverage: it stays in the denominator every share on this page is taken over | | Not applicable | 1.0% | 1 of 96 gated MUSTs | a {not-applicable} annotation says the obligation does not bind Ze, so no test is owed for it. It stays in the denominator every share here is taken over | | Met below Ze | 0.0% | 0 of 96 gated MUSTs | a {lower-layer} annotation says a layer under Ze performs the behavior, on state Ze installs into that layer, and names the producer that installs it. The obligation binds Ze and is met; Ze proves none of it, because its own boundary carries no value the behavior reads | | Optional feature declined | 0.0% | 0 of 96 gated MUSTs | a {feature-declined} annotation says the obligation is conditional on a feature the RFC makes optional and Ze does not offer, and it quotes the sentence that makes it optional. The condition is false, so nothing is owed and nothing is missing. It stays in the denominator every share here is taken over | ### Negative what Ze owes | Measure | Value | Count | What it means | |---|---:|---|---| | No test at all | 14.6% | 14 of 96 gated MUSTs | no test carries the requirement id, whether or not a gap states why | The 7 shares marked as a part above are the whole of the 96 gated MUSTs: they add to 100%. Proven by a recorded break is a share of TAGGED UNITS, a different population, so it is not one of them. A color names what the measure MEANS, not how well Ze scores on it. Green is a good outcome at any value, red is a bad one, and neither a population nor a scope count is an outcome, so both take no color. The number under the label is what says how far Ze has got. | Card | Tone here | Why that color | |---|---|---| | Gated MUSTs | neutral | no color: a population is a scale, and a larger one is neither good news nor bad. It is the accounting total | | Out of scope | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Tested both ways | ok | green at every value: a test pair is the outcome this gate exists to produce, and the share under the label is what says how far Ze has got | | One polarity plus reason | ok | green at every value: where no counter-case exists, one polarity IS the complete answer, and a recorded reason is what the gate demands beside it | | One polarity, unexcused | ok | green at zero, RED above it: half a proof with no reason for the other half | | No test at all | bad | green at zero, RED above it: a binding obligation nothing exercises is a claim with nothing behind it, whether or not a reason is stated | | Not applicable | neutral | no color: an obligation that never bound Ze is neither an achievement nor a failure, and counting it either way would be a claim | | Met below Ze | neutral | no color: an obligation met below Ze is neither a test Ze wrote nor work Ze owes, and the two green shares above are what says how much Ze proves itself | | Optional feature declined | neutral | no color: an obligation whose condition Ze never meets is neither an achievement nor a failure. The absent FEATURE is disclosed on the RFC's own status row, as an implementation gap a later scope decision can revisit | | Proven by a recorded break | ok | green at every value: an observed break is the outcome the discrimination gate exists to produce. The denominator is TAGGED UNITS, not obligations, so this share is not one of the parts above | | Audit verdicts | warn | RED on the first weak, wrong or unimplemented verdict, amber while a verdict is no longer current or a gated MUST is unjudged, green when every one is judged sound and current | ## At a glance | Field | Value | |---|---| | Public status | Partial | | Enrolment | Enrolled | | Requirements | 119 | | Gated MUST-level | 96 | | Not applicable, so out of scope | 1 | | Declared gaps | 14 | | Gated with no test | 0 | | Nightly-only evidence | 0 | | Test tags | 160 | | Tagged units | 160 | | Recorded audit verdicts | 0 | | Discrimination records | 1 | | Summary | `rfc/short/rfc5880.md` | | Requirement shard | `rfc/requirements/rfc5880.md` | | RFC text | `rfc/full/rfc5880.txt` | ## Enrolment Enrolled: Bidirectional Forwarding Detection (BFD) ## What the public ledger says **Status:** Partial **What the ledger says is covered** - 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`](https://github.com/ze-software/ze/blob/main/rfc/requirements/rfc5880.md). **What the ledger says remains** Fourteen MUST gaps, gated in [`rfc/short/rfc5880.md`](https://github.com/ze-software/ze/blob/main/rfc/short/rfc5880.md): 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`](#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`](#rfc5880-6.8.7-6)); the two timing changes the RFC excepts from immediate effect are applied immediately instead of at Poll termination ([`RFC5880-6.8.3-3`](#rfc5880-6.8.3-3), 6.8.3-4); bfd.XmitAuthSeq starts at zero rather than a random value and bfd.AuthSeqKnown is never cleared after twice the Detection Time ([`RFC5880-6.8.1-11`](#rfc5880-6.8.1-11), 6.8.1-13); the meticulous replay check accepts any strictly greater sequence rather than exactly RcvAuthSeq+1 ([`RFC5880-6.7.3-10`](#rfc5880-6.7.3-10)); there is no forwarding-plane-reset hook, so diagnostic 4 has no producer ([`RFC5880-6.8.15-1`](#rfc5880-6.8.15-1)); and no congestion-control mechanism governs the transmit rate ([`RFC5880-7-1`](#rfc5880-7-1), 7-2). Final-packet rate limiting is absent by design and annotated not-applicable. IPv6 transport coverage is tracked with BFD. ## Coverage | Bucket | Count | What it counts | |---|---|---| | Positive and negative tests | 73 | one part of the gated population | | Annotated instead of tested | 23 | one part of the gated population | | One polarity only | 0 | one part of the gated population | | No test and no annotation | 0 | one part of the gated population | | Evidence that runs nightly only | 0 | an overlay: each of these is also counted by the part it falls in | | **Gated MUST-level requirements** | **96** | every gated MUST falls in exactly one bucket above | **Positive and negative tests (73):** [`RFC5880-4.1-1`](#rfc5880-4.1-1), [`RFC5880-4.1-2`](#rfc5880-4.1-2), [`RFC5880-6.3-1`](#rfc5880-6.3-1), [`RFC5880-6.8.1-3`](#rfc5880-6.8.1-3), [`RFC5880-6.8.1-4`](#rfc5880-6.8.1-4), [`RFC5880-6.8.1-5`](#rfc5880-6.8.1-5), [`RFC5880-6.8.1-6`](#rfc5880-6.8.1-6), [`RFC5880-6.8.1-7`](#rfc5880-6.8.1-7), [`RFC5880-6.8.1-8`](#rfc5880-6.8.1-8), [`RFC5880-6.8.1-9`](#rfc5880-6.8.1-9), [`RFC5880-6.8.1-10`](#rfc5880-6.8.1-10), [`RFC5880-6.8.1-12`](#rfc5880-6.8.1-12), [`RFC5880-6.8.1-14`](#rfc5880-6.8.1-14), [`RFC5880-6.1-1`](#rfc5880-6.1-1), [`RFC5880-6.1-2`](#rfc5880-6.1-2), [`RFC5880-6.1-3`](#rfc5880-6.1-3), [`RFC5880-6.8.3-1`](#rfc5880-6.8.3-1), [`RFC5880-6.8.3-2`](#rfc5880-6.8.3-2), [`RFC5880-6.8.3-5`](#rfc5880-6.8.3-5), [`RFC5880-6.5-1`](#rfc5880-6.5-1), [`RFC5880-6.5-2`](#rfc5880-6.5-2), [`RFC5880-6.6-1`](#rfc5880-6.6-1), [`RFC5880-6.7-1`](#rfc5880-6.7-1), [`RFC5880-4.2-1`](#rfc5880-4.2-1), [`RFC5880-6.7.2-1`](#rfc5880-6.7.2-1), [`RFC5880-6.7.2-8`](#rfc5880-6.7.2-8), [`RFC5880-6.7.3-1`](#rfc5880-6.7.3-1), [`RFC5880-6.7.3-2`](#rfc5880-6.7.3-2), [`RFC5880-6.7.3-4`](#rfc5880-6.7.3-4), [`RFC5880-6.7.4-1`](#rfc5880-6.7.4-1), [`RFC5880-6.7.4-2`](#rfc5880-6.7.4-2), [`RFC5880-6.7.4-4`](#rfc5880-6.7.4-4), [`RFC5880-6.7.4-5`](#rfc5880-6.7.4-5), [`RFC5880-6.8.6-1`](#rfc5880-6.8.6-1), [`RFC5880-6.8.6-2`](#rfc5880-6.8.6-2), [`RFC5880-6.8.6-3`](#rfc5880-6.8.6-3), [`RFC5880-6.8.6-4`](#rfc5880-6.8.6-4), [`RFC5880-6.8.6-5`](#rfc5880-6.8.6-5), [`RFC5880-6.8.6-6`](#rfc5880-6.8.6-6), [`RFC5880-6.8.6-7`](#rfc5880-6.8.6-7), [`RFC5880-6.8.6-8`](#rfc5880-6.8.6-8), [`RFC5880-6.8.6-9`](#rfc5880-6.8.6-9), [`RFC5880-6.8.6-10`](#rfc5880-6.8.6-10), [`RFC5880-6.8.6-11`](#rfc5880-6.8.6-11), [`RFC5880-6.8.6-12`](#rfc5880-6.8.6-12), [`RFC5880-6.8.6-13`](#rfc5880-6.8.6-13), [`RFC5880-6.8.6-15`](#rfc5880-6.8.6-15), [`RFC5880-6.8.6-16`](#rfc5880-6.8.6-16), [`RFC5880-6.8.6-18`](#rfc5880-6.8.6-18), [`RFC5880-6.8.7-1`](#rfc5880-6.8.7-1), [`RFC5880-6.8.7-2`](#rfc5880-6.8.7-2), [`RFC5880-6.8.7-3`](#rfc5880-6.8.7-3), [`RFC5880-6.8.7-4`](#rfc5880-6.8.7-4), [`RFC5880-6.8.7-5`](#rfc5880-6.8.7-5), [`RFC5880-6.8.4-1`](#rfc5880-6.8.4-1), [`RFC5880-6.8.5-1`](#rfc5880-6.8.5-1), [`RFC5880-6.8.16-1`](#rfc5880-6.8.16-1), [`RFC5880-6.8.8-1`](#rfc5880-6.8.8-1), [`RFC5880-6.8.8-2`](#rfc5880-6.8.8-2), [`RFC5880-6.8.9-1`](#rfc5880-6.8.9-1), [`RFC5880-6.8.9-2`](#rfc5880-6.8.9-2), [`RFC5880-6.8.9-3`](#rfc5880-6.8.9-3), [`RFC5880-9-2`](#rfc5880-9-2), [`RFC5880-6.7.3-7`](#rfc5880-6.7.3-7), [`RFC5880-6.7.2-3`](#rfc5880-6.7.2-3), [`RFC5880-6.7.3-8`](#rfc5880-6.7.3-8), [`RFC5880-6.7.2-4`](#rfc5880-6.7.2-4), [`RFC5880-6.7.2-5`](#rfc5880-6.7.2-5), [`RFC5880-6.7.2-6`](#rfc5880-6.7.2-6), [`RFC5880-6.7.2-7`](#rfc5880-6.7.2-7), [`RFC5880-6.7.3-9`](#rfc5880-6.7.3-9), [`RFC5880-6.7.3-11`](#rfc5880-6.7.3-11), [`RFC5880-6.7.3-12`](#rfc5880-6.7.3-12) **Annotated instead of tested (23):** [`RFC5880-6.8.1-1`](#rfc5880-6.8.1-1), [`RFC5880-6.8.1-2`](#rfc5880-6.8.1-2), [`RFC5880-6.8.1-11`](#rfc5880-6.8.1-11), [`RFC5880-6.8.1-13`](#rfc5880-6.8.1-13), [`RFC5880-6.8.3-3`](#rfc5880-6.8.3-3), [`RFC5880-6.8.3-4`](#rfc5880-6.8.3-4), [`RFC5880-6.8.3-6`](#rfc5880-6.8.3-6), [`RFC5880-6.6-2`](#rfc5880-6.6-2), [`RFC5880-6.6-3`](#rfc5880-6.6-3), [`RFC5880-6.7.3-3`](#rfc5880-6.7.3-3), [`RFC5880-6.7.4-3`](#rfc5880-6.7.4-3), [`RFC5880-6.8.6-14`](#rfc5880-6.8.6-14), [`RFC5880-6.8.7-6`](#rfc5880-6.8.7-6), [`RFC5880-6.8.7-7`](#rfc5880-6.8.7-7), [`RFC5880-6.8.7-8`](#rfc5880-6.8.7-8), [`RFC5880-6.8.15-1`](#rfc5880-6.8.15-1), [`RFC5880-7-1`](#rfc5880-7-1), [`RFC5880-7-2`](#rfc5880-7-2), [`RFC5880-4.3-1`](#rfc5880-4.3-1), [`RFC5880-6.8.14-1`](#rfc5880-6.8.14-1), [`RFC5880-6.8.17-1`](#rfc5880-6.8.17-1), [`RFC5880-6.8.3-8`](#rfc5880-6.8.3-8), [`RFC5880-6.7.3-10`](#rfc5880-6.7.3-10) ## Requirements | Requirement | Text | Level | Section | Tests | |---|---|---|---|---| | `RFC5880-4.1-1` | Version field must be 1 (§4.1, §6.8.6) | MUST | 4.1 - Generic BFD Control Packet Format | **positive:** `unit/verify` [`TestRFC5880VersionOneAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L44). **negative:** `unit/verify` [`TestRFC5880VersionNotOneDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L64) | | `RFC5880-4.1-2` | Multipoint (M) bit must be zero on both transmit and receipt (§4.1) | MUST | 4.1 - Generic BFD Control Packet Format | **positive:** `unit/verify` [`TestRFC5880MultipointZeroAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L80). **negative:** `unit/verify` [`TestRFC5880MultipointSetDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L96) | | `RFC5880-6.3-1` | My Discriminator must be nonzero and unique across all BFD sessions on the system (§6.3) | MUST | 6.3 - Demultiplexing and the Discriminator Fields | **positive:** `unit/verify` [`TestRFC5880DiscriminatorsAreNonZeroAndUnique`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L62). **negative:** `unit/verify` [`TestRFC5880DiscriminatorAllocatorSkipsReservedAndTaken`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L90) | | `RFC5880-6.8.1-1` | bfd.SessionState must be initialized to Down (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880InitStatesAreDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L73). **negative:** no negative test. **{single-polarity}:** Init assigns bfd.SessionState = Down unconditionally (internal/component/bfd/session/session.go:246) before any packet can be exchanged, so no non-conformant input exists to reject | | `RFC5880-6.8.1-2` | bfd.RemoteSessionState must be initialized to Down (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880InitStatesAreDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L76). **negative:** no negative test. **{single-polarity}:** Init assigns bfd.RemoteSessionState = Down unconditionally (internal/component/bfd/session/session.go:247), so there is no non-conformant input to reject | | `RFC5880-6.8.1-3` | bfd.LocalDiscr must be unique across all BFD sessions on the system, and nonzero (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880DiscriminatorsAreNonZeroAndUnique`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L67). **negative:** `unit/verify` [`TestRFC5880DiscriminatorAllocatorSkipsReservedAndTaken`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L95) | | `RFC5880-6.8.1-4` | bfd.RemoteDiscr must be initialized to zero (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880InitVariableDefaults`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L88). **negative:** `unit/verify` [`TestRFC5880InitVariablesAreNotConstants`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L113) | | `RFC5880-6.8.1-5` | bfd.RemoteDiscr must be set to zero if no valid packet received for one Detection Time (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880RemoteDiscrClearedOnDetectionExpiry`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L153). **negative:** `unit/verify` [`TestRFC5880RemoteDiscrKeptOnNeighborSignaledDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L180) | | `RFC5880-6.8.1-6` | bfd.LocalDiag must be initialized to zero (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880InitVariableDefaults`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L90). **negative:** `unit/verify` [`TestRFC5880InitVariablesAreNotConstants`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L116) | | `RFC5880-6.8.1-7` | bfd.DesiredMinTxInterval must be initialized to at least 1,000,000 microseconds (§6.8.1, §6.8.3) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880SlowStartFloorWhileNotUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L208). **negative:** `unit/verify` [`TestRFC5880SlowStartFloorLiftedWhenUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L228) | | `RFC5880-6.8.1-8` | bfd.RemoteMinRxInterval must be initialized to 1 (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880InitVariableDefaults`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L92). **negative:** `unit/verify` [`TestRFC5880InitVariablesAreNotConstants`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L118) | | `RFC5880-6.8.1-9` | bfd.RemoteDemandMode must be initialized to zero (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880InitVariableDefaults`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L94). **negative:** `unit/verify` [`TestRFC5880InitVariablesAreNotConstants`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L121) | | `RFC5880-6.8.1-10` | bfd.DetectMult must be a nonzero integer (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880DetectMultConfiguredValue`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L248). **negative:** `unit/verify` [`TestRFC5880DetectMultZeroRequestSubstituted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L259) | | `RFC5880-6.8.1-11` | bfd.XmitAuthSeq must be initialized to a random 32-bit value (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** no positive test. **negative:** no negative test. **{gap}:** SetAuth seeds bfd.XmitAuthSeq from the persister or leaves the Vars zero value (internal/component/bfd/session/auth.go:36) and Init never randomizes it (internal/component/bfd/session/session.go:245-259), so the initial transmit sequence is 0 rather than a random 32-bit value | | `RFC5880-6.8.1-12` | bfd.AuthSeqKnown must be initialized to zero (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880AuthSeqKnownStartsZero`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L418). **negative:** `unit/verify` [`TestRFC5880AuthSeqKnownSetAfterFirstPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L436) | | `RFC5880-6.8.1-13` | bfd.AuthSeqKnown must be set to zero after no packets received for twice the Detection Time (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** no positive test. **negative:** no negative test. **{gap}:** bfd.AuthSeqKnown is SeqState.initialized, which only Advance sets (internal/component/bfd/auth/meticulous.go:63-66) and nothing ever clears; CheckDetection clears the detection deadline alone (internal/component/bfd/session/timers.go:82), so the flag survives twice the Detection Time of silence | | `RFC5880-6.8.1-14` | Session state must be preserved for at least one Detection Time after last valid packet (§6.8.1) | MUST | 6.8.1 - State Variables | **positive:** `unit/verify` [`TestRFC5880StatePreservedForOneDetectionTime`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L283). **negative:** `unit/verify` [`TestRFC5880DetectionExpiryDownDiagOne`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L308) | | `RFC5880-6.1-1` | Active role system must send BFD Control packets regardless of whether packets have been received (§6.1) | MUST | 6.1 - Overview | **positive:** `unit/verify` [`TestRFC5880ActiveRoleTransmitsWithoutReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L361). **negative:** `unit/verify` [`TestRFC5880PassiveRoleTransmitsAfterReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L410) | | `RFC5880-6.1-2` | Passive role system must not begin sending BFD packets until it has received one (§6.1) | MUST NOT | 6.1 - Overview | **positive:** `unit/verify` [`TestRFC5880PassiveRoleSilentUntilReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L386). **negative:** `unit/verify` [`TestRFC5880PassiveRoleTransmitsAfterReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L415) | | `RFC5880-6.1-3` | At least one system must take the Active role (§6.1) | MUST | 6.1 - Overview | **positive:** `unit/verify` [`TestRFC5880ActiveRoleTransmitsWithoutReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L366). **negative:** `unit/verify` [`TestRFC5880PassiveRoleSilentUntilReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L394) | | `RFC5880-6.8.3-1` | When session state is not Up, bfd.DesiredMinTxInterval must be at least 1,000,000 microseconds (§6.8.3) | MUST | 6.8.3 - Timer Manipulation | **positive:** `unit/verify` [`TestRFC5880SlowStartFloorWhileNotUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L213). **negative:** `unit/verify` [`TestRFC5880SlowStartFloorLiftedWhenUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L232) | | `RFC5880-6.8.3-2` | If bfd.DesiredMinTxInterval or bfd.RequiredMinRxInterval changes, a Poll Sequence must be initiated (§6.8.3) | MUST | 6.8.3 - Timer Manipulation | **positive:** `unit/verify` [`TestRFC5880PollInitiatedOnIntervalChange`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L436). **negative:** `unit/verify` [`TestRFC5880NoPollWhenIntervalsUnchanged`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L470) | | `RFC5880-6.8.3-3` | If bfd.DesiredMinTxInterval is increased while Up, the actual TX interval must not change until the Poll Sequence terminates (§6.8.3) | MUST NOT | 6.8.3 - Timer Manipulation | **positive:** no positive test. **negative:** no negative test. **{gap}:** ApplyEchoSlowdown raises bfd.DesiredMinTxInterval while the session is Up (internal/component/bfd/session/timers.go:235) and TransmitInterval returns the raised value on the very next call (internal/component/bfd/session/timers.go:44), so the longer interval takes effect before the Poll Sequence terminates | | `RFC5880-6.8.3-4` | If bfd.RequiredMinRxInterval is reduced while Up, the previous value must be used for detection time until the Poll terminates (§6.8.3) | MUST | 6.8.3 - Timer Manipulation | **positive:** no positive test. **negative:** no negative test. **{gap}:** revertEchoSlowdownLocked reduces bfd.RequiredMinRxInterval while Up (internal/component/bfd/session/timers.go:249) and DetectionInterval immediately uses the reduced value (internal/component/bfd/session/timers.go:29); no previous value is retained until the Poll Sequence terminates | | `RFC5880-6.8.3-5` | If local system reduces TX interval due to bfd.RemoteMinRxInterval being reduced, it must honor the new interval immediately (§6.8.3) | MUST | 6.8.3 - Timer Manipulation | **positive:** `unit/verify` [`TestRFC5880RemoteMinRxReductionHonoredImmediately`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L497). **negative:** `unit/verify` [`TestRFC5880TransmitIntervalFlooredByLocalDesired`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L533) | | `RFC5880-6.8.3-6` | Multiple parameter changes requiring Poll Sequence must be communicated in a single packet, or a round-trip must elapse, or an F=0 packet must be received before starting another Poll (§6.8.3) | MUST | 6.8.3 - Timer Manipulation | **positive:** `unit/verify` [`TestRFC5880PollInitiatedOnIntervalChange`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L441). **negative:** no negative test. **{single-polarity}:** onStateChange applies both interval changes and raises exactly one Poll (internal/component/bfd/session/fsm.go:139-144), so ze always takes the single-packet option and no overlapping second Poll Sequence exists to reject | | `RFC5880-6.5-1` | A BFD Control packet must not have both Poll (P) and Final (F) bits set (§6.5) | MUST NOT | 6.5 - The Poll Sequence | **positive:** `unit/verify` [`TestRFC5880PollPacketHasNoFinalBit`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L567). **negative:** `unit/verify` [`TestRFC5880FinalReplyClearsPollBit`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L586) | | `RFC5880-6.5-2` | If periodic Control packets are being sent, Poll Sequence must be performed by setting P bit on scheduled transmissions; additional packets must not be sent (§6.5) | MUST | 6.5 - The Poll Sequence | **positive:** `unit/verify` [`TestRFC5880PollPacketHasNoFinalBit`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L571). **negative:** `unit/verify` [`TestRFC5880FinalReplyClearsPollBit`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L590) | | `RFC5880-6.6-1` | Demand (D) bit must not be set unless bfd.DemandMode is 1, bfd.SessionState is Up, and bfd.RemoteSessionState is Up (§6.6, §6.8.7) | MUST NOT | 6.6 - Demand mode | **positive:** `unit/verify` [`TestRFC5880DemandBitSetWhenAllConditionsHold`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L652). **negative:** `unit/verify` [`TestRFC5880DemandBitClearWhenAnyConditionFails`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L666) | | `RFC5880-6.6-2` | When the D bit value is to be changed, a Poll Sequence must be initiated (§6.6) | MUST | 6.6 - Demand mode | **positive:** no positive test. **negative:** no negative test. **{gap}:** bfd.DemandMode has no writer in production code -- it is declared at internal/component/bfd/session/session.go:58 and read only by canSetDemand (internal/component/bfd/session/fsm.go:247) -- so no D-bit change path exists and none initiates a Poll Sequence | | `RFC5880-6.6-3` | If Demand mode is active on either system, a Poll Sequence must be initiated whenever next packet contents would differ (except P/F bits) (§6.6) | MUST | 6.6 - Demand mode | **positive:** no positive test. **negative:** no negative test. **{gap}:** bfd.RemoteDemandMode is stored by Receive (internal/component/bfd/session/fsm.go:58) and read nowhere, and bfd.DemandMode has no writer (internal/component/bfd/session/session.go:58), so no code path starts a Poll when packet contents would change while Demand mode is active | | `RFC5880-6.7-1` | Implementations supporting authentication must support both types of SHA1 authentication (§6.7) | MUST | 6.7 - Authentication | **positive:** `unit/verify` [`TestRFC5880BothSHA1VariantsSupported`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L48). **negative:** `unit/verify` [`TestRFC5880UnsupportedAuthTypesRejected`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L67) | | `RFC5880-4.2-1` | Simple Password must be 1 to 16 bytes in length (§4.2, §6.7.2) | MUST | 4.2 - Simple Password Authentication Section Format | **positive:** `unit/verify` [`TestRFC5880SimplePasswordManagementAcceptsASCIIStrings`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L101). **positive:** `unit/verify` [`TestRFC5880SimplePasswordSectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L589). **negative:** `unit/verify` [`auth/TestRFC5880SimplePasswordLengthOutOfRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L744). **negative:** `unit/verify` [`bfd/TestRFC5880SimplePasswordLengthOutOfRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L128) | | `RFC5880-6.7.2-1` | Simple Password management interface must accept ASCII strings (§6.7.2) | MUST | 6.7.2 - Simple Password Authentication | **positive:** `unit/verify` [`TestRFC5880SimplePasswordManagementAcceptsASCIIStrings`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L96). **negative:** `unit/verify` [`bfd/TestRFC5880SimplePasswordLengthOutOfRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L132) | | `RFC5880-6.7.2-8` | Simple Password Auth Type must be set to 1, Auth Len must be set to the proper length of 4 to 19 bytes (§6.7.2) | MUST | 6.7.2 - Simple Password Authentication | **positive:** `unit/verify` [`TestRFC5880SimplePasswordSectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L584). **negative:** `unit/verify` [`TestRFC5880SimplePasswordRejectsForeignSectionShape`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L610) | | `RFC5880-6.7.3-1` | Keyed MD5 Auth Type must be set to 2 or 3, Auth Len must be 24 (§6.7.3) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880KeyedMD5SectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L83). **negative:** `unit/verify` [`TestRFC5880KeyedMD5RejectsForeignSectionShape`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L110) | | `RFC5880-6.7.3-2` | Keyed MD5 Auth Key/Digest: MD5 digest must be calculated over entire BFD Control packet (§6.7.3) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880DigestCoversWholePacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L179). **negative:** `unit/verify` [`TestRFC5880DigestRejectsMandatorySectionTamper`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L198) | | `RFC5880-6.7.3-3` | Keyed MD5: secret key must not be carried in the packet (§6.7.3) | MUST NOT | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880SecretKeyNotCarriedInPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L222). **negative:** no negative test. **{single-polarity}:** Sign overwrites the key scratch with the computed digest before the packet is handed to the transport (internal/component/bfd/auth/sha1.go:85-87), so no key-bearing packet is ever emitted for a receiver to reject | | `RFC5880-6.7.3-4` | Meticulous Keyed MD5: bfd.XmitAuthSeq must be incremented for each packet (§6.7.3) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880MeticulousSequenceIncrementsPerPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L287). **negative:** `unit/verify` [`TestRFC5880MeticulousRejectsUnincrementedSequence`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L536) | | `RFC5880-6.7.4-1` | Keyed SHA1 Auth Type must be set to 4 or 5, Auth Len must be 28 (§6.7.4) | MUST | 6.7.4 - Keyed SHA1 and Meticulous Keyed SHA1 Authentication | **positive:** `unit/verify` [`TestRFC5880KeyedSHA1SectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L135). **negative:** `unit/verify` [`TestRFC5880KeyedSHA1RejectsForeignSectionShape`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L156) | | `RFC5880-6.7.4-2` | SHA1 hash must be calculated over entire BFD Control packet (§6.7.4) | MUST | 6.7.4 - Keyed SHA1 and Meticulous Keyed SHA1 Authentication | **positive:** `unit/verify` [`TestRFC5880DigestCoversWholePacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L185). **negative:** `unit/verify` [`TestRFC5880DigestRejectsMandatorySectionTamper`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L203) | | `RFC5880-6.7.4-3` | Keyed SHA1: secret key must not be carried in the packet (§6.7.4) | MUST NOT | 6.7.4 - Keyed SHA1 and Meticulous Keyed SHA1 Authentication | **positive:** `unit/verify` [`TestRFC5880SecretKeyNotCarriedInPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L226). **negative:** no negative test. **{single-polarity}:** the SHA1 signer shares that producer with a 20-byte digest slot (internal/component/bfd/auth/sha1.go:85-87,193-195), so no key-bearing packet is emitted | | `RFC5880-6.7.4-4` | Meticulous Keyed SHA1: bfd.XmitAuthSeq must be incremented for each packet (§6.7.4) | MUST | 6.7.4 - Keyed SHA1 and Meticulous Keyed SHA1 Authentication | **positive:** `unit/verify` [`TestRFC5880MeticulousSequenceIncrementsPerPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L293). **negative:** `unit/verify` [`TestRFC5880MeticulousRejectsUnincrementedSequence`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L541) | | `RFC5880-6.7.4-5` | SHA1 key management interface must accept ASCII strings (§6.7.4) | MUST | 6.7.4 - Keyed SHA1 and Meticulous Keyed SHA1 Authentication | **positive:** `unit/verify` [`TestRFC5880KeyManagementAcceptsASCIIStrings`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L31). **negative:** `unit/verify` [`TestRFC5880KeyManagementRejectsIncompleteConfig`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L70) | | `RFC5880-6.8.6-1` | Reception: discard if Version != 1 (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880VersionOneAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L48). **negative:** `unit/verify` [`TestRFC5880VersionNotOneDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L66) | | `RFC5880-6.8.6-2` | Reception: discard if Length < 24 (A=0) or < 26 (A=1) (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880LengthMinimumAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L108). **negative:** `unit/verify` [`TestRFC5880LengthBelowMinimumDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L128) | | `RFC5880-6.8.6-3` | Reception: discard if Length > encapsulating payload (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880LengthEqualsPayloadAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L148). **negative:** `unit/verify` [`TestRFC5880LengthOverPayloadDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L163) | | `RFC5880-6.8.6-4` | Reception: discard if Detect Mult == 0 (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880DetectMultNonZeroAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L174). **negative:** `unit/verify` [`TestRFC5880DetectMultZeroDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L190) | | `RFC5880-6.8.6-5` | Reception: discard if M bit is nonzero (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880MultipointZeroAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L84). **negative:** `unit/verify` [`TestRFC5880MultipointSetDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L99) | | `RFC5880-6.8.6-6` | Reception: discard if My Discriminator == 0 (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880MyDiscriminatorNonZeroAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L200). **negative:** `unit/verify` [`TestRFC5880MyDiscriminatorZeroDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L214) | | `RFC5880-6.8.6-7` | Reception: if Your Discriminator nonzero, use it to select session; discard if no session found (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880YourDiscriminatorSelectsSession`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L124). **negative:** `unit/verify` [`TestRFC5880UnknownYourDiscriminatorDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L140) | | `RFC5880-6.8.6-8` | Reception: if Your Discriminator zero and State is not Down or AdminDown, discard (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880ZeroYourDiscriminatorAcceptedWhenDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L724). **negative:** `unit/verify` [`TestRFC5880ZeroYourDiscriminatorDiscardedWhenLive`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L699) | | `RFC5880-6.8.6-9` | Reception: if A=1 and bfd.AuthType is zero, discard (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880UnauthenticatedSessionAcceptsUnauthenticatedPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L746). **negative:** `unit/verify` [`TestRFC5880UnauthenticatedSessionDiscardsAuthenticatedPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L761) | | `RFC5880-6.8.6-10` | Reception: if A=0 and bfd.AuthType is nonzero, discard (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880AuthenticatedSessionAcceptsAuthenticatedPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L778). **negative:** `unit/verify` [`TestRFC5880AuthenticatedSessionDiscardsUnauthenticatedPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L796) | | `RFC5880-6.8.6-11` | Reception: if A=1, authenticate per §6.7 (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880AuthenticatedPacketVerifiedAndDelivered`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L241). **negative:** `unit/verify` [`TestRFC5880UnauthenticPacketDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L258) | | `RFC5880-6.8.6-12` | Reception: if Required Min Echo RX Interval == 0, cease Echo transmission (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880EchoCeasesWhenPeerAdvertisesZero`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L812). **negative:** `unit/verify` [`TestRFC5880EchoEnabledWhenPeerAdvertisesNonZero`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L849) | | `RFC5880-6.8.6-13` | Reception: if Poll in flight and F=1 received, terminate the Poll Sequence (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880FinalTerminatesPoll`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L610). **negative:** `unit/verify` [`TestRFC5880NonFinalDoesNotTerminatePoll`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L630) | | `RFC5880-6.8.6-14` | Reception: if remote Demand mode active (D=1, both Up), cease periodic Control packet transmission (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** no positive test. **negative:** no negative test. **{gap}:** tick transmits whenever the periodic deadline has passed and never consults the remote Demand state (internal/component/bfd/engine/loop.go:192-201), so periodic Control packets continue after the peer sets D=1 | | `RFC5880-6.8.6-15` | Reception: if remote Demand mode not active, send periodic Control packets (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880PeriodicTransmitWhenRemoteDemandInactive`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L368). **negative:** `unit/verify` [`TestRFC5880NoPeriodicTransmitWhileAdminDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L403) | | `RFC5880-6.8.6-16` | Reception: if P=1, send Final packet immediately (§6.8.6, §6.8.7) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880PollAnsweredWithImmediateFinal`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L322). **negative:** `unit/verify` [`TestRFC5880NonPollProducesNoImmediateReply`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L352) | | `RFC5880-6.8.6-18` | Reception: if Your Discriminator zero, select the session on a combination of other fields, which can include source addressing information, My Discriminator, and the ingress interface (§6.8.6) | MUST | 6.8.6 - Reception of BFD Control Packets | **positive:** `unit/verify` [`TestFirstPacketMatchesWhatTheTransportSurfaces`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5881_test.go#L356). **positive:** `unit/verify` [`TestRFC5881FirstPacketMatchesByTuple`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5881_test.go#L128). **negative:** `unit/verify` [`TestRFC5881FirstPacketWrongSourceDropped`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5881_test.go#L151) | | `RFC5880-6.8.7-1` | Transmission: must not transmit at interval less than max(bfd.DesiredMinTxInterval, bfd.RemoteMinRxInterval) less jitter (§6.8.7) | MUST NOT | 6.8.7 - Transmitting BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880TransmitDeadlineUsesNegotiatedInterval`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1015). **negative:** `unit/verify` [`TestRFC5880TransmitDeadlineClampsBadJitter`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1045) | | `RFC5880-6.8.7-2` | Transmission: periodic TX must be jittered by 0-25% per packet (§6.8.7) | MUST | 6.8.7 - Transmitting BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880JitterIsAppliedPerPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L422). **negative:** `unit/verify` [`TestRFC5880JitterStaysWithinBand`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L450) | | `RFC5880-6.8.7-3` | Transmission: if bfd.DetectMult == 1, interval must be 75-90% of negotiated interval (§6.8.7) | MUST | 6.8.7 - Transmitting BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880JitterDetectMultOneWindow`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L470). **negative:** `unit/verify` [`TestRFC5880JitterFloorOnlyForDetectMultOne`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L488) | | `RFC5880-6.8.7-4` | Transmission: TX interval must be recalculated whenever bfd.DesiredMinTxInterval or bfd.RemoteMinRxInterval changes (§6.8.7) | MUST | 6.8.7 - Transmitting BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880RemoteMinRxReductionHonoredImmediately`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L504). **negative:** `unit/verify` [`TestRFC5880TransmitIntervalFlooredByLocalDesired`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L538) | | `RFC5880-6.8.7-5` | Transmission: must not transmit if bfd.RemoteDiscr is zero and system is Passive (§6.8.7) | MUST NOT | 6.8.7 - Transmitting BFD Control Packets | **positive:** `unit/verify` [`TestRFC5880PassiveRoleSilentUntilReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L391). **negative:** `unit/verify` [`TestRFC5880ActiveRoleTransmitsWithoutReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L369) | | `RFC5880-6.8.7-6` | Transmission: must not periodically transmit if bfd.RemoteMinRxInterval is zero (§6.8.7) | MUST NOT | 6.8.7 - Transmitting BFD Control Packets | **positive:** no positive test. **negative:** no negative test. **{gap}:** TransmitInterval substitutes the slow-start interval when the negotiated maximum is zero (internal/component/bfd/session/timers.go:45-47) and tick carries no bfd.RemoteMinRxInterval == 0 suppression (internal/component/bfd/engine/loop.go:192-201), so periodic transmission continues | | `RFC5880-6.8.7-7` | Transmission: must not periodically transmit if remote Demand mode active and no Poll in flight (§6.8.7) | MUST NOT | 6.8.7 - Transmitting BFD Control Packets | **positive:** no positive test. **negative:** no negative test. **{gap}:** the same tick producer (internal/component/bfd/engine/loop.go:192-201) has no remote-Demand suppression, so periodic transmission continues while the remote Demand mode is active with no Poll in flight | | `RFC5880-6.8.7-8` | If rate limiting Final packets, advertised Desired Min TX Interval must be >= rate-limit interval (§6.8.7) | MUST | 6.8.7 - Transmitting BFD Control Packets | **positive:** no positive test. **negative:** no negative test. **{not-applicable}:** ze does not rate-limit Final packets -- handleInbound sends the Final unconditionally on every received Poll (internal/component/bfd/engine/loop.go:140-142) and no rate limiter exists on that path, so the conditional advertised-interval floor never applies | | `RFC5880-6.8.4-1` | Detection time expired while Init or Up: set bfd.SessionState to Down, bfd.LocalDiag to 1 (§6.8.4) | MUST | 6.8.4 - Calculating the Detection Time | **positive:** `unit/verify` [`TestRFC5880DetectionExpiryDownDiagOne`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L312). **negative:** `unit/verify` [`TestRFC5880DetectionExpiryIgnoredWhenDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L333) | | `RFC5880-6.8.5-1` | Echo function failure: set bfd.SessionState to Down, bfd.LocalDiag to 2 (§6.8.5) | MUST | 6.8.5 - Detecting Failures with the Echo Function | **positive:** `unit/verify` [`TestRFC5880EchoMissDetectedAndReported`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L943). **negative:** `unit/verify` [`TestRFC5880EchoReturnClearsMissAndFailIsScoped`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L979) | | `RFC5880-6.8.15-1` | Forwarding plane reset: set bfd.LocalDiag to 4, bfd.SessionState to Down (§6.8.15) | MUST | 6.8.15 - Forwarding Plane Reset | **positive:** no positive test. **negative:** no negative test. **{gap}:** packet.DiagForwardingPlaneReset (internal/component/bfd/packet/diag.go:21) has no producer, and the only external state-forcing entry points are AdminDown and AdminEnable (internal/component/bfd/session/fsm.go:180,192), which move the session to AdminDown or Down carrying the caller's diagnostic and are never called with code 4 | | `RFC5880-6.8.16-1` | Administrative control: follow the enable/disable procedure (§6.8.16) | MUST | 6.8.16 - Administrative Control | **positive:** `unit/verify` [`TestRFC5880AdministrativeDisableEnable`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1071). **negative:** `unit/verify` [`TestRFC5880AdministrativeCallsAreGuarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1106) | | `RFC5880-6.8.8-1` | Echo packets must be demultiplexed to the appropriate session (§6.8.8) | MUST | 6.8.8 - Reception of BFD Echo Packets | **positive:** `unit/verify` [`TestRFC5880EchoDemultiplexedToItsSession`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L589). **negative:** `unit/verify` [`TestRFC5880UnknownEchoDropped`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L626) | | `RFC5880-6.8.8-2` | A means of detecting missing Echo packets must be implemented (§6.8.8) | MUST | 6.8.8 - Reception of BFD Echo Packets | **positive:** `unit/verify` [`TestRFC5880EchoMissDetectedAndReported`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L938). **negative:** `unit/verify` [`TestRFC5880EchoReturnClearsMissAndFailIsScoped`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L974) | | `RFC5880-6.8.9-1` | Echo packets must not be transmitted when bfd.SessionState is not Up (§6.8.9) | MUST NOT | 6.8.9 - Transmission of BFD Echo Packets | **positive:** `unit/verify` [`TestRFC5880EchoTransmittedWhileUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L543). **negative:** `unit/verify` [`TestRFC5880NoEchoTransmittedWhenNotUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L564) | | `RFC5880-6.8.9-2` | Echo packets must not be transmitted unless remote Required Min Echo RX Interval is nonzero (§6.8.9) | MUST NOT | 6.8.9 - Transmission of BFD Echo Packets | **positive:** `unit/verify` [`TestRFC5880EchoEnabledWhenPeerAdvertisesNonZero`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L852). **negative:** `unit/verify` [`TestRFC5880EchoNotTransmittedWithoutPeerAdvertisement`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L873) | | `RFC5880-6.8.9-3` | Echo packet TX interval must not be less than remote Required Min Echo RX Interval (§6.8.9) | MUST NOT | 6.8.9 - Transmission of BFD Echo Packets | **positive:** `unit/verify` [`TestRFC5880EchoIntervalHonorsPeerFloor`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L896). **negative:** `unit/verify` [`TestRFC5880EchoIntervalNotBelowLocalTarget`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L915) | | `RFC5880-7-1` | Multihop: a congestion control mechanism must be implemented (§7) | MUST | 7 - Operational Considerations | **positive:** no positive test. **negative:** no negative test. **{gap}:** the transmit rate is derived solely from TransmitInterval plus jitter (internal/component/bfd/engine/loop.go:197-200) with no congestion-feedback input, and no congestion-control producer exists anywhere under internal/component/bfd | | `RFC5880-7-2` | Multihop: when congestion detected, BFD must reduce traffic generated (§7) | MUST | 7 - Operational Considerations | **positive:** no positive test. **negative:** no negative test. **{gap}:** the same rate producer (internal/component/bfd/engine/loop.go:197-200) is the only authority over generated traffic and nothing reduces it in response to detected congestion | | `RFC5880-4.3-1` | Reserved byte in MD5/SHA1 auth section must be set to zero on transmit (§4.3, §4.4) | MUST | 4.3 | **positive:** `unit/verify` [`TestRFC5880KeyedMD5SectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L88). **negative:** no negative test. **{single-polarity}:** Sign hardcodes the Reserved byte to 0 (internal/component/bfd/auth/sha1.go:83) and no code path emits a non-zero value, so there is no non-conformant transmission to reject | | `RFC5880-6.8.1-15` | bfd.LocalDiscr should be set to a random value to improve security (§6.8.1) | SHOULD | 6.8.1 - State Variables | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.4-1` | Advertise the lowest values of Required Min RX Interval and Required Min Echo RX Interval possible (§6.4) | SHOULD | 6.4 - The Echo Function and Asymmetry | **positive:** no positive test. **negative:** no negative test | | `RFC5880-5-1` | Echo packets should include some form of authentication (§5) | SHOULD | 5 - BFD Echo Packet Format | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.3-7` | When Echo function is active, set bfd.RequiredMinRxInterval to at least 1,000,000 microseconds (§6.8.3) | SHOULD | 6.8.3 - Timer Manipulation | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.7-9` | A non-periodic Control packet should be sent when content would change (§6.8.7) | SHOULD | 6.8.7 - Transmitting BFD Control Packets | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.7.1-1` | Authentication state change should only be allowed at most once without intervention (§6.7.1) | SHOULD | 6.7.1 - Enabling and Disabling Authentication | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.7.1-2` | Authentication state should not change based on receipt of BFD Control packets (§6.7.1) | SHOULD NOT | 6.7.1 - Enabling and Disabling Authentication | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.7.2-2` | Simple Password and key management should also allow hexadecimal binary string configuration (§6.7.2, §6.7.3, §6.7.4) | SHOULD | 6.7.2 - Simple Password Authentication | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.7.3-5` | Keyed MD5/SHA1: bfd.XmitAuthSeq should be incremented on state change or content change (§6.7.3, §6.7.4) | SHOULD | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.16-2` | BFD Control packets should be transmitted for at least one Detection Time after transitioning to AdminDown (§6.8.16) | SHOULD | 6.8.16 - Administrative Control | **positive:** no positive test. **negative:** no negative test | | `RFC5880-7-3` | Multihop: congestion control algorithm should be used even across single hops (§7) | SHOULD | 7 - Operational Considerations | **positive:** no positive test. **negative:** no negative test | | `RFC5880-9-1` | Multihop: if run across multiple hops or insecure tunnel, Authentication Section should be utilized (§9) | SHOULD | 9 - Security Considerations | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.18-1` | If no BFD Control packets received for a Detection Time, remote system should reset bfd.RemoteMinRxInterval to 1 (§6.8.18) | SHOULD | 6.8.18 - Holding Down Sessions | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.2-1` | A session may be kept administratively down (§6.2) | MAY | 6.2 - BFD State Machine | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.3-2` | A system may change its discriminator during a session (§6.3) | MAY | 6.3 - Demultiplexing and the Discriminator Fields | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.6-17` | When Your Discriminator is zero, a new session may be created or the packet may be discarded (§6.8.6) | MAY | 6.8.6 - Reception of BFD Control Packets | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.7-10` | Final packets may be rate-limited (§6.8.7) | MAY | 6.8.7 - Transmitting BFD Control Packets | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.6-4` | Demand mode may be enabled or disabled at any time (§6.6) | MAY | 6.6 - Demand mode | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.13-1` | Echo function may be enabled or disabled at any time (§6.8.13) | MAY | 6.8.13 - Enabling or Disabling The Echo Function | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.7.3-6` | Keyed MD5: bfd.XmitAuthSeq may be incremented in a circular fashion (§6.7.3) | MAY | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.7.4-6` | Keyed SHA1: bfd.XmitAuthSeq may be incremented in a circular fashion (§6.7.4) | MAY | 6.7.4 - Keyed SHA1 and Meticulous Keyed SHA1 Authentication | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.1-16` | Session state may be preserved longer than one Detection Time (§6.8.1) | MAY | 6.8.1 - State Variables | **positive:** no positive test. **negative:** no negative test | | `RFC5880-6.8.16-3` | BFD Control packets may be transmitted indefinitely after transitioning to AdminDown (§6.8.16) | MAY | 6.8.16 - Administrative Control | **positive:** no positive test. **negative:** no negative test | | `RFC5880-9-2` | For single-hop sessions, TTL or Hop Count MUST be set to maximum on transmit and checked equal to maximum on receipt (§9) | MUST | 9 - Security Considerations | **positive:** `unit/verify` [`TestRFC5880SingleHopTransmitTTLIsMaximum`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/transport/rfc5880_test.go#L43). **negative:** `unit/verify` [`TestRFC5880SingleHopReceiveTTLNotMaxDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L160) | | `RFC5880-6.8.14-1` | If Demand mode is no longer active on the remote system, the local system MUST begin transmitting periodic BFD Control packets (§6.8.14) | MUST | 6.8.14 - Enabling or Disabling Demand Mode | **positive:** `unit/verify` [`TestRFC5880PeriodicTransmitWhenRemoteDemandInactive`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L372). **negative:** no negative test. **{single-polarity}:** tick transmits periodic Control packets without consulting the remote D bit (internal/component/bfd/engine/loop.go:186-201), so a remote that stops asserting Demand keeps receiving them and there is no suppressed state to leave | | `RFC5880-6.8.17-1` | If concatenated path failure diagnostic must be communicated and remote Demand mode is active, a Poll Sequence MUST be initiated (§6.8.17) | MUST | 6.8.17 - Concatenated Paths | **positive:** no positive test. **negative:** no negative test. **{gap}:** packet.DiagConcatPathDown (internal/component/bfd/packet/diag.go:23) has no producer and the remote Demand state stored at internal/component/bfd/session/fsm.go:58 is never read, so a concatenated-path failure neither sets the diagnostic nor initiates a Poll Sequence | | `RFC5880-6.8.3-8` | Timing parameter changes not explicitly excepted MUST be effected immediately (§6.8.3) | MUST | 6.8.3 - Timer Manipulation | **positive:** `unit/verify` [`TestRFC5880RemoteMinRxReductionHonoredImmediately`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L506). **negative:** no negative test. **{single-polarity}:** TransmitInterval and DetectionInterval are recomputed from the live bfd.* variables on every call (internal/component/bfd/session/timers.go:24-49), so an unexcepted timing change is in force at the next evaluation and no held-back value exists to reject | | `RFC5880-6.7.3-7` | MD5 key management interface MUST accept ASCII strings (§6.7.3) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880KeyManagementAcceptsASCIIStrings`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L26). **negative:** `unit/verify` [`TestRFC5880KeyManagementRejectsIncompleteConfig`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L64) | | `RFC5880-6.7.2-3` | Simple Password, MD5, and SHA1 Auth Key ID field MUST be set to the ID of the current authentication key (§6.7.2, §6.7.3, §6.7.4) | MUST | 6.7.2 - Simple Password Authentication | **positive:** `unit/verify` [`TestRFC5880AuthKeyIDIsTheConfiguredKey`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L246). **positive:** `unit/verify` [`TestRFC5880SimplePasswordSectionCarriesPasswordAndKeyID`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L639). **negative:** `unit/verify` [`TestRFC5880AuthKeyIDMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L264) | | `RFC5880-6.7.3-8` | Simple Password, MD5, and SHA1 Sequence Number field MUST be set to bfd.XmitAuthSeq (§6.7.3, §6.7.4) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880AuthSequenceFieldIsXmitAuthSeq`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1140). **negative:** `unit/verify` [`TestRFC5880AuthSequenceFieldFollowsAdvance`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1163) | | `RFC5880-6.7.2-4` | Reception: if Auth Type does not match bfd.AuthType, packet MUST be discarded (§6.7.2, §6.7.3, §6.7.4) | MUST | 6.7.2 - Simple Password Authentication | **positive:** `unit/verify` [`TestRFC5880AuthTypeMatchAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L297). **negative:** `unit/verify` [`TestRFC5880AuthTypeMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L310) | | `RFC5880-6.7.2-5` | Reception: if Auth Key ID does not match any configured authentication key, packet MUST be discarded (§6.7.2, §6.7.3, §6.7.4) | MUST | 6.7.2 - Simple Password Authentication | **positive:** `unit/verify` [`TestRFC5880AuthKeyIDMatchAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L285). **positive:** `unit/verify` [`TestRFC5880SimplePasswordMatchingKeyIDAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L729). **negative:** `unit/verify` [`TestRFC5880AuthKeyIDMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L267). **negative:** `unit/verify` [`TestRFC5880SimplePasswordWrongKeyIDDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L712) | | `RFC5880-6.7.2-6` | Reception: if Auth Len does not match expected length, packet MUST be discarded (§6.7.2, §6.7.3, §6.7.4) | MUST | 6.7.2 - Simple Password Authentication | **positive:** `unit/verify` [`TestRFC5880AuthLenExpectedAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L326). **negative:** `unit/verify` [`TestRFC5880AuthLenMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L343) | | `RFC5880-6.7.2-7` | Reception: if password does not match configured password for Simple Password, packet MUST be discarded (§6.7.2) | MUST | 6.7.2 - Simple Password Authentication | **positive:** `unit/verify` [`TestRFC5880SimplePasswordMatchAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L662). **negative:** `unit/verify` [`TestRFC5880SimplePasswordMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L681) | | `RFC5880-6.7.3-9` | Reception: for Keyed MD5/SHA1, if Sequence Number is less than bfd.RcvAuthSeq (accounting for wrap), packet MUST be discarded (§6.7.3, §6.7.4) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880KeyedSequenceAtOrAboveFloorAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L494). **negative:** `unit/verify` [`TestRFC5880KeyedSequenceBelowFloorDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L514) | | `RFC5880-6.7.3-10` | Reception: for Meticulous Keyed MD5/SHA1, if Sequence Number is not exactly bfd.RcvAuthSeq+1 (accounting for wrap), packet MUST be discarded (§6.7.3, §6.7.4) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** no positive test. **negative:** no negative test. **{gap}:** SeqState.Check accepts any sequence strictly greater than bfd.RcvAuthSeq for the meticulous variants (internal/component/bfd/auth/meticulous.go:47-51), so a packet whose sequence jumps past RcvAuthSeq+1 is accepted rather than discarded | | `RFC5880-6.7.3-11` | Reception: if digest/hash does not match computed value, packet MUST be discarded (§6.7.3, §6.7.4) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880DigestMatchAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L377). **negative:** `unit/verify` [`TestRFC5880DigestMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L390) | | `RFC5880-6.7.3-12` | Reception: if bfd.AuthSeqKnown is 0, it MUST be set to 1 and bfd.RcvAuthSeq MUST be set to received Sequence Number (§6.7.3, §6.7.4) | MUST | 6.7.3 - Keyed MD5 and Meticulous Keyed MD5 Authentication | **positive:** `unit/verify` [`TestRFC5880FirstAuthenticatedPacketSeedsReplayFloor`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L447). **negative:** `unit/verify` [`TestRFC5880ForgedFirstPacketDoesNotSeedReplayFloor`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L471) | ## Gaps and untested MUSTs | Requirement | State | Reason | |---|---|---| | [`RFC5880-6.8.1-11`](#rfc5880-6.8.1-11) bfd.XmitAuthSeq must be initialized to a random 32-bit value (§6.8.1) | {gap}, no test | SetAuth seeds bfd.XmitAuthSeq from the persister or leaves the Vars zero value (internal/component/bfd/session/auth.go:36) and Init never randomizes it (internal/component/bfd/session/session.go:245-259), so the initial transmit sequence is 0 rather than a random 32-bit value | | [`RFC5880-6.8.1-13`](#rfc5880-6.8.1-13) bfd.AuthSeqKnown must be set to zero after no packets received for twice the Detection Time (§6.8.1) | {gap}, no test | bfd.AuthSeqKnown is SeqState.initialized, which only Advance sets (internal/component/bfd/auth/meticulous.go:63-66) and nothing ever clears; CheckDetection clears the detection deadline alone (internal/component/bfd/session/timers.go:82), so the flag survives twice the Detection Time of silence | | [`RFC5880-6.8.3-3`](#rfc5880-6.8.3-3) If bfd.DesiredMinTxInterval is increased while Up, the actual TX interval must not change until the Poll Sequence terminates (§6.8.3) | {gap}, no test | ApplyEchoSlowdown raises bfd.DesiredMinTxInterval while the session is Up (internal/component/bfd/session/timers.go:235) and TransmitInterval returns the raised value on the very next call (internal/component/bfd/session/timers.go:44), so the longer interval takes effect before the Poll Sequence terminates | | [`RFC5880-6.8.3-4`](#rfc5880-6.8.3-4) If bfd.RequiredMinRxInterval is reduced while Up, the previous value must be used for detection time until the Poll terminates (§6.8.3) | {gap}, no test | revertEchoSlowdownLocked reduces bfd.RequiredMinRxInterval while Up (internal/component/bfd/session/timers.go:249) and DetectionInterval immediately uses the reduced value (internal/component/bfd/session/timers.go:29); no previous value is retained until the Poll Sequence terminates | | [`RFC5880-6.6-2`](#rfc5880-6.6-2) When the D bit value is to be changed, a Poll Sequence must be initiated (§6.6) | {gap}, no test | bfd.DemandMode has no writer in production code -- it is declared at internal/component/bfd/session/session.go:58 and read only by canSetDemand (internal/component/bfd/session/fsm.go:247) -- so no D-bit change path exists and none initiates a Poll Sequence | | [`RFC5880-6.6-3`](#rfc5880-6.6-3) If Demand mode is active on either system, a Poll Sequence must be initiated whenever next packet contents would differ (except P/F bits) (§6.6) | {gap}, no test | bfd.RemoteDemandMode is stored by Receive (internal/component/bfd/session/fsm.go:58) and read nowhere, and bfd.DemandMode has no writer (internal/component/bfd/session/session.go:58), so no code path starts a Poll when packet contents would change while Demand mode is active | | [`RFC5880-6.8.6-14`](#rfc5880-6.8.6-14) Reception: if remote Demand mode active (D=1, both Up), cease periodic Control packet transmission (§6.8.6) | {gap}, no test | tick transmits whenever the periodic deadline has passed and never consults the remote Demand state (internal/component/bfd/engine/loop.go:192-201), so periodic Control packets continue after the peer sets D=1 | | [`RFC5880-6.8.7-6`](#rfc5880-6.8.7-6) Transmission: must not periodically transmit if bfd.RemoteMinRxInterval is zero (§6.8.7) | {gap}, no test | TransmitInterval substitutes the slow-start interval when the negotiated maximum is zero (internal/component/bfd/session/timers.go:45-47) and tick carries no bfd.RemoteMinRxInterval == 0 suppression (internal/component/bfd/engine/loop.go:192-201), so periodic transmission continues | | [`RFC5880-6.8.7-7`](#rfc5880-6.8.7-7) Transmission: must not periodically transmit if remote Demand mode active and no Poll in flight (§6.8.7) | {gap}, no test | the same tick producer (internal/component/bfd/engine/loop.go:192-201) has no remote-Demand suppression, so periodic transmission continues while the remote Demand mode is active with no Poll in flight | | [`RFC5880-6.8.7-8`](#rfc5880-6.8.7-8) If rate limiting Final packets, advertised Desired Min TX Interval must be >= rate-limit interval (§6.8.7) | no test | no test carries this requirement id; annotated {not-applicable}: ze does not rate-limit Final packets -- handleInbound sends the Final unconditionally on every received Poll (internal/component/bfd/engine/loop.go:140-142) and no rate limiter exists on that path, so the conditional advertised-interval floor never applies | | [`RFC5880-6.8.15-1`](#rfc5880-6.8.15-1) Forwarding plane reset: set bfd.LocalDiag to 4, bfd.SessionState to Down (§6.8.15) | {gap}, no test | packet.DiagForwardingPlaneReset (internal/component/bfd/packet/diag.go:21) has no producer, and the only external state-forcing entry points are AdminDown and AdminEnable (internal/component/bfd/session/fsm.go:180,192), which move the session to AdminDown or Down carrying the caller's diagnostic and are never called with code 4 | | [`RFC5880-7-1`](#rfc5880-7-1) Multihop: a congestion control mechanism must be implemented (§7) | {gap}, no test | the transmit rate is derived solely from TransmitInterval plus jitter (internal/component/bfd/engine/loop.go:197-200) with no congestion-feedback input, and no congestion-control producer exists anywhere under internal/component/bfd | | [`RFC5880-7-2`](#rfc5880-7-2) Multihop: when congestion detected, BFD must reduce traffic generated (§7) | {gap}, no test | the same rate producer (internal/component/bfd/engine/loop.go:197-200) is the only authority over generated traffic and nothing reduces it in response to detected congestion | | [`RFC5880-6.8.17-1`](#rfc5880-6.8.17-1) If concatenated path failure diagnostic must be communicated and remote Demand mode is active, a Poll Sequence MUST be initiated (§6.8.17) | {gap}, no test | packet.DiagConcatPathDown (internal/component/bfd/packet/diag.go:23) has no producer and the remote Demand state stored at internal/component/bfd/session/fsm.go:58 is never read, so a concatenated-path failure neither sets the diagnostic nor initiates a Poll Sequence | | [`RFC5880-6.7.3-10`](#rfc5880-6.7.3-10) Reception: for Meticulous Keyed MD5/SHA1, if Sequence Number is not exactly bfd.RcvAuthSeq+1 (accounting for wrap), packet MUST be discarded (§6.7.3, §6.7.4) | {gap}, no test | SeqState.Check accepts any sequence strictly greater than bfd.RcvAuthSeq for the meticulous variants (internal/component/bfd/auth/meticulous.go:47-51), so a packet whose sequence jumps past RcvAuthSeq+1 is accepted rather than discarded | ## Proof state A tagged unit reads unproven where no discrimination record exists for it: nothing in this tree has been observed to break it, so the claim its tag makes is unproven. ### [`RFC5880-4.1-1`](#rfc5880-4.1-1) Version field must be 1 (§4.1, §6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880VersionNotOneDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L64) | unit/verify | unproven | | positive | [`TestRFC5880VersionOneAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L44) | unit/verify | unproven | ### [`RFC5880-4.1-2`](#rfc5880-4.1-2) Multipoint (M) bit must be zero on both transmit and receipt (§4.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880MultipointSetDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L96) | unit/verify | unproven | | positive | [`TestRFC5880MultipointZeroAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L80) | unit/verify | unproven | ### [`RFC5880-6.3-1`](#rfc5880-6.3-1) My Discriminator must be nonzero and unique across all BFD sessions on the system (§6.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DiscriminatorAllocatorSkipsReservedAndTaken`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L90) | unit/verify | unproven | | positive | [`TestRFC5880DiscriminatorsAreNonZeroAndUnique`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L62) | unit/verify | unproven | ### [`RFC5880-6.8.1-1`](#rfc5880-6.8.1-1) bfd.SessionState must be initialized to Down (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5880InitStatesAreDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L73) | unit/verify | unproven | ### [`RFC5880-6.8.1-2`](#rfc5880-6.8.1-2) bfd.RemoteSessionState must be initialized to Down (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5880InitStatesAreDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L76) | unit/verify | unproven | ### [`RFC5880-6.8.1-3`](#rfc5880-6.8.1-3) bfd.LocalDiscr must be unique across all BFD sessions on the system, and nonzero (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DiscriminatorAllocatorSkipsReservedAndTaken`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L95) | unit/verify | unproven | | positive | [`TestRFC5880DiscriminatorsAreNonZeroAndUnique`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L67) | unit/verify | unproven | ### [`RFC5880-6.8.1-4`](#rfc5880-6.8.1-4) bfd.RemoteDiscr must be initialized to zero (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880InitVariablesAreNotConstants`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L113) | unit/verify | unproven | | positive | [`TestRFC5880InitVariableDefaults`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L88) | unit/verify | unproven | ### [`RFC5880-6.8.1-5`](#rfc5880-6.8.1-5) bfd.RemoteDiscr must be set to zero if no valid packet received for one Detection Time (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880RemoteDiscrKeptOnNeighborSignaledDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L180) | unit/verify | unproven | | positive | [`TestRFC5880RemoteDiscrClearedOnDetectionExpiry`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L153) | unit/verify | unproven | ### [`RFC5880-6.8.1-6`](#rfc5880-6.8.1-6) bfd.LocalDiag must be initialized to zero (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880InitVariablesAreNotConstants`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L116) | unit/verify | unproven | | positive | [`TestRFC5880InitVariableDefaults`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L90) | unit/verify | unproven | ### [`RFC5880-6.8.1-7`](#rfc5880-6.8.1-7) bfd.DesiredMinTxInterval must be initialized to at least 1,000,000 microseconds (§6.8.1, §6.8.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880SlowStartFloorLiftedWhenUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L228) | unit/verify | unproven | | positive | [`TestRFC5880SlowStartFloorWhileNotUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L208) | unit/verify | unproven | ### [`RFC5880-6.8.1-8`](#rfc5880-6.8.1-8) bfd.RemoteMinRxInterval must be initialized to 1 (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880InitVariablesAreNotConstants`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L118) | unit/verify | unproven | | positive | [`TestRFC5880InitVariableDefaults`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L92) | unit/verify | unproven | ### [`RFC5880-6.8.1-9`](#rfc5880-6.8.1-9) bfd.RemoteDemandMode must be initialized to zero (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880InitVariablesAreNotConstants`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L121) | unit/verify | unproven | | positive | [`TestRFC5880InitVariableDefaults`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L94) | unit/verify | unproven | ### [`RFC5880-6.8.1-10`](#rfc5880-6.8.1-10) bfd.DetectMult must be a nonzero integer (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DetectMultZeroRequestSubstituted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L259) | unit/verify | unproven | | positive | [`TestRFC5880DetectMultConfiguredValue`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L248) | unit/verify | unproven | ### [`RFC5880-6.8.1-11`](#rfc5880-6.8.1-11) bfd.XmitAuthSeq must be initialized to a random 32-bit value (§6.8.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.1-11, so no unit is bound to it. ### [`RFC5880-6.8.1-12`](#rfc5880-6.8.1-12) bfd.AuthSeqKnown must be initialized to zero (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880AuthSeqKnownSetAfterFirstPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L436) | unit/verify | unproven | | positive | [`TestRFC5880AuthSeqKnownStartsZero`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L418) | unit/verify | unproven | ### [`RFC5880-6.8.1-13`](#rfc5880-6.8.1-13) bfd.AuthSeqKnown must be set to zero after no packets received for twice the Detection Time (§6.8.1) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.1-13, so no unit is bound to it. ### [`RFC5880-6.8.1-14`](#rfc5880-6.8.1-14) Session state must be preserved for at least one Detection Time after last valid packet (§6.8.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DetectionExpiryDownDiagOne`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L308) | unit/verify | unproven | | positive | [`TestRFC5880StatePreservedForOneDetectionTime`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L283) | unit/verify | unproven | ### [`RFC5880-6.1-1`](#rfc5880-6.1-1) Active role system must send BFD Control packets regardless of whether packets have been received (§6.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880PassiveRoleTransmitsAfterReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L410) | unit/verify | unproven | | positive | [`TestRFC5880ActiveRoleTransmitsWithoutReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L361) | unit/verify | unproven | ### [`RFC5880-6.1-2`](#rfc5880-6.1-2) Passive role system must not begin sending BFD packets until it has received one (§6.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880PassiveRoleTransmitsAfterReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L415) | unit/verify | unproven | | positive | [`TestRFC5880PassiveRoleSilentUntilReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L386) | unit/verify | unproven | ### [`RFC5880-6.1-3`](#rfc5880-6.1-3) At least one system must take the Active role (§6.1) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880PassiveRoleSilentUntilReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L394) | unit/verify | unproven | | positive | [`TestRFC5880ActiveRoleTransmitsWithoutReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L366) | unit/verify | unproven | ### [`RFC5880-6.8.3-1`](#rfc5880-6.8.3-1) When session state is not Up, bfd.DesiredMinTxInterval must be at least 1,000,000 microseconds (§6.8.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880SlowStartFloorLiftedWhenUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L232) | unit/verify | unproven | | positive | [`TestRFC5880SlowStartFloorWhileNotUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L213) | unit/verify | unproven | ### [`RFC5880-6.8.3-2`](#rfc5880-6.8.3-2) If bfd.DesiredMinTxInterval or bfd.RequiredMinRxInterval changes, a Poll Sequence must be initiated (§6.8.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880NoPollWhenIntervalsUnchanged`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L470) | unit/verify | unproven | | positive | [`TestRFC5880PollInitiatedOnIntervalChange`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L436) | unit/verify | unproven | ### [`RFC5880-6.8.3-3`](#rfc5880-6.8.3-3) If bfd.DesiredMinTxInterval is increased while Up, the actual TX interval must not change until the Poll Sequence terminates (§6.8.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.3-3, so no unit is bound to it. ### [`RFC5880-6.8.3-4`](#rfc5880-6.8.3-4) If bfd.RequiredMinRxInterval is reduced while Up, the previous value must be used for detection time until the Poll terminates (§6.8.3) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.3-4, so no unit is bound to it. ### [`RFC5880-6.8.3-5`](#rfc5880-6.8.3-5) If local system reduces TX interval due to bfd.RemoteMinRxInterval being reduced, it must honor the new interval immediately (§6.8.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880TransmitIntervalFlooredByLocalDesired`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L533) | unit/verify | unproven | | positive | [`TestRFC5880RemoteMinRxReductionHonoredImmediately`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L497) | unit/verify | unproven | ### [`RFC5880-6.8.3-6`](#rfc5880-6.8.3-6) Multiple parameter changes requiring Poll Sequence must be communicated in a single packet, or a round-trip must elapse, or an F=0 packet must be received before starting another Poll (§6.8.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5880PollInitiatedOnIntervalChange`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L441) | unit/verify | unproven | ### [`RFC5880-6.5-1`](#rfc5880-6.5-1) A BFD Control packet must not have both Poll (P) and Final (F) bits set (§6.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880FinalReplyClearsPollBit`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L586) | unit/verify | unproven | | positive | [`TestRFC5880PollPacketHasNoFinalBit`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L567) | unit/verify | unproven | ### [`RFC5880-6.5-2`](#rfc5880-6.5-2) If periodic Control packets are being sent, Poll Sequence must be performed by setting P bit on scheduled transmissions; additional packets must not be sent (§6.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880FinalReplyClearsPollBit`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L590) | unit/verify | unproven | | positive | [`TestRFC5880PollPacketHasNoFinalBit`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L571) | unit/verify | unproven | ### [`RFC5880-6.6-1`](#rfc5880-6.6-1) Demand (D) bit must not be set unless bfd.DemandMode is 1, bfd.SessionState is Up, and bfd.RemoteSessionState is Up (§6.6, §6.8.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DemandBitClearWhenAnyConditionFails`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L666) | unit/verify | unproven | | positive | [`TestRFC5880DemandBitSetWhenAllConditionsHold`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L652) | unit/verify | unproven | ### [`RFC5880-6.6-2`](#rfc5880-6.6-2) When the D bit value is to be changed, a Poll Sequence must be initiated (§6.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.6-2, so no unit is bound to it. ### [`RFC5880-6.6-3`](#rfc5880-6.6-3) If Demand mode is active on either system, a Poll Sequence must be initiated whenever next packet contents would differ (except P/F bits) (§6.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.6-3, so no unit is bound to it. ### [`RFC5880-6.7-1`](#rfc5880-6.7-1) Implementations supporting authentication must support both types of SHA1 authentication (§6.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880UnsupportedAuthTypesRejected`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L67) | unit/verify | unproven | | positive | [`TestRFC5880BothSHA1VariantsSupported`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L48) | unit/verify | unproven | ### [`RFC5880-4.2-1`](#rfc5880-4.2-1) Simple Password must be 1 to 16 bytes in length (§4.2, §6.7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`auth/TestRFC5880SimplePasswordLengthOutOfRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L744) | unit/verify | unproven | | negative | [`bfd/TestRFC5880SimplePasswordLengthOutOfRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L128) | unit/verify | unproven | | positive | [`TestRFC5880SimplePasswordSectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L589) | unit/verify | unproven | | positive | [`TestRFC5880SimplePasswordManagementAcceptsASCIIStrings`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L101) | unit/verify | unproven | ### [`RFC5880-6.7.2-1`](#rfc5880-6.7.2-1) Simple Password management interface must accept ASCII strings (§6.7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`bfd/TestRFC5880SimplePasswordLengthOutOfRangeRefused`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L132) | unit/verify | unproven | | positive | [`TestRFC5880SimplePasswordManagementAcceptsASCIIStrings`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L96) | unit/verify | unproven | ### [`RFC5880-6.7.2-8`](#rfc5880-6.7.2-8) Simple Password Auth Type must be set to 1, Auth Len must be set to the proper length of 4 to 19 bytes (§6.7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880SimplePasswordRejectsForeignSectionShape`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L610) | unit/verify | unproven | | positive | [`TestRFC5880SimplePasswordSectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L584) | unit/verify | unproven | ### [`RFC5880-6.7.3-1`](#rfc5880-6.7.3-1) Keyed MD5 Auth Type must be set to 2 or 3, Auth Len must be 24 (§6.7.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880KeyedMD5RejectsForeignSectionShape`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L110) | unit/verify | unproven | | positive | [`TestRFC5880KeyedMD5SectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L83) | unit/verify | unproven | ### [`RFC5880-6.7.3-2`](#rfc5880-6.7.3-2) Keyed MD5 Auth Key/Digest: MD5 digest must be calculated over entire BFD Control packet (§6.7.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DigestRejectsMandatorySectionTamper`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L198) | unit/verify | unproven | | positive | [`TestRFC5880DigestCoversWholePacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L179) | unit/verify | unproven | ### [`RFC5880-6.7.3-3`](#rfc5880-6.7.3-3) Keyed MD5: secret key must not be carried in the packet (§6.7.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5880SecretKeyNotCarriedInPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L222) | unit/verify | unproven | ### [`RFC5880-6.7.3-4`](#rfc5880-6.7.3-4) Meticulous Keyed MD5: bfd.XmitAuthSeq must be incremented for each packet (§6.7.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880MeticulousRejectsUnincrementedSequence`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L536) | unit/verify | unproven | | positive | [`TestRFC5880MeticulousSequenceIncrementsPerPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L287) | unit/verify | unproven | ### [`RFC5880-6.7.4-1`](#rfc5880-6.7.4-1) Keyed SHA1 Auth Type must be set to 4 or 5, Auth Len must be 28 (§6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880KeyedSHA1RejectsForeignSectionShape`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L156) | unit/verify | unproven | | positive | [`TestRFC5880KeyedSHA1SectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L135) | unit/verify | unproven | ### [`RFC5880-6.7.4-2`](#rfc5880-6.7.4-2) SHA1 hash must be calculated over entire BFD Control packet (§6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DigestRejectsMandatorySectionTamper`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L203) | unit/verify | unproven | | positive | [`TestRFC5880DigestCoversWholePacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L185) | unit/verify | unproven | ### [`RFC5880-6.7.4-3`](#rfc5880-6.7.4-3) Keyed SHA1: secret key must not be carried in the packet (§6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5880SecretKeyNotCarriedInPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L226) | unit/verify | unproven | ### [`RFC5880-6.7.4-4`](#rfc5880-6.7.4-4) Meticulous Keyed SHA1: bfd.XmitAuthSeq must be incremented for each packet (§6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880MeticulousRejectsUnincrementedSequence`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L541) | unit/verify | unproven | | positive | [`TestRFC5880MeticulousSequenceIncrementsPerPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L293) | unit/verify | unproven | ### [`RFC5880-6.7.4-5`](#rfc5880-6.7.4-5) SHA1 key management interface must accept ASCII strings (§6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880KeyManagementRejectsIncompleteConfig`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L70) | unit/verify | unproven | | positive | [`TestRFC5880KeyManagementAcceptsASCIIStrings`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L31) | unit/verify | unproven | ### [`RFC5880-6.8.6-1`](#rfc5880-6.8.6-1) Reception: discard if Version != 1 (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880VersionNotOneDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L66) | unit/verify | unproven | | positive | [`TestRFC5880VersionOneAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L48) | unit/verify | unproven | ### [`RFC5880-6.8.6-2`](#rfc5880-6.8.6-2) Reception: discard if Length < 24 (A=0) or < 26 (A=1) (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880LengthBelowMinimumDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L128) | unit/verify | unproven | | positive | [`TestRFC5880LengthMinimumAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L108) | unit/verify | unproven | ### [`RFC5880-6.8.6-3`](#rfc5880-6.8.6-3) Reception: discard if Length > encapsulating payload (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880LengthOverPayloadDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L163) | unit/verify | unproven | | positive | [`TestRFC5880LengthEqualsPayloadAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L148) | unit/verify | unproven | ### [`RFC5880-6.8.6-4`](#rfc5880-6.8.6-4) Reception: discard if Detect Mult == 0 (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DetectMultZeroDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L190) | unit/verify | unproven | | positive | [`TestRFC5880DetectMultNonZeroAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L174) | unit/verify | unproven | ### [`RFC5880-6.8.6-5`](#rfc5880-6.8.6-5) Reception: discard if M bit is nonzero (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880MultipointSetDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L99) | unit/verify | unproven | | positive | [`TestRFC5880MultipointZeroAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L84) | unit/verify | unproven | ### [`RFC5880-6.8.6-6`](#rfc5880-6.8.6-6) Reception: discard if My Discriminator == 0 (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880MyDiscriminatorZeroDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L214) | unit/verify | unproven | | positive | [`TestRFC5880MyDiscriminatorNonZeroAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/packet/rfc5880_test.go#L200) | unit/verify | unproven | ### [`RFC5880-6.8.6-7`](#rfc5880-6.8.6-7) Reception: if Your Discriminator nonzero, use it to select session; discard if no session found (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880UnknownYourDiscriminatorDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L140) | unit/verify | unproven | | positive | [`TestRFC5880YourDiscriminatorSelectsSession`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L124) | unit/verify | unproven | ### [`RFC5880-6.8.6-8`](#rfc5880-6.8.6-8) Reception: if Your Discriminator zero and State is not Down or AdminDown, discard (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880ZeroYourDiscriminatorDiscardedWhenLive`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L699) | unit/verify | unproven | | positive | [`TestRFC5880ZeroYourDiscriminatorAcceptedWhenDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L724) | unit/verify | unproven | ### [`RFC5880-6.8.6-9`](#rfc5880-6.8.6-9) Reception: if A=1 and bfd.AuthType is zero, discard (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880UnauthenticatedSessionDiscardsAuthenticatedPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L761) | unit/verify | unproven | | positive | [`TestRFC5880UnauthenticatedSessionAcceptsUnauthenticatedPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L746) | unit/verify | unproven | ### [`RFC5880-6.8.6-10`](#rfc5880-6.8.6-10) Reception: if A=0 and bfd.AuthType is nonzero, discard (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880AuthenticatedSessionDiscardsUnauthenticatedPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L796) | unit/verify | unproven | | positive | [`TestRFC5880AuthenticatedSessionAcceptsAuthenticatedPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L778) | unit/verify | unproven | ### [`RFC5880-6.8.6-11`](#rfc5880-6.8.6-11) Reception: if A=1, authenticate per §6.7 (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880UnauthenticPacketDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L258) | unit/verify | unproven | | positive | [`TestRFC5880AuthenticatedPacketVerifiedAndDelivered`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L241) | unit/verify | unproven | ### [`RFC5880-6.8.6-12`](#rfc5880-6.8.6-12) Reception: if Required Min Echo RX Interval == 0, cease Echo transmission (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880EchoEnabledWhenPeerAdvertisesNonZero`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L849) | unit/verify | unproven | | positive | [`TestRFC5880EchoCeasesWhenPeerAdvertisesZero`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L812) | unit/verify | unproven | ### [`RFC5880-6.8.6-13`](#rfc5880-6.8.6-13) Reception: if Poll in flight and F=1 received, terminate the Poll Sequence (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880NonFinalDoesNotTerminatePoll`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L630) | unit/verify | unproven | | positive | [`TestRFC5880FinalTerminatesPoll`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L610) | unit/verify | unproven | ### [`RFC5880-6.8.6-14`](#rfc5880-6.8.6-14) Reception: if remote Demand mode active (D=1, both Up), cease periodic Control packet transmission (§6.8.6) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.6-14, so no unit is bound to it. ### [`RFC5880-6.8.6-15`](#rfc5880-6.8.6-15) Reception: if remote Demand mode not active, send periodic Control packets (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880NoPeriodicTransmitWhileAdminDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L403) | unit/verify | unproven | | positive | [`TestRFC5880PeriodicTransmitWhenRemoteDemandInactive`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L368) | unit/verify | unproven | ### [`RFC5880-6.8.6-16`](#rfc5880-6.8.6-16) Reception: if P=1, send Final packet immediately (§6.8.6, §6.8.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880NonPollProducesNoImmediateReply`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L352) | unit/verify | unproven | | positive | [`TestRFC5880PollAnsweredWithImmediateFinal`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L322) | unit/verify | unproven | ### [`RFC5880-6.8.6-18`](#rfc5880-6.8.6-18) Reception: if Your Discriminator zero, select the session on a combination of other fields, which can include source addressing information, My Discriminator, and the ingress interface (§6.8.6) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5881FirstPacketWrongSourceDropped`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5881_test.go#L151) | unit/verify | unproven | | positive | [`TestFirstPacketMatchesWhatTheTransportSurfaces`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5881_test.go#L356) | unit/verify | revert, unit-changed (the tagged unit's behavior changed since the red was observed) | | positive | [`TestRFC5881FirstPacketMatchesByTuple`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5881_test.go#L128) | unit/verify | unproven | ### [`RFC5880-6.8.7-1`](#rfc5880-6.8.7-1) Transmission: must not transmit at interval less than max(bfd.DesiredMinTxInterval, bfd.RemoteMinRxInterval) less jitter (§6.8.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880TransmitDeadlineClampsBadJitter`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1045) | unit/verify | unproven | | positive | [`TestRFC5880TransmitDeadlineUsesNegotiatedInterval`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1015) | unit/verify | unproven | ### [`RFC5880-6.8.7-2`](#rfc5880-6.8.7-2) Transmission: periodic TX must be jittered by 0-25% per packet (§6.8.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880JitterStaysWithinBand`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L450) | unit/verify | unproven | | positive | [`TestRFC5880JitterIsAppliedPerPacket`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L422) | unit/verify | unproven | ### [`RFC5880-6.8.7-3`](#rfc5880-6.8.7-3) Transmission: if bfd.DetectMult == 1, interval must be 75-90% of negotiated interval (§6.8.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880JitterFloorOnlyForDetectMultOne`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L488) | unit/verify | unproven | | positive | [`TestRFC5880JitterDetectMultOneWindow`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L470) | unit/verify | unproven | ### [`RFC5880-6.8.7-4`](#rfc5880-6.8.7-4) Transmission: TX interval must be recalculated whenever bfd.DesiredMinTxInterval or bfd.RemoteMinRxInterval changes (§6.8.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880TransmitIntervalFlooredByLocalDesired`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L538) | unit/verify | unproven | | positive | [`TestRFC5880RemoteMinRxReductionHonoredImmediately`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L504) | unit/verify | unproven | ### [`RFC5880-6.8.7-5`](#rfc5880-6.8.7-5) Transmission: must not transmit if bfd.RemoteDiscr is zero and system is Passive (§6.8.7) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880ActiveRoleTransmitsWithoutReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L369) | unit/verify | unproven | | positive | [`TestRFC5880PassiveRoleSilentUntilReception`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L391) | unit/verify | unproven | ### [`RFC5880-6.8.7-6`](#rfc5880-6.8.7-6) Transmission: must not periodically transmit if bfd.RemoteMinRxInterval is zero (§6.8.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.7-6, so no unit is bound to it. ### [`RFC5880-6.8.7-7`](#rfc5880-6.8.7-7) Transmission: must not periodically transmit if remote Demand mode active and no Poll in flight (§6.8.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.7-7, so no unit is bound to it. ### [`RFC5880-6.8.7-8`](#rfc5880-6.8.7-8) If rate limiting Final packets, advertised Desired Min TX Interval must be >= rate-limit interval (§6.8.7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.7-8, so no unit is bound to it. ### [`RFC5880-6.8.4-1`](#rfc5880-6.8.4-1) Detection time expired while Init or Up: set bfd.SessionState to Down, bfd.LocalDiag to 1 (§6.8.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DetectionExpiryIgnoredWhenDown`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L333) | unit/verify | unproven | | positive | [`TestRFC5880DetectionExpiryDownDiagOne`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L312) | unit/verify | unproven | ### [`RFC5880-6.8.5-1`](#rfc5880-6.8.5-1) Echo function failure: set bfd.SessionState to Down, bfd.LocalDiag to 2 (§6.8.5) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880EchoReturnClearsMissAndFailIsScoped`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L979) | unit/verify | unproven | | positive | [`TestRFC5880EchoMissDetectedAndReported`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L943) | unit/verify | unproven | ### [`RFC5880-6.8.15-1`](#rfc5880-6.8.15-1) Forwarding plane reset: set bfd.LocalDiag to 4, bfd.SessionState to Down (§6.8.15) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.15-1, so no unit is bound to it. ### [`RFC5880-6.8.16-1`](#rfc5880-6.8.16-1) Administrative control: follow the enable/disable procedure (§6.8.16) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880AdministrativeCallsAreGuarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1106) | unit/verify | unproven | | positive | [`TestRFC5880AdministrativeDisableEnable`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1071) | unit/verify | unproven | ### [`RFC5880-6.8.8-1`](#rfc5880-6.8.8-1) Echo packets must be demultiplexed to the appropriate session (§6.8.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880UnknownEchoDropped`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L626) | unit/verify | unproven | | positive | [`TestRFC5880EchoDemultiplexedToItsSession`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L589) | unit/verify | unproven | ### [`RFC5880-6.8.8-2`](#rfc5880-6.8.8-2) A means of detecting missing Echo packets must be implemented (§6.8.8) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880EchoReturnClearsMissAndFailIsScoped`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L974) | unit/verify | unproven | | positive | [`TestRFC5880EchoMissDetectedAndReported`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L938) | unit/verify | unproven | ### [`RFC5880-6.8.9-1`](#rfc5880-6.8.9-1) Echo packets must not be transmitted when bfd.SessionState is not Up (§6.8.9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880NoEchoTransmittedWhenNotUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L564) | unit/verify | unproven | | positive | [`TestRFC5880EchoTransmittedWhileUp`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L543) | unit/verify | unproven | ### [`RFC5880-6.8.9-2`](#rfc5880-6.8.9-2) Echo packets must not be transmitted unless remote Required Min Echo RX Interval is nonzero (§6.8.9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880EchoNotTransmittedWithoutPeerAdvertisement`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L873) | unit/verify | unproven | | positive | [`TestRFC5880EchoEnabledWhenPeerAdvertisesNonZero`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L852) | unit/verify | unproven | ### [`RFC5880-6.8.9-3`](#rfc5880-6.8.9-3) Echo packet TX interval must not be less than remote Required Min Echo RX Interval (§6.8.9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880EchoIntervalNotBelowLocalTarget`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L915) | unit/verify | unproven | | positive | [`TestRFC5880EchoIntervalHonorsPeerFloor`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L896) | unit/verify | unproven | ### [`RFC5880-7-1`](#rfc5880-7-1) Multihop: a congestion control mechanism must be implemented (§7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-7-1, so no unit is bound to it. ### [`RFC5880-7-2`](#rfc5880-7-2) Multihop: when congestion detected, BFD must reduce traffic generated (§7) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-7-2, so no unit is bound to it. ### [`RFC5880-4.3-1`](#rfc5880-4.3-1) Reserved byte in MD5/SHA1 auth section must be set to zero on transmit (§4.3, §4.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5880KeyedMD5SectionHeader`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L88) | unit/verify | unproven | ### [`RFC5880-9-2`](#rfc5880-9-2) For single-hop sessions, TTL or Hop Count MUST be set to maximum on transmit and checked equal to maximum on receipt (§9) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880SingleHopReceiveTTLNotMaxDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L160) | unit/verify | unproven | | positive | [`TestRFC5880SingleHopTransmitTTLIsMaximum`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/transport/rfc5880_test.go#L43) | unit/verify | unproven | ### [`RFC5880-6.8.14-1`](#rfc5880-6.8.14-1) If Demand mode is no longer active on the remote system, the local system MUST begin transmitting periodic BFD Control packets (§6.8.14) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5880PeriodicTransmitWhenRemoteDemandInactive`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/engine/rfc5880_test.go#L372) | unit/verify | unproven | ### [`RFC5880-6.8.17-1`](#rfc5880-6.8.17-1) If concatenated path failure diagnostic must be communicated and remote Demand mode is active, a Poll Sequence MUST be initiated (§6.8.17) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.8.17-1, so no unit is bound to it. ### [`RFC5880-6.8.3-8`](#rfc5880-6.8.3-8) Timing parameter changes not explicitly excepted MUST be effected immediately (§6.8.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | positive | [`TestRFC5880RemoteMinRxReductionHonoredImmediately`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L506) | unit/verify | unproven | ### [`RFC5880-6.7.3-7`](#rfc5880-6.7.3-7) MD5 key management interface MUST accept ASCII strings (§6.7.3) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880KeyManagementRejectsIncompleteConfig`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L64) | unit/verify | unproven | | positive | [`TestRFC5880KeyManagementAcceptsASCIIStrings`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/rfc5880_test.go#L26) | unit/verify | unproven | ### [`RFC5880-6.7.2-3`](#rfc5880-6.7.2-3) Simple Password, MD5, and SHA1 Auth Key ID field MUST be set to the ID of the current authentication key (§6.7.2, §6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880AuthKeyIDMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L264) | unit/verify | unproven | | positive | [`TestRFC5880AuthKeyIDIsTheConfiguredKey`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L246) | unit/verify | unproven | | positive | [`TestRFC5880SimplePasswordSectionCarriesPasswordAndKeyID`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L639) | unit/verify | unproven | ### [`RFC5880-6.7.3-8`](#rfc5880-6.7.3-8) Simple Password, MD5, and SHA1 Sequence Number field MUST be set to bfd.XmitAuthSeq (§6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880AuthSequenceFieldFollowsAdvance`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1163) | unit/verify | unproven | | positive | [`TestRFC5880AuthSequenceFieldIsXmitAuthSeq`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/session/rfc5880_test.go#L1140) | unit/verify | unproven | ### [`RFC5880-6.7.2-4`](#rfc5880-6.7.2-4) Reception: if Auth Type does not match bfd.AuthType, packet MUST be discarded (§6.7.2, §6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880AuthTypeMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L310) | unit/verify | unproven | | positive | [`TestRFC5880AuthTypeMatchAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L297) | unit/verify | unproven | ### [`RFC5880-6.7.2-5`](#rfc5880-6.7.2-5) Reception: if Auth Key ID does not match any configured authentication key, packet MUST be discarded (§6.7.2, §6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880AuthKeyIDMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L267) | unit/verify | unproven | | negative | [`TestRFC5880SimplePasswordWrongKeyIDDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L712) | unit/verify | unproven | | positive | [`TestRFC5880AuthKeyIDMatchAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L285) | unit/verify | unproven | | positive | [`TestRFC5880SimplePasswordMatchingKeyIDAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L729) | unit/verify | unproven | ### [`RFC5880-6.7.2-6`](#rfc5880-6.7.2-6) Reception: if Auth Len does not match expected length, packet MUST be discarded (§6.7.2, §6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880AuthLenMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L343) | unit/verify | unproven | | positive | [`TestRFC5880AuthLenExpectedAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L326) | unit/verify | unproven | ### [`RFC5880-6.7.2-7`](#rfc5880-6.7.2-7) Reception: if password does not match configured password for Simple Password, packet MUST be discarded (§6.7.2) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880SimplePasswordMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L681) | unit/verify | unproven | | positive | [`TestRFC5880SimplePasswordMatchAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L662) | unit/verify | unproven | ### [`RFC5880-6.7.3-9`](#rfc5880-6.7.3-9) Reception: for Keyed MD5/SHA1, if Sequence Number is less than bfd.RcvAuthSeq (accounting for wrap), packet MUST be discarded (§6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880KeyedSequenceBelowFloorDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L514) | unit/verify | unproven | | positive | [`TestRFC5880KeyedSequenceAtOrAboveFloorAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L494) | unit/verify | unproven | ### [`RFC5880-6.7.3-10`](#rfc5880-6.7.3-10) Reception: for Meticulous Keyed MD5/SHA1, if Sequence Number is not exactly bfd.RcvAuthSeq+1 (accounting for wrap), packet MUST be discarded (§6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests No test carries RFC5880-6.7.3-10, so no unit is bound to it. ### [`RFC5880-6.7.3-11`](#rfc5880-6.7.3-11) Reception: if digest/hash does not match computed value, packet MUST be discarded (§6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880DigestMismatchDiscarded`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L390) | unit/verify | unproven | | positive | [`TestRFC5880DigestMatchAccepted`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L377) | unit/verify | unproven | ### [`RFC5880-6.7.3-12`](#rfc5880-6.7.3-12) Reception: if bfd.AuthSeqKnown is 0, it MUST be set to 1 and bfd.RcvAuthSeq MUST be set to received Sequence Number (§6.7.3, §6.7.4) Audit verdict: not audited: no reader has judged these tests | Polarity | Test | Kind and tier | Proof state | |---|---|---|---| | negative | [`TestRFC5880ForgedFirstPacketDoesNotSeedReplayFloor`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L471) | unit/verify | unproven | | positive | [`TestRFC5880FirstAuthenticatedPacketSeedsReplayFloor`](https://github.com/ze-software/ze/blob/main/internal/component/bfd/auth/rfc5880_test.go#L447) | unit/verify | unproven | ## Extraction sign-off | Field | Value | |---|---| | Reviewer | ze-work agent, spec-fixit-rfc-drain-quota-never-armed WP-1 | | Signed off | 2026-08-31 | | Register | rfc2119 | | Source | rfc/full/rfc5880.txt | | Source fingerprint | 9a3492d0917193af | | Record | rfc/extraction/rfc5880.json | | Mapped sentences | 99 | | Declined as scope | 29 | | Relocated to a spec, which Ze OWES | 0 | | Unclassified | 0 | ### Sections | Section | Name | Sites | Disposition | Reason | |---|---|---|---|---| | `front` | not stated | 0 | skipped (front-matter) | Title block, Abstract, Status of This Memo, copyright notice and table of contents. The Abstract restates section 1 and states no obligation. | | `1` | Introduction | 0 | walked | Introduction. States the goal of the protocol and says that application-dependent mechanisms live in companion documents. No obligation. | | `1.1` | not stated | 0 | walked | Conventions Used in This Document: the RFC 2119 key-words paragraph. It tells a reader how to read the other sections and binds no speaker, which is why the derivation excludes it from the site inventory. | | `2` | Design | 0 | walked | Design. Describes where BFD sits relative to the forwarding plane and what it runs over. Written in the indicative throughout and states no obligation. | | `3` | Protocol Overview | 0 | walked | Protocol Overview. Describes the Hello exchange and the rate negotiation in the indicative. No obligation. | | `3.1` | Addressing and Session Establishment | 0 | walked | Addressing and Session Establishment. States that the application chooses the addresses and that BFD has no discovery mechanism. No obligation. | | `3.2` | Operating Modes | 0 | walked | Operating Modes. Describes Asynchronous mode, Demand mode and the Echo function, and the trade-offs between them. Their obligations are stated in section 6 and captured there. | | `4` | BFD Control Packet Format | 0 | walked | BFD Control Packet Format. Section heading only; the formats are in the subsections below. | | `4.1` | Generic BFD Control Packet Format | 1 | walked | Generic BFD Control Packet Format. The field descriptions are written in the indicative apart from the Multipoint bit, which is the one site. | | `4.2` | Simple Password Authentication Section Format | 2 | walked | Simple Password Authentication Section Format. Two sites: the password length bound and the pointer to the section 6.7.2 encoding rules. | | `4.3` | not stated | 2 | walked | Keyed MD5 and Meticulous Keyed MD5 Authentication Section Format. Two sites: the Reserved byte and the pointer to the section 6.7.3 key encoding rules. | | `4.4` | not stated | 2 | walked | Keyed SHA1 and Meticulous Keyed SHA1 Authentication Section Format. Two sites: the Reserved byte and the pointer to the section 6.7.4 key encoding rules. | | `5` | BFD Echo Packet Format | 0 | walked | BFD Echo Packet Format. The payload is a local matter, so the section states one advisory obligation and no MUST-level one. | | `6` | Elements of Procedure | 0 | walked | Elements of Procedure. Introductory text that names section 6.8.1 as the home of the bfd.Xx state variables and warns against enforcing more than the section states. No obligation. | | `6.1` | Overview | 3 | walked | Overview. The three role obligations are the sites; the rest of the section describes the session lifecycle in the indicative and is specified normatively in section 6.8. | | `6.2` | BFD State Machine | 0 | walked | BFD State Machine. The transitions are described in the indicative and are specified normatively in section 6.8.6 and section 6.8.16. The one advisory obligation is the right to hold a session down. | | `6.3` | Demultiplexing and the Discriminator Fields | 1 | walked | Demultiplexing and the Discriminator Fields. One site, the discriminator choice rule. The permission to change a discriminator mid-session is advisory. | | `6.4` | The Echo Function and Asymmetry | 0 | walked | The Echo Function and Asymmetry. Describes independent Echo directions in the indicative and states one advisory obligation about advertised intervals. | | `6.5` | The Poll Sequence | 2 | walked | The Poll Sequence. Two sites: the P and F exclusion, and the rule that a Poll rides on the scheduled periodic packets. | | `6.6` | Demand mode | 3 | walked | Demand mode. Three sites, all about the Demand bit and the Poll Sequences a Demand mode change requires. The permission to enable or disable Demand mode at any time is advisory. | | `6.7` | Authentication | 1 | walked | Authentication. Describes the generic authentication section, then states the one obligation of the section: an implementation that supports authentication supports both SHA1 types. | | `6.7.1` | Enabling and Disabling Authentication | 0 | walked | Enabling and Disabling Authentication. The section says the mechanism is out of scope and then states two advisory obligations about changing the authentication state on received packets. | | `6.7.2` | Simple Password Authentication | 10 | walked | Simple Password Authentication. Ten sites: three transmission field rules, the password bound, the management interface rule, four reception discards and the closing acceptance. The hexadecimal configuration clause is advisory. | | `6.7.3` | Keyed MD5 and Meticulous Keyed MD5 Authentication | 17 | walked | Keyed MD5 and Meticulous Keyed MD5 Authentication. Seventeen sites over the transmission and reception rules. The sequence increment guidance for Keyed MD5 is advisory, and the circular increment is a permission. | | `6.7.4` | Keyed SHA1 and Meticulous Keyed SHA1 Authentication | 17 | walked | Keyed SHA1 and Meticulous Keyed SHA1 Authentication. Seventeen sites that repeat the section 6.7.3 rules with