Repository navigation
FreeRadius Conformance Tests and Fixes - #41
Merged
nicholasamorim merged 7 commits intoJun 2, 2026
Merged
Conversation
scripts/fetch_freeradius_corpus.py sparse-clones two pinned slices of freeradius-server on demand, release_3_2_5 for the 244 share/dictionary files, v4 master for 41 decode-proto/match packet vectors into a gitignored tests/conformance/_corpus/. The fixtures skip cleanly when the corpus is absent so `make test` stays fast and network-free. Per-dict load is strict: 209 vendor dicts parse standalone, 35 xfail with a documented reason (combo-ip, 3-level nested TLVs, parent-context, FR's "String" typo), and xfail strict=True re-arms the test the moment pyrad2 grows support. Two parser tokens added to DATATYPES (vsa, ipv4prefix) so the root RFC dictionaries load. vsa is functionally octets since the real VSA dispatch lives at the packet layer, and ipv4prefix mirrors the ipv6prefix shape pyrad2 already handles. Wire-level encoders for either are a follow-up.
FreeRADIUS dictionaries declaring combo-ip as "either an IPv4 or IPv6 address, decided by wire length" now load. Codec lives in tools.py: encoder dispatches on input shape via ip_address(), decoder on wire length (4 = v4, 16 = v6, anything else rejected). Wired into both encode_attr/decode_attr and the parser's DATATYPES allowlist.
The dictionary conformance test was loading each vendor file standalone, which made 24 of them xfail for a reason that has nothing to do with pyrad2: they reference attributes declared in another FreeRADIUS dict. Switched to a session fixture that walks the FreeRADIUS root's $INCLUDE order and cumulatively loads every RFC dict pyrad2 can parse, then stacks each vendor dict on that base.
Encode side: add_attribute walks the parent chain (top-level down) and nests dicts at every level, so an attribute declared as 241.5.1 lands at self[241][5][1]. _pkt_encode_extended / _pkt_encode_long_extended / _pkt_encode_tlv detect dict-valued slots and route them through a new _encode_tlv_chain helper that flattens any depth into a TLV chain before the outer envelope is applied. Decode side: a new _decode_tlv_chain_into recursively parses TLV chains, descending into any child slot the dictionary declares as ``tlv``. Both _pkt_decode_extended and the long-extended reassembly path check the slot type and either store raw bytes (leaf) or a nested map (tlv). __getitem__ recurses through nested storage so pkt["Wrapper"]["Container"]["Leaf"] returns decoded values.
uint8/uint16/uint32/uint64/int32 normalise to the canonical pyrad2 type tokens (byte/short/integer/integer64/signed) at parser time, so nothing downstream has to know they exist. ``virtual`` marks attributes that exist in the dictionary for server-internal use but are never sent on the wire, the encoder skips them. ``array`` (RFC 8044 §3.8) packs multiple values of a fixed-length type into one AVP on encode and splits them back out post-decode via a wire-length table covering all ten fixed-length RADIUS types. ``secret`` is accepted as a no-op marker for parity with FreeRADIUS-internal dicts. The conformance fixture now stacks dictionary.freeradius, dictionary.freeradius.internal, and dictionary.dhcp on top of the RFC base so vendor dicts can find their transitively-required attributes. Four xfails unblock cleanly (freeradius, freeradius.internal, compat, dhcp).
The parser now accepts ``format=type_len,len_len,c``. vendor_formats becomes a 3-tuple ``(type_len, len_len, has_continuation)`` and threads through the encode/decode path. Encode side: when a vendor has continuation, _pack_vsa_inner inserts a continuation byte after the length header and _pkt_encode_continuation_vsa fragments oversized values across AVPs with the 0x80 More flag set on every fragment except the last. Per-fragment budget works out to 246 payload bytes for the standard 1,1,c layout. Decode side: _pkt_decode_vendor_attribute reads the continuation byte for continuation-format vendors, buffers fragments in a per-decode table keyed on (vendor, vsa_type), and emits the joined value when More clears.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is about real-world FreeRADIUS interop. pyrad2 now ships a conformance suite that loads the upstream FreeRADIUS dictionary corpus and decodes its packet test vectors on every CI run, and every gap the suite surfaced got a real codec.
As of this entry, 281 of the 244 vendor dictionaries plus 41 of the v4 packet test vectors pass; only four dictionaries still
xfail, and three of those are upstream FreeRADIUS dictionary bugs.