- Zig 95.3%
- Python 3%
- Nix 1.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P9FTtVnMcnFRXFB7z9TZRE |
||
| .forgejo/workflows | ||
| docs/adr | ||
| LICENSES | ||
| src | ||
| tests | ||
| tools | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| build.zig.zon.nix | ||
| CONTRIBUTING.md | ||
| flake.lock | ||
| flake.nix | ||
| GLOSSARY.md | ||
| README.md | ||
| REUSE.toml | ||
zig-dns
DNS messages for Zig 0.17, decoded and encoded, and nothing else.
This library turns bytes into a message you can read and a message you describe into bytes. It opens no sockets, reads no files, starts no threads, consults no configuration and holds no cache — and it never allocates. What you do with a message is somewhere else, which is the point: the hard parts of this format are the same whether the bytes came from a socket, from a packet capture, or from a fuzzer.
The API documentation is generated from the doc comments, which are most of the explanation of why DNS is the shape it is.
Where this lives
The repository lives in three places that carry the same history. The Forgejo instance at https://git.jcollie.dev/jeff/zig-dns is the web-visible one:
$ git clone https://git.jcollie.dev/jeff/zig-dns.git
it is mirrored on Tangled at https://tangled.org/jcollie.dev/zig-dns, and it is also on the Radicle network, where the repository's identifier is
rad:z2zFuqA4NbWoKuX8FoSnwGZ1n2XU3
and rad clone rad:z2zFuqA4NbWoKuX8FoSnwGZ1n2XU3 fetches it from any node that
seeds it. Any of the three is the whole project.
Adding it to a project
$ zig fetch --save git+https://git.jcollie.dev/jeff/zig-dns.git
and then, in build.zig:
const dns = b.dependency("dns", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("dns", dns.module("dns"));
main needs Zig 0.17.0. For Zig 0.16.0, use the v0.1.0 tag, the last
release of zig-dns to build with it:
$ zig fetch --save git+https://git.jcollie.dev/jeff/zig-dns.git#v0.1.0
$ git clone -b v0.1.0 https://git.jcollie.dev/jeff/zig-dns.git
The zig-0.16 branch points at the same commit. It is kept for 0.16 users
rather than developed; new work lands on main only.
Quick start
Reading:
const dns = @import("dns");
const message = try dns.Message.parse(bytes);
std.debug.print("{f}\n", .{message}); // prints it the way dig does
var it = message.answers();
while (try it.next()) |record| {
switch (try record.data()) {
.a => |address| std.debug.print("{f}\n", .{address}),
.cname => |name| std.debug.print("{f}\n", .{name}),
else => {},
}
}
Writing:
var buf: [512]u8 = undefined;
var builder = try dns.Builder.init(&buf, .{
.id = 0x1234,
.recursion_desired = true,
}, .{});
try builder.question(.{
.name = dns.Name.literal("example.com"),
.type = .a,
});
try builder.opt(.{}); // EDNS(0), without which almost nothing works
const query = builder.finish();
Name.literal encodes and validates the name at compile time, so a typo is a
compile error and it needs no buffer. For a name that is not known until run
time there is Name.fromText, which writes into a Name.Buffer you supply:
var names: dns.Name.Buffer = undefined;
const name = try dns.Name.fromText(&names, whatever_the_user_typed);
What is in it
Message |
the four sections, with each one's offset found in a single validating pass |
Header |
the twelve bytes, and every flag, readable on their own when the rest cannot be trusted |
Name |
labels, compression pointers, case-insensitive comparison and hashing, canonical order, the presentation escaping, and reading one back — absolute, or relative to an origin |
Record |
an owner, a type, a TTL, and data that is only decoded when asked for |
rdata |
seventy-nine record types, and RFC 3597 opaque data for everything else |
edns |
the opt pseudo-record: payload size, extended response codes, cookies, client subnet, extended errors |
Builder |
encoding, with name compression and an atomic record that rolls back rather than half-filling a buffer |
serial |
RFC 1982 arithmetic: comparing two numbers that wrap, which an soa serial does and < gets wrong at the one moment it matters |
tsig |
the bytes a TSIG signature covers, written into a writer — which fields, in which order, unsigned again and with the identifier put back. The keyed hash that consumes them is somebody else's |
sig0 |
the same for SIG(0): the record data with the signature omitted, then the message before the signature was added. The public key operation that consumes them is somebody else's too |
update |
the nine shapes a dynamic update takes — which class, which TTL and which empty rdata says "delete this RRset" rather than "add a record with nothing in it" — as two unions with one variant per subsection of §2.4 and §2.5 |
Seventy-nine record types have a parser of their own — every type dnspython parses, less the five that are only ever questions:
| addresses and names | A AAAA NS CNAME DNAME PTR SOA MX SRV NAPTR NSAP-PTR MD MF MB MG MR |
| text | TXT SPF HINFO RP MINFO NINFO AVC RESINFO WALLET X25 ISDN GPOS |
| service and policy | SVCB HTTPS CAA URI KX LP AFSDB RT PX |
| DNSSEC | DS CDS DLV TA DNSKEY CDNSKEY KEY RRSIG SIG NSEC NSEC3 NSEC3PARAM NXT |
| keys published for elsewhere | SSHFP TLSA SMIMEA CERT IPSECKEY OPENPGPKEY DHCID HIP AMTRELAY HHIT BRID |
| zone maintenance | CSYNC ZONEMD DSYNC |
| pseudo-records | OPT TSIG TKEY |
| identifiers and locators | NID L32 L64 EUI48 EUI64 APL LOC WKS A6 NSAP NULL UNSPEC |
Anything else decodes as unknown and re-encodes byte for byte, because RFC
3597 requires that a resolver carry a type nobody has told it about. The six
types that never have record data are refused instead: the five QTYPEs —
ANY, AXFR, IXFR, MAILA, MAILB — which name a question, and NXNAME
(RFC 9824), which only ever appears
in an NSEC's type bitmap. There is no such record, so data says so rather
than handing back bytes that cannot mean anything.
What it will not do
No cryptography, and neither tsig nor sig0 is an exception. RFC 8945
§4.3 and RFC 2931 §3.1 say which bytes a signature covers, in which order, with
the message unsigned again first — and that is serialisation, which is what
this library is for. So both write those bytes into an Io.Writer and stop: point the writer at an
HMAC and you have a signer, point it at Writer.fixed and you have the digest
input in your hand, which is the only way a signature bug is ever found. What
the writer does with them is not a message library's business.
rdata.dnssec decodes every DNSSEC record type and
Dnskey.keyTag does the arithmetic that links a signature to the key that
made it, but nothing here verifies one, and Tsig is decoded without its MAC
ever being checked. Builder refuses to compress names in anything signed so
that what a validator is handed is what was signed.
No zone files. Everything has a format that writes the presentation
form, because a decoded message that cannot be printed is hard to trust, and
nothing here reads one back — with one exception, which is names.
Name.fromText and Name.fromTextRelative exist because a name is what a
program has the moment somebody types one, long before any zone file is
involved: a resolver needs it for its search list and a zone file reader needs
it for $ORIGIN, and neither should have to write the escape rules again. The
rest of the presentation format — the record types, the directives, the
grammar — is zig-dns-zone, which
is built on this.
No I/O, and no resolver. No retry, no truncation-and-retry-over-TCP, no cache, no root hints, no happy eyeballs. Those need a policy and a clock and this needs neither.
Three things worth knowing
Everything borrows. A decoded Message is a view over the buffer it was
read from, and so is every Name and every record in it. A name can be
compressed, meaning its labels are not where it appears to be but somewhere
earlier in the message — which is why a Name holds the whole message and an
offset, and why Name.clone exists for when one has to outlive the buffer.
Running out of room is normal. A DNS server does not decide how big its
answer will be, it discovers it. So Builder.record is atomic: on
error.NoSpace the builder is exactly as it was, with no half-written record
and no compression pointer aimed at bytes that were rolled back. Filling a
response is a loop and a catch:
for (answers) |rr| builder.record(.answer, rr) catch |err| switch (err) {
error.NoSpace => {
builder.setTruncated();
break;
},
else => return err,
};
Decoding is strict about framing and lenient about meaning. A message
whose sections do not add up, a record whose length disagrees with its type, a
compression pointer that leads anywhere but to a name wholly before it, or one
inside a record type whose specification says its names are written out in
full — all refused. A record type nobody knows, a response code nobody has
defined, a class that is not IN — all carried through unchanged, because a
library that cannot hold what it does not understand cannot be a forwarder, a
proxy, or a debugging tool.
Where a rule falls between those two is written down. A rule that holds
whatever a record's fields mean — a bitmap window ending in a zero byte, an
SVCB port that is not two bytes — is a shape rule, and breaking one is
refused. A rule that depends on what a registry number currently means — the
digest length a TLSA matching type implies — is a meaning rule, and a new
number could make breaking it correct tomorrow, so it is carried.
docs/adr/ holds the reasoning for each such decision and
GLOSSARY.md the words they are argued in.
RFC 1035 says a pointer names "a prior occurrence of the same name", and the pointer rule is the strict reading of prior: the name a pointer leads to must end before the stretch of name holding the pointer began, and must start with a label rather than another pointer. No compressor does otherwise, and the two together keep reading any name to at most 127 jumps, where a crafted message could otherwise make one name — and every record naming it — take thousands.
Building
zig build test runs everything; zig build check compiles the parts no test
runs. IPv4 and IPv6 addresses are parsed and formatted by
z46, which is the only dependency.
$ zig build test --summary all
$ zig build docs && zig build docs-serve # http://127.0.0.1:8000/
Under Nix, nix develop -c zig build test uses the toolchain in flake.nix
rather than whatever is on the path.
Testing
Beyond the unit tests beside each file, tests/messages.zig holds messages
captured verbatim from a real resolver — a signed DNSKEY set, an HTTPS
record with four parameters, a denial of existence — and checks both that they
decode and that they survive being built again. A round trip through code that
agrees with itself proves nothing about whether it agrees with the rest of the
internet.
Checking it against something else
Every test above is this library agreeing with itself, which cannot catch the
decoder and the encoder being wrong the same way. zig build differential
puts a few hundred thousand mutations of the captured messages to this library
and to dnspython, and compares whether each
accepted the message and what it found in it:
$ nix develop -c zig build differential
200000 messages from seed 1: both accept 33445, both refuse 165839, disagree 742
...
Every disagreement is one that is written down.
The disagreements that remain are strictness choices rather than defects, and
each is recorded in tests/differential.py with the reason — this library
refusing a record whose length does not match its type where dnspython keeps
the bytes as opaque data; dnspython refusing record types this library carries
through as unknown; dnspython following up to sixteen compression pointers
in a row where this library refuses a pointer that leads to another. A kind
of disagreement
that is not written down fails the run, so a change to what is accepted has
to be looked at rather than absorbed.
nix flake check runs the same comparison as a build, with the Zig
dependency coming from build.zig.zon.nix rather than
from the network, so it needs nothing on the path. Regenerate that file with
nix develop -c zon2nix --17 --nix=build.zig.zon.nix build.zig.zon whenever a
dependency moves.
Fuzzing
tests/fuzz.zig holds the properties that must survive any input at all: that
decoding terminates, stays inside its buffer, and round-trips whatever it
claims to have understood. Zig's own fuzzer runs them with coverage feedback:
$ zig build fuzz --fuzz # until interrupted
$ zig build fuzz --fuzz=1M # a bounded run
$ zig build fuzz --fuzz -Dfuzz-filter=names # one target
zig build fuzz-run is a loop of our own over the same targets, with a corpus
in place of coverage feedback. It does a fixed amount of work from a seed that
can be given again, which is what CI runs, and replays a single input:
$ zig build fuzz-run # a minute of each target
$ zig build fuzz-run -- --seconds 300 --target names
$ zig build fuzz-run -- --input fuzz-findings/x.bin --target names
Contributing
Issues and pull requests go to the Tangled mirror,
https://tangled.org/jcollie.dev/zig-dns. CONTRIBUTING.md
says what a change needs before it is sent — above all, how to change what
the decoder accepts without the comparison with dnspython quietly absorbing
it.
License
MIT. The project follows the REUSE standard; reuse lint checks it.
References cited
The documents this library is written against, in the RFC citation format so
that a reference here matches one anywhere else. Each entry says what the
document is and what in src/ is written from it.
- [RFC822] Crocker, D., "STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT
MESSAGES", STD 11, RFC 822, August 1982,
https://www.rfc-editor.org/info/rfc822. The mail address form that one
half of a
pxrecord maps to an X.400 one. - [RFC973] Mockapetris, P., "Domain system changes and observations",
RFC 973, January 1986, https://www.rfc-editor.org/info/rfc973. What made
mdandmfobsolete in favor ofmx. Both still decode, as the domain name they always were. - [RFC1035] Mockapetris, P., "Domain names - implementation and
specification", STD 13, RFC 1035, November 1987,
https://www.rfc-editor.org/info/rfc1035. The format itself: the header,
the four sections, labels and compression pointers (§4.1.4), the
character-string (§3.3), the 512-byte UDP limit and the two-byte TCP length
prefix (§4.2), the presentation escaping (§5.1), and §3.3.14's "one or
more" strings in a
TXT. Its "a prior occurrence of the same name" is the sentence the pointer rule is the strict reading of. - [RFC1183] Everhart, C., Mamakos, L., Ullmann, R., and P. Mockapetris,
Ed., "New DNS RR Definitions", RFC 1183, October 1990,
https://www.rfc-editor.org/info/rfc1183.
AFSDB,RP,X25,ISDNandRT, and §3's rules for what an X25 or ISDN address holds: decimal digits from the DNIC on for X25, any IA5 character for an ISDN address, and hexadecimal digits for its subaddress. - [RFC1712] Farrell, C., Schulze, M., Pleitner, S., and D. Baldoni, "DNS
Encoding of Geographical Location", RFC 1712, November 1994,
https://www.rfc-editor.org/info/rfc1712.
GPOS, three decimal strings, whichLOCreplaced with something a machine can compare. Its text gives latitude and longitude each other's ranges, which is one reason a value out of range is carried rather than judged. - [RFC1876] Davis, C., Vixie, P., Goodwin, T., and I. Dickinson, "A Means
for Expressing Location Information in the Domain Name System", RFC 1876,
January 1996, https://www.rfc-editor.org/info/rfc1876.
LOC, its base-and-exponent sizes with each half a digit from zero to nine, and §2's instruction to treat a version this does not know as data of unknown meaning. - [RFC1982] Elz, R. and R. Bush, "Serial Number Arithmetic", RFC 1982,
August 1996, https://www.rfc-editor.org/info/rfc1982. How an
soaserial is compared, which is not how a number is:serialis §3.2's comparison, including the pair of serials half the space apart that §3.2 leaves deliberately unordered and that this reports asnullrather than deciding. - [RFC1996] Vixie, P., "A Mechanism for Prompt Notification of Zone Changes
(DNS NOTIFY)", RFC 1996, August 1996,
https://www.rfc-editor.org/info/rfc1996. The
notifyopcode. - [RFC2136] Vixie, P., Ed., Thomson, S., Rekhter, Y., and J. Bound,
"Dynamic Updates in the Domain Name System (DNS UPDATE)", RFC 2136,
April 1997, https://www.rfc-editor.org/info/rfc2136. The
updateopcode, which renames the four sections rather than changing them, andupdateentire: §2.4 and §2.5's nine shapes, which are encodings rather than ideas — the class, the TTL and the emptiness of the rdata are what tell a deletion from an addition — and §3.4.1's prescan, which a requestor can run on its own changes before spending a round trip learningnotzone. Decoding one stays the caller's: a message library that reinterpreted the sections by opcode would be deciding something, which is one of the recorded disagreements with dnspython. - [RFC2163] Allocchio, C., "Using the Internet DNS to Distribute MIXER
Conformant Global Address Mapping (MCGAM)", RFC 2163, January 1998,
https://www.rfc-editor.org/info/rfc2163.
PX. - [RFC2181] Elz, R. and R. Bush, "Clarifications to the DNS
Specification", RFC 2181, July 1997,
https://www.rfc-editor.org/info/rfc2181. §5.2, that records in one RRset
whose TTLs differ are not thereby different records, which is why
Recordcompares as it does. - [RFC2308] Andrews, M., "Negative Caching of DNS Queries (DNS NCACHE)",
RFC 2308, March 1998, https://www.rfc-editor.org/info/rfc2308. What the
last field of an
soahas meant since: how long a denial may be cached, rather than a default TTL. - [RFC2535] Eastlake 3rd, D., "Domain Name System Security Extensions",
RFC 2535, March 1999, https://www.rfc-editor.org/info/rfc2535.
KEY,SIGandNXT— the DNSSEC that came before RFC 4034's, all three of which still decode here. - [RFC2782] Gulbrandsen, A., Vixie, P., and L. Esibov, "A DNS RR for
specifying the location of services (DNS SRV)", RFC 2782, February 2000,
https://www.rfc-editor.org/info/rfc2782.
SRV, and its "name compression is not to be used for this field". - [RFC2874] Crawford, M. and C. Huitema, "DNS Extensions to Support IPv6
Address Aggregation and Renumbering", RFC 2874, July 2000,
https://www.rfc-editor.org/info/rfc2874.
A6, an address with its prefix somewhere else. - [RFC2930] Eastlake 3rd, D., "Secret Key Establishment for DNS (TKEY
RR)", RFC 2930, September 2000,
https://www.rfc-editor.org/info/rfc2930.
TKEY, the other half ofTSIG. - [RFC2931] Eastlake 3rd, D., "DNS Request and Transaction Signatures
( SIG(0)s )", RFC 2931, September 2000,
https://www.rfc-editor.org/info/rfc2931.
sig0: §3's constants — type covered zero, the root as the owner, classany— and §3.1's two-part data, the record's own rdata with the signature omitted rather than zeroed, followed by the message as it stood before the signature was added to it. - [RFC3007] Wellington, B., "Secure Domain Name System (DNS) Dynamic
Update", RFC 3007, November 2000,
https://www.rfc-editor.org/info/rfc3007. What ties
updatetotsigandsig0: an update is authenticated by a signature on the request rather than by anything inside it, which is why none of the three knows about the other two. - [RFC3123] Koch, P., "A DNS RR Type for Lists of Address Prefixes (APL
RR)", RFC 3123, June 2001, https://www.rfc-editor.org/info/rfc3123.
APL, and §4.1's address with its trailing zero bytes left off. - [RFC3403] Mealling, M., "Dynamic Delegation Discovery System (DDDS) Part
Three: The Domain Name System (DNS) Database", RFC 3403, October 2002,
https://www.rfc-editor.org/info/rfc3403.
NAPTR, whose replacement field §4.1 says is not compressed. - [RFC3425] Lawrence, D., "Obsoleting IQUERY", RFC 3425, November 2002, https://www.rfc-editor.org/info/rfc3425. Why opcode 1 is named and has no meaning.
- [RFC3597] Gustafsson, A., "Handling of Unknown DNS Resource Record (RR)
Types", RFC 3597, September 2003,
https://www.rfc-editor.org/info/rfc3597. Why
unknownis not a failure case: an unrecognized type is carried byte for byte. §4 forbids compressing a name in anything defined after RFC 1035, which is whatBuilderobeys, what makes a signed record safe to re-encode, and what the decoder refuses a pointer against — except in the short list §4 asks a reader to decompress anyway; §5 is the\# length hexpresentation form and theTYPE1234andCLASS42spellings. - [RFC4025] Richardson, M., "A Method for Storing IPsec Keying Material in
DNS", RFC 4025, March 2005, https://www.rfc-editor.org/info/rfc4025.
IPSECKEY. - [RFC4034] Arends, R., Austein, R., Larson, M., Massey, D., and S. Rose,
"Resource Records for the DNS Security Extensions", RFC 4034, March 2005,
https://www.rfc-editor.org/info/rfc4034.
DS,DNSKEY,RRSIGandNSEC: the key tag arithmetic of Appendix B, the type bitmap of §4.1.2 with each window ending at its last nonzero byte, the uncompressed signer of §3.1.7, the serial comparison of an expiry time in §3.1.5, and the canonical name order of §6.1 thatName.orderimplements. - [RFC4343] Eastlake 3rd, D., "Domain Name System (DNS) Case Insensitivity
Clarification", RFC 4343, January 2006,
https://www.rfc-editor.org/info/rfc4343. Why
Name.eqlandName.hashignore case, and over which bytes. - [RFC4398] Josefsson, S., "Storing Certificates in the Domain Name System
(DNS)", RFC 4398, March 2006, https://www.rfc-editor.org/info/rfc4398.
CERT. - [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, October 2006, https://www.rfc-editor.org/info/rfc4648. The alphabets the presentation forms are written in, §7's extended hex one among them.
- [RFC5001] Austein, R., "DNS Name Server Identifier (NSID) Option",
RFC 5001, August 2007, https://www.rfc-editor.org/info/rfc5001. The
nsidoption, which says which node answered. - [RFC5155] Laurie, B., Sisson, G., Arends, R., and D. Blacka, "DNS
Security (DNSSEC) Hashed Authenticated Denial of Existence", RFC 5155,
March 2008, https://www.rfc-editor.org/info/rfc5155.
NSEC3andNSEC3PARAM, the one to 255 bytes §3.1.6 gives the next hashed owner name, and the unpadded base32hex their hashes are printed in. - [RFC6563] Jiang, S., Conrad, D., and B. Carpenter, "Moving A6 to
Historic Status", RFC 6563, March 2012,
https://www.rfc-editor.org/info/rfc6563. Why
A6is decoded but nothing should send one. - [RFC6672] Rose, S. and W. Wijngaards, "DNAME Redirection in the DNS",
RFC 6672, June 2012, https://www.rfc-editor.org/info/rfc6672.
DNAME, whose target is not compressed even though it looks like aCNAME's. - [RFC6742] Atkinson, RJ, Bhatti, SN, and S. Rose, "DNS Resource Records
for the Identifier-Locator Network Protocol (ILNP)", RFC 6742,
November 2012, https://www.rfc-editor.org/info/rfc6742.
NID,L32,L64andLP. - [RFC6840] Weiler, S., Ed. and D. Blacka, Ed., "Clarifications and
Implementation Notes for DNS Security (DNSSEC)", RFC 6840, February 2013,
https://www.rfc-editor.org/info/rfc6840. §5.7, what the
DObit means in a query rather than a response. - [RFC6891] Damas, J., Graff, M., and P. Vixie, "Extension Mechanisms for
DNS (EDNS(0))", STD 75, RFC 6891, April 2013,
https://www.rfc-editor.org/info/rfc6891. The
optpseudo-record, which is where a message keeps everything the twelve-byte header had no room for: exactly one of them (§6.1.1), owned by the root (§6.1.2), carrying the payload size, the upper bits of the response code, and the option list. - [RFC6975] Crocker, S. and S. Rose, "Signaling Cryptographic Algorithm
Understanding in DNS Security Extensions (DNSSEC)", RFC 6975, July 2013,
https://www.rfc-editor.org/info/rfc6975. The
dau,dhuandn3uoptions. - [RFC7208] Kitterman, S., "Sender Policy Framework (SPF) for Authorizing
Use of Domains in Email, Version 1", RFC 7208, April 2014,
https://www.rfc-editor.org/info/rfc7208. Why the
SPFtype is decoded and deprecated: the policy went back to living in aTXT. - [RFC7301] Friedl, S., Popov, A., Langley, A., and E. Stephan, "Transport
Layer Security (TLS) Application-Layer Protocol Negotiation Extension",
RFC 7301, July 2014, https://www.rfc-editor.org/info/rfc7301. Why an
alpnparameter may not hold an empty protocol name: §3.1 gives TLS none. - [RFC7314] Andrews, M., "Extension Mechanisms for DNS (EDNS) EXPIRE
Option", RFC 7314, July 2014,
https://www.rfc-editor.org/info/rfc7314. The
expireoption. - [RFC7477] Hardaker, W., "Child-to-Parent Synchronization in DNS",
RFC 7477, March 2015, https://www.rfc-editor.org/info/rfc7477.
CSYNC. - [RFC7553] Faltstrom, P. and O. Kolkman, "The Uniform Resource Identifier
(URI) DNS Resource Record", RFC 7553, June 2015,
https://www.rfc-editor.org/info/rfc7553.
URI, whose target §4.5 says must not be empty — one of the length-versus-type checks this library makes and dnspython does not. - [RFC7828] Wouters, P., Abley, J., Dickinson, S., and R. Bellis, "The
edns-tcp-keepalive EDNS0 Option", RFC 7828, April 2016,
https://www.rfc-editor.org/info/rfc7828. The
tcp_keepaliveoption. - [RFC7830] Mayrhofer, A., "The EDNS(0) Padding Option", RFC 7830,
May 2016, https://www.rfc-editor.org/info/rfc7830. The
paddingoption, which carries nothing and is the point. - [RFC7858] Hu, Z., Zhu, L., Heidemann, J., Mankin, A., Wessels, D., and P. Hoffman, "Specification for DNS over Transport Layer Security (TLS)", RFC 7858, May 2016, https://www.rfc-editor.org/info/rfc7858. Port 853, which is the whole of what this library takes from it.
- [RFC7871] Contavalli, C., van der Gaast, W., Lawrence, D., and
W. Kumari, "Client Subnet in DNS Queries", RFC 7871, May 2016,
https://www.rfc-editor.org/info/rfc7871. The
client_subnetoption, and §6's rule that only the significant bits of the address are sent, with the rest of the last byte zero. - [RFC7873] Eastlake 3rd, D. and M. Andrews, "Domain Name System (DNS)
Cookies", RFC 7873, May 2016,
https://www.rfc-editor.org/info/rfc7873. The
cookieoption and thebadcookieresponse code. - [RFC7901] Wouters, P., "CHAIN Query Requests in DNS", RFC 7901,
June 2016, https://www.rfc-editor.org/info/rfc7901. The
chainoption. - [RFC8005] Laganier, J., "Host Identity Protocol (HIP) Domain Name System
(DNS) Extension", RFC 8005, October 2016,
https://www.rfc-editor.org/info/rfc8005.
HIP. - [RFC8145] Wessels, D., Kumari, W., and P. Hoffman, "Signaling Trust
Anchor Knowledge in DNS Security Extensions (DNSSEC)", RFC 8145, April 2017,
https://www.rfc-editor.org/info/rfc8145. The
key_tagoption. - [RFC8490] Bellis, R., Cheshire, S., Dickinson, J., Dickinson, S.,
Lemon, T., and T. Pusateri, "DNS Stateful Operations", RFC 8490, March 2019,
https://www.rfc-editor.org/info/rfc8490. The
dsoopcode and thedsotypeniresponse code. - [RFC8659] Hallam-Baker, P., Stradling, R., and J. Hoffman-Andrews, "DNS
Certification Authority Authorization (CAA) Resource Record", RFC 8659,
November 2019, https://www.rfc-editor.org/info/rfc8659.
CAA, and §4.1's tag of one to fifteen letters and digits. - [RFC8749] Mekking, W. and D. Mahoney, "Moving DNSSEC Lookaside
Validation (DLV) to Historic Status", RFC 8749, March 2020,
https://www.rfc-editor.org/info/rfc8749. Why
DLVis decoded and decommissioned. - [RFC8777] Holland, J., "DNS Reverse IP Automatic Multicast Tunneling
(AMT) Discovery", RFC 8777, April 2020,
https://www.rfc-editor.org/info/rfc8777.
AMTRELAY. - [RFC8914] Kumari, W., Hunt, E., Arends, R., Hardaker, W., and
D. Lawrence, "Extended DNS Errors", RFC 8914, October 2020,
https://www.rfc-editor.org/info/rfc8914. The
extended_erroroption: the difference between a server that cannot answer and a server that will not. - [RFC8945] Dupont, F., Morris, S., Vixie, P., Eastlake 3rd, D.,
Gudmundsson, O., and B. Wellington, "Secret Key Transaction Authentication
for DNS (TSIG)", STD 93, RFC 8945, November 2020,
https://www.rfc-editor.org/info/rfc8945.
TSIG, decoded without its MAC ever being checked, and never compressed on the way out so that a verifier sees what was signed. §4.3 is whattsigwrites: the request's MAC, the message with its signature removed and its identifier put back, and the variables — or, for the second and later messages of a transfer, §5.3.1's timers alone. §4.2's error field is its own registry, where 16 isbadsigand not thebadversthe same number means in a header, which is whyTsig.ErrorCodeexists. - [RFC8976] Wessels, D., Barber, P., Weinberg, M., Kumari, W., and
W. Hardaker, "Message Digest for DNS Zones", RFC 8976, February 2021,
https://www.rfc-editor.org/info/rfc8976.
ZONEMD, and the twelve bytes §2.2.4 sets as the shortest digest. - [RFC9276] Hardaker, W. and V. Dukhovni, "Guidance for NSEC3 Parameter
Settings", BCP 236, RFC 9276, August 2022,
https://www.rfc-editor.org/info/rfc9276. Why zero iterations and an empty
salt are what an
NSEC3PARAMshould say. - [RFC9460] Schwartz, B., Bishop, M., and E. Nygren, "Service Binding and
Parameter Specification via the DNS (SVCB and HTTPS Resource Records)",
RFC 9460, November 2023, https://www.rfc-editor.org/info/rfc9460.
SVCBandHTTPS: the strictly increasing key order of §2.2, the shape of each parameter's value in §7, themandatorycheck of §8, alias mode and the parameters §2.4.2 says to ignore there, and thekeyNNNNNspelling of §2.1. - [RFC9461] Schwartz, B., "Service Binding Mapping for DNS Servers",
RFC 9461, November 2023, https://www.rfc-editor.org/info/rfc9461. The
dohpathservice parameter. - [RFC9540] Pauly, T. and T. Reddy.K, "Discovery of Oblivious Services via
Service Binding Records", RFC 9540, February 2024,
https://www.rfc-editor.org/info/rfc9540. The
ohttpservice parameter. - [RFC9567] Arends, R. and M. Larson, "DNS Error Reporting", RFC 9567,
April 2024, https://www.rfc-editor.org/info/rfc9567. The
report_channeloption. - [RFC9606] Reddy.K, T. and M. Boucadair, "DNS Resolver Information",
RFC 9606, June 2024, https://www.rfc-editor.org/info/rfc9606.
RESINFO. - [RFC9660] Salgado, H., Vergara, M., and D. Wessels, "The DNS Zone Version
(ZONEVERSION) Option", RFC 9660, October 2024,
https://www.rfc-editor.org/info/rfc9660. The
zoneversionoption. - [RFC9715] Fujiwara, K. and P. Vixie, "IP Fragmentation Avoidance in DNS over UDP", RFC 9715, January 2025, https://www.rfc-editor.org/info/rfc9715. Why the payload size to advertise is 1232 rather than as much as possible.
- [RFC9824] Huque, S., Elmerot, C., and O. Gudmundsson, "Compact Denial
of Existence in DNSSEC", RFC 9824, September 2025,
https://www.rfc-editor.org/info/rfc9824.
NXNAME, a type number that is only ever a bit in anNSECtype bitmap and so never has record data. - [RFC9848] Schwartz, B., Bishop, M., and E. Nygren, "Bootstrapping TLS
Encrypted ClientHello with DNS Service Bindings", RFC 9848, March 2026,
https://www.rfc-editor.org/info/rfc9848. The
echservice parameter, which holds an ECHConfigList. - [RFC9849] Rescorla, E., Oku, K., Sullivan, N., and C. A. Wood, "TLS Encrypted Client Hello", RFC 9849, March 2026, https://www.rfc-editor.org/info/rfc9849. The ECHConfigList itself, whose length prefix is all of it the decoder checks.
- [RFC9859] Stenstam, J., Thomassen, P., and J. Levine, "Generalized DNS
Notifications", RFC 9859, September 2025,
https://www.rfc-editor.org/info/rfc9859.
DSYNC, and the generalized notify that has a parent re-check aCDSorCSYNCwithout waiting for a scan. - [RFC9886] Wiethuechter, A., Ed. and J. Reid, "DRIP Entity Tags (DETs)
in the Domain Name System", RFC 9886, December 2025,
https://www.rfc-editor.org/info/rfc9886.
HHITandBRID, a drone's identity and broadcast data as CBOR, carried as base64. - [Z46] Ollie, J., "z46 — IPv4 and IPv6 addresses, networks and endpoints
for Zig", 2026, https://git.jcollie.dev/jeff/z46. The only dependency:
every address this library hands back — in an
a, anaaaa, awks, anapl, anipseckey, an SVCB address hint or a client subnet option — is one of its types, parsed and formatted by it. - [BIND9] Internet Systems Consortium, "BIND 9", version 9.20.30,
https://www.isc.org/bind/. The reference implementation whose
digprints messages the wayformatdoes here; the names given to EDNS options are the ones inoption_namesin itslib/dns/message.c. - [DNSPYTHON] Halley, B. and the dnspython contributors, "dnspython", version 2.9.1, https://www.dnspython.org/. The second decoder the differential test compares against, message by message.