Heimdall Domain Name Server (DNS)

❤️ (@Sully) ❤️ (@Sully)
Edited Last Month by ❤️ (@Sully)

Heimdall, the watchman of the gods, guards the Bifröst and sees every traveller who crosses it. That felt like the right name for the thing that now watches every DNS query crossing our edge.

Up until recently we have been using CoreDNS as our primary Domain Name Server however, it has its limitations in relation to being a good recursor. To minimize extra packages and more third party software needing to be installed, we have decided to go full-steam ahead and build our own lightweight minimal DNS.

Introducing Heimdall DNS

Heimdall DNS is our latest offering. We replaced a ~45 MB Go binary that used tens of megabytes of RAM with a ~100 KB static binary that idles at ~2 MB of RAM - and in the process we got the one feature we actually needed and couldn't get cleanly from CoreDNS.

Our mail platform Hermes leans on DNS blocklists like Spamhaus to score inbound mail. Here's the catch most people hit eventually: Spamhaus (and most DNSBLs) refuse queries that arrive through big shared resolvers like 1.1.1.1 or 8.8.8.8. If your mail server asks a public resolver "is this IP listed?", you get back errors or nothing.

The only reliable way to query a DNSBL is to run a recursive resolver that walks the DNS tree from the root servers using your own server's IP address. CoreDNS is primarily an authoritative server and a forwarder - to get true from-root recursion you bolt on Unbound or BIND alongside it. That's two services, two configs, two things to monitor, for one job.

Heimdall does both roles in one binary: authoritative for our zones, and a full from-root recursive resolver for everything else - querying from our IP, exactly the way the blocklists want.

Heimdall is written in C against musl, ships as a single static binary with zero runtime dependencies, and the whole thing is about 3,400 lines of code you can read in an afternoon.

The numbers

Measured on our dev build, serving 6 real zones with a 16-thread worker pool:

Heimdall DNSCoreDNS (what we ran before)
Binary size (stripped)~102 KB~45 MB
RAM (resident, idle)~2.3 MB~30 MB
RAM under query load~2 MB (stayed flat)~45 of MB + GC
Runtime dependenciesnoneGo runtime
Configone fileCorefile + plugins
Cold startinstantfast

The RAM number is the one that still makes us smile. A fully-threaded DNS server, authoritative + recursive, with an answer cache and live metrics, resident in about two megabytes. On Alpine with a static musl build it lands in the same ballpark.

What Heimdall does today

Everything we needed on day one is already shipping:

  • Split-role - authoritative for our domains (answers with the AA bit), recursive resolver for everything else.
  • Recursion you can toggle - on does the full from-root walk (referrals, glue, out-of-bailiwick nameserver resolution, CNAME chasing); off forwards to /etc/resolv.conf.
  • Recursion ACL - CIDR allow-list so we're never an open resolver. Authoritative answers are served to the world; the recursive path is gated.
  • Answer cache - honours record TTLs with 6h / 1d-style clamps, LRU eviction, and negative (NXDOMAIN/NODATA) caching.
  • Real zone files - BIND master format: SOA, NS, A, AAAA, CNAME, MX, TXT, PTR, SRV, CAA, $ORIGIN / $TTL, CNAME chains, proper SOA-in-authority for negative answers, even split multi-string TXT records (hello, DKIM keys).
  • Fault-tolerant loading - a broken zone file is logged and skipped; every other zone still loads and the server always starts. One bad file never takes the fleet down.
  • IPv4 + IPv6, UDP + TCP, EDNS0 with automatic TC→TCP fallback.
  • Per-domain metrics - query counts aggregated per registrable domain, written to a single rolling-7-day JSON file (great for "how many Spamhaus lookups did we do this week?").
  • Operable - -t to validate config, --test-zones to validate zones, -r for zero-downtime reload via SIGHUP, leveled logging.

What's coming next

We've already scoped the next batch - the goal is to make the recursive engine genuinely production-hard and add a couple of conveniences:

  1. Delegation & nameserver-IP caching - so a Spamhaus lookup jumps straight to the right nameservers instead of re-walking from the root every time. Biggest latency win on the mail path.
  2. Query retransmission / multi-server fan-out - shrug off the occasional dropped UDP packet instead of failing the lookup.
  3. Serve-stale + prefetch - hand back a just-expired answer instantly while refreshing it in the background, so the mail path never blocks on a slow upstream.
  4. Wildcard records (*.example.com) in zones.
  5. Live stats via CHAOS TXT queries - dig @heimdall version.bind-style introspection plus cache hit-rate and uptime, no extra ports.
  6. Abuse hardening - RFC 8482 minimal ANY responses now, and optional Response Rate Limiting for any internet-facing authoritative zones.
  7. CNAME flattening (ALIAS / ANAME) - let a zone apex effectively "point" at a hostname (e.g. a CDN) and answer with the flattened A/AAAA records, which plain CNAMEs can't do at the apex.

More soon!

Replies (1)

NetPeople (@NetPeople) NetPeople (@NetPeople)

Wonderfull, i hope to see the Alpha soon, can't wait to test it. 

Log in to reply.