dns-packet
A low-level, spec-compliant encoder and decoder for binary DNS wire-format packets in Node.js, covering queries, responses, and DNSSEC/EDNS0 records.
Repository Health
Technical Analysis
dns-packet is the encoding/decoding engine underneath many Node.js DNS tools, translating between JavaScript packet objects and the raw binary wire format defined in RFC 1035 and its extensions. It handles the 12-byte DNS header, question/answer/authority/additional sections, and RFC-compliant name compression with pointer-loop protection, while exposing a symmetric encode/decode/encodingLength API for each supported record type.
Beyond basic UDP query/response packets, it ships explicit support for TCP length-prefixed framing, DNS-over-TLS, and DNS-over-HTTPS transports, plus EDNS0 (OPT) option codes like CLIENT_SUBNET and TCP_KEEPALIVE, and DNSSEC record types (DS, DNSKEY, RRSIG, NSEC/NSEC3). It powers downstream libraries like multicast-dns and is downloaded tens of millions of times a week, making it a de facto standard for hand-rolling DNS wire-format parsing in JavaScript.
What You Get
- A complete
encode/decodepair for translating structured JS objects to/from binary DNS packets - Per-record-type codec objects (
packet.txt,packet.mx,packet.caa, etc.) usable independently of the full packet shape - TCP framing helpers (
streamEncode/streamDecode) for length-prefixed DNS-over-TCP payloads - Working examples for UDP, TCP, DNS-over-TLS, and DNS-over-HTTPS transports
Common Use Cases
- Building a custom DNS resolver or forwarder over raw sockets
- Implementing mDNS/multicast service discovery
- Handling DNS-over-HTTPS or DNS-over-TLS payloads
- Encoding and parsing DNSSEC records for signing or validation tools
Under The Hood
Architecture
The module is organized around per-record-type codec objects (name, a, aaaa, txt, cname, dname, ptr, srv, hinfo, caa, ns, soa, mx, naptr, opt, dnskey, ds, sshfp, rrsig, nsec, nsec3, tlsa, and more), each exposing symmetrical encode/decode/encodingLength triplets, composed together by a top-level packet encoder/decoder in a single index.js file that assembles the header, question, and answer/authority/additional sections in RFC 1035 order. There is no class hierarchy or dependency injection — it’s a flat functional module exporting composable codec objects, with encode/decode/streamEncode (length-prefixed TCP framing)/streamDecode orchestrating a fixed 12-byte header and name-compression-aware label encoding, including pointer/jump handling with loop protection per RFC 1035 section 4.1.4. Changing the core name codec would ripple into every record type, since all of them delegate name and rdata-length prefixing back through it.
Tech Stack Plain CommonJS with zero build step, targeting older Node.js runtimes; the single runtime dependency is an IP address codec library reused across A/AAAA/OPT records for IPv4/IPv6 encoding. Dev tooling relies on a lightweight TAP-based test runner, a standard-config linter wired through npm’s pretest hook, and a coverage tool, all run again in CI on every push. There is no bundler or transpiler — the package ships the raw JS files listed explicitly in its manifest.
Code Quality A single large test file covers encode/decode round-trips for essentially every supported record type plus edge cases such as invalid TXT input types, oversized names, buffer overflows, and malicious compression pointers. Error handling is explicit — malformed input throws descriptive errors rather than failing silently — and naming is consistent across the encode/decode/encodingLength triplets. There are no static types (no TypeScript, no JSDoc annotations), so type safety relies entirely on tests and runtime checks; the codebase is linted and CI-gated on every push.
API Design
This library’s specific value is being an actively-maintained, framework-agnostic binary codec for the full DNS record surface, including modern additions like CAA, TLSA, SSHFP, and DNSSEC types, exposed through a small, symmetric API (encode/decode/streamEncode/streamDecode/encodingLength) with pluggable per-record objects addressable directly rather than only through the top-level packet shape. Getting started requires no configuration beyond installing the package and shaping a plain JS object to match a record type, though the lack of TypeScript typings and reliance on duck-typed record objects documented only in the README adds friction for newcomers.