Repository navigation
Add ILNP, AMTRELAY, DRIP, HIP, SIG and PX records - #152
Merged
Merged
Conversation
NelsonVides
force-pushed
the
fix/rfc-conformance
branch
from
October 6, 2026 10:50
d8875f1 to
e2da626
Compare
NelsonVides
force-pushed
the
rfc-record-types
branch
2 times, most recently
from
October 6, 2026 12:21
5b5d0a5 to
409f1c5
Compare
NID (104), L32 (105), L64 (106) and LP (107) decoded as opaque bytes,
failed to parse from a zone file and had no JSON form. They now get
records of their own: a 16-bit preference with a 64-bit NodeID, a
32-bit Locator32, a 64-bit Locator64 or a FQDN.
All four are class independent (RFC 6742 §2), so unlike A and
EUI48/EUI64 they decode in any class. LP's FQDN is written without
compression (§2.4) and keeps its case in DNSSEC canonical form, since
LP is not one of the types RFC 4034 §6.2 lowercases (RFC 3597 §7).
The NodeID and Locator64 are 8-byte binaries, as the EUI64 address is,
and like it the encoder refuses one of another length, which it would
write under an RDLENGTH of 10 and so shift every record after it.
dns_check refuses the same at load time, along with a preference past 16
bits, a Locator32 that is not an address and an LP name that does not
fit: dns_json:from_map/1 raises {invalid_record, Record} and the zone
file parser returns a semantic error.
In zone files a NodeID or Locator64 is four colon-separated groups of
1-4 hex digits, and is written back with all four digits of each group.
The "::" shorthand is rejected (§2.1, §2.3). The Locator32 goes through
the same strict parser as an A record, so leading zeros are refused,
including in the RFC's own "10.1.02.0" example. In JSON the 64-bit
values are hex, as EUI64 is, and the Locator32 is a dotted quad.
Tests cover known-answer wire vectors built from the RFC examples, the
class independence, the uncompressed LP target, the encoder's refusal of
a NodeID or Locator64 of the wrong size, the load-time checks for the
8-byte values and field widths, zone parse, encode and round trip, JSON
transcoding and rejection, the canonical form, and the four types in the
wire and zone-file properties. fmt, lint, xref, dialyzer and ct (760)
pass, and cover stays at 92%.
AMTRELAY (260) decoded as opaque bytes, failed to parse from a zone file and had no JSON form. It now gets a record of its own: an 8-bit precedence, the D-bit as discovery_optional, the relay type and the relay, which is empty for type 0, an IPv4 or IPv6 address for types 1 and 2, and a domain name for type 3. AMTRELAY is class independent (§4.1), so unlike A and AAAA its addresses decode in any class. The relay's name is written without compression (§4.2.3) and keeps its case in DNSSEC canonical form, as AMTRELAY is not one of the types RFC 4034 §6.2 lowercases (RFC 3597 §7). Relay types 4 to 127 are undefined and receivers SHOULD NOT use them (§4.2.3), so their RDATA stays opaque, as BIND keeps it, and so does a relay of the wrong length for types 0 to 2. A type 3 relay that is not one name, being missing, cut short or followed by more bytes, makes the message a FORMERR, as a bad name in any other RDATA does. The encoder trusts the record, so dns_check refuses at load time a relay that is not what its type announces, an undefined relay type, a D-bit that is not a boolean and a precedence past 8 bits. A type 0 record carries no relay on the wire, so one holding a name would have lost it. In zone files the relay is "." for type 0, and a type 3 relay is a name even when it reads like an address, as in BIND. In JSON the relay is an address string, a name or the empty string; the address is parsed after the record is built, since it depends on the relay type. Tests cover known-answer vectors from the RFC 8777 §4.3.2 examples, as corrected by erratum 6218, the class independence, the uncompressed relay, the opaque undefined types, the FORMERR for a bad type 3 relay, the load-time checks, zone parse, encode and round trip, JSON transcoding and rejection, the canonical form, and AMTRELAY in the wire and zone-file properties. fmt, lint, xref, dialyzer and ct (772) pass, and cover stays at 92%.
NelsonVides
force-pushed
the
rfc-record-types
branch
from
October 6, 2026 14:12
409f1c5 to
a2218a9
Compare
HHIT (67) and BRID (68) decoded as opaque bytes, failed to parse from a zone file and had no JSON form. Each now gets a record holding its data, the CBOR of a DRIP Entity Tag's registration (§5.1) or of a drone's static Broadcast Remote ID (§5.2), carried as it is. The CBOR is not checked, as BIND and PowerDNS do not check it either, and neither type is bound to a class. The encoder trusts the record, so dns_check refuses at load time data that is empty or not a binary. The CBOR has mandatory fields, and the decoder refuses RDATA of zero length for every type it knows, so an empty record would have gone out and not come back. In zone files the data is base64, which may be split into any number of whitespace-separated pieces, also across lines in parentheses (§5.1.1, §5.2.1), and it is written back as a single string. In JSON it is base64, as OPENPGPKEY's data is. Tests cover the RFC's Appendix A.2.2 zone file, with the BRID data that erratum 8823 corrects, checked against the CBOR structure of Figures 19 and 21, the data carried as it is in any class, the load-time check for empty data, zone parse, encode and round trip, JSON transcoding and rejection, and both types in the wire and zone-file properties. fmt, lint, xref, dialyzer and ct (779) pass, and cover stays at 92%.
HIP (55) decoded as opaque bytes, failed to parse from a zone file and had no JSON form. It now gets a record of its own: the public key algorithm, the HIT, the public key and the rendezvous servers, a list of names that may be empty. The HIT and key lengths are not fields, as they are the lengths of the binaries. The rendezvous servers' names are written without compression (§5.6) and keep their case in DNSSEC canonical form, as HIP is not one of the types RFC 4034 §6.2 lowercases (RFC 3597 §7). HIP is not bound to a class, as BIND has it. The HIT and the public key are REQUIRED (§5), so a HIP RDATA with either empty, which BIND refuses, stays opaque. The encoder refuses a HIT past 255 bytes, which would go out with a wrapped 8-bit length, and dns_check refuses it at load time too, along with an empty HIT or key, an algorithm past 8 bits and servers that are not a list of names that fit. In zone files the HIT is hex and the key base64, each one word, as §6 has them, followed by the servers, which may be relative to the origin. In JSON the HIT is hex and the key base64. A HIT of decimal digits only lexes as a number and loses its leading zeros; that is the zone file lexer's limitation, which DS digests share, and the zone property draws HITs and keys that lex as words, as it does HHIT and BRID data. Tests cover the RFC's §7 example as a known-answer wire vector in any class and as a zone file, the uncompressed server names, the opaque RDATA without a HIT or key, the encoder's refusal of a HIT past 255 bytes, the load-time checks, zone parse and rejection, each case breaking one field of a record that parses, encode and round trip, JSON transcoding and rejection, the canonical form, and HIP in the wire and zone-file properties. fmt, lint, xref, dialyzer and ct (789) pass, and cover stays at 92%.
SIG (24) decoded as opaque bytes, failed to parse from a zone file and had no JSON form. It now gets a record of its own, with RRSIG's fields: RRSIG took over SIG's RDATA for zone data (RFC 4034 §3), and SIG lives on as SIG(0), which signs a whole message with type covered 0 and goes last in the additional section (RFC 2931 §3). Only the record comes with this; creating and verifying SIG(0) signatures does not. SIG is not one of RFC 1035's types, so the signer's name is written without compression (RFC 3597 §4), and it is lowercased in DNSSEC canonical form, as SIG is on the list of RFC 4034 §6.2. The algorithm is any 8-bit number, since SIG predates the DNSSEC algorithms RRSIG's record lists. The encoder trusts the record, so dns_check refuses at load time a field past its width and a signer's name that does not fit, as it does for RRSIG. In zone files and JSON a SIG is spelled as an RRSIG, which it shares the presentation format with (RFC 2535 §7.2), the times as seconds or YYYYMMDDHHMMSS dates and the signature in base64; the zone file parser and encoder go through RRSIG's. A SIG(0) is class ANY, which zone files do not take, so one the zone encoder writes does not read back. The record has RRSIG's fields in the same order, so the wire codec, dns_check and the zone file parser and encoder read and write a SIG as an RRSIG and retag it, rather than each keep a copy of RRSIG's code that a fix to one could miss. Tests cover a known-answer wire vector, SIG and RRSIG having the same fields and staying opaque alike, a SIG(0) at the end of a message's additional section, with class ANY and the uncompressed signer's name, the load-time checks, zone parse with both time forms and a TYPE0 type covered, encode and round trip, JSON transcoding of a SIG(0) and rejection, the lowercased canonical form, and SIG in the wire properties. fmt, lint, xref, dialyzer and ct (796) pass, and cover is at 93%.
PX (26) decoded as opaque bytes, failed to parse from a zone file and had no JSON form. It now gets a record of its own: a preference and the two names that map between Internet and X.400 mail domains, MAP822 and MAPX400. PX is defined for class IN (§4), so like A it decodes in IN and NONE only and stays opaque in other classes. PX is not one of RFC 1035's types, so its names are written without compression (RFC 3597 §4), and they are lowercased in DNSSEC canonical form, as PX is on the list of RFC 4034 §6.2. The encoder trusts the record, so dns_check refuses at load time a preference past 16 bits and names that do not fit. In zone files both names may be relative to the origin; in JSON they are plain names. Tests cover a known-answer wire vector from the §4.1 examples and its uncompressed names in a message, the class binding, the load-time checks, zone parse of the §4.1 examples, encode and round trip, JSON transcoding and rejection, the lowercased canonical form, and PX in the wire and zone-file properties. fmt, lint, xref, dialyzer and ct (802) pass, and cover stays at 93%.
The ILNP, AMTRELAY, DRIP, HIP, SIG and PX records are new features, so their changelog entries go under a minor release, v5.1.0. main is left empty above it.
NelsonVides
force-pushed
the
rfc-record-types
branch
from
October 6, 2026 14:42
a2218a9 to
fdd9939
Compare
JSON: a type this library decodes can still leave RDATA opaque, as an AMTRELAY with an undefined relay type or a HIP with an empty HIT does, and to_map writes that as a lone base64 "data" key. from_map built a record from it and refused it. A lone "data" key for a record without a data field now loads back as the opaque binary. SIG was read and written as an RRSIG by swapping the record tag with setelement, which held only while both records kept their fields in the same order. dns_encode now converts between them field by field with sig_to_rrsig/1 and rrsig_to_sig/1, which every codec uses, and dns:rrdata_sig() names the record's type. AMTRELAY is encoded by one clause, its relay by relay type in a helper. The NID and L64 zone builders share one, and an ILNP NodeID or Locator64 is read with hex_to_binary/1 rather than a hand-written digit check. The changelog now says that SIG, PX and HIP RRs, which decoded as opaque binaries, now decode into records, and that a zone line starting with one of the new mnemonics and no class reads as a record of that type, as it does for the other types. make test passes: fmt, lint, xref, dialyzer, ct (803) and cover (93%).
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.
Adds the ILNP (NID, L32, L64, LP), AMTRELAY, HHIT and BRID, HIP, SIG and PX record types, on the wire, in zone files and in JSON, and releases them as v5.1.0.
Each type decodes from and encodes to the wire, parses from and encodes to zone files, and transcodes to JSON, with load-time checks in
dns_checkand DNSSEC canonical forms per RFC 4034 §6.2. Names inside the new RDATA are never compressed on the wire. Where the RFCs leave RDATA undefined (an AMTRELAY relay type past 3, a HIP with an empty HIT or public key), it stays opaque, as for an unknown type, and round-trips through JSON as base64. SIG covers the record format, and so SIG(0) message signatures as records; creating and verifying SIG(0) signatures is not part of this.Two behavior changes for consumers, also in the changelog:
datafor them no longer matches. As for every type decoded into a record, an empty RDATA for them now decodes toformerr.PX, needs a class after it, as inPX IN A 192.0.2.1.Known limitation, not new here: in zone files, a base64 or hex piece made only of digits (
1234,0012,12h) is lexed as a number, so it fails for HHIT and BRID as it already does for DNSKEY, RRSIG and TLSA. Fixing it needs a lexer change for every type, left for a follow-up.🔍 QA
Scenario: Test suite
make test: fmt, lint, xref, dialyzer and all 803 CT tests pass, with coverage at 93%.Scenario: Consumers
data.📋 Deployment Pre/Post tasks
v5.1.0and push the tag to publish to Hex.pm.