In the summer of 2008, Dan Kaminsky made a lot of people cancel their vacations. The attack he’d found let an off-path attacker — someone who couldn’t see your traffic, just guess at it — poison a recursive resolver’s cache and redirect a whole domain. The scramble that followed produced the fix everyone remembers: source port randomization. A DNS query already carried a 16-bit transaction ID an attacker had to guess; now it also went out from a random source port, so a forger had to guess the ID and the port, turning a few thousand guesses into a few billion. RFC 5452 wrote it down in 2009, resolvers shipped it, and the immediate crisis passed.
But randomizing the source port was always a workaround dressed as a fix. It doesn’t authenticate anything. It just makes the lottery harder to win, and against a fast enough attacker on a big enough pipe, harder is not the same as impossible. There was a better idea sitting right there — cheap, stateless, and general — and in 2016 it finally got a standard. Almost nobody turned it on.
What a DNS cookie actually is
A DNS cookie is not a browser cookie, and it has nothing to do with tracking. It’s an EDNS option — option code 10 — that rides along in the query and response. RFC 7873, published in 2016, describes it plainly as “a lightweight DNS transaction security mechanism that provides limited protection to DNS servers and clients against a variety of denial-of-service, amplification, forgery, or cache-poisoning attacks by off-path attackers.”
The word doing the work in that sentence is lightweight. The whole mechanism is two small opaque byte strings:
- The client cookie — 8 bytes the client picks and sends. The server echoes it back.
- The server cookie — 8 to 32 bytes the server generates and hands to the client, which stores it and includes it on the next query.
That’s it. No shared keys to distribute, no certificates, no clocks to keep in sync. And that minimalism is the entire reason it exists, because we already had a cryptographic way to authenticate DNS transactions — TSIG — and precisely because it needs shared secret keys configured on both ends, almost nobody runs it between strangers. Cookies were designed to be the thing you could leave on by default without a key-management project. A speed bump, not a vault. But a speed bump in exactly the right place.
How two cheap byte strings stop two different attacks
The clever part is that the client cookie and the server cookie defend against different things.
The client cookie protects the person asking. When your resolver sends a query with a client cookie, a legitimate server echoes it back in the answer. An off-path attacker forging a response can’t see that cookie — it’s not on the wire where they’re guessing — so their forgery comes back with the wrong cookie or none at all, and the resolver drops it. It’s the same idea as the random source port, except an off-path attacker now has to guess 64 additional bits that change every query. That’s not “harder.” That’s over.
The server cookie protects the server, and this is the half that matters for the internet’s larger problem. DNS over UDP is the internet’s favorite amplifier: send a small query with a forged source address, and the server blasts a much larger answer at the victim you named. The server cookie breaks the con. A server can be told to require a valid server cookie before it answers a stranger over UDP. A first-time client gets a small response — BADCOOKIE, extended RCODE 23 — carrying a fresh cookie and an instruction to come back with it. A real client does. A spoofed source address can’t, because the BADCOOKIE reply goes to the forged victim, not the attacker, and the handshake never completes. No completed handshake, no amplified answer. You’ve turned an open reflector into one that only amplifies for clients that can prove they exist.
The gap between “supports” and “requires”
Here’s where it goes quietly wrong.
BIND — the most widely deployed authoritative and recursive server there is — turned DNS cookies on by default back in version 9.11, in 2016. Out of the box it sends cookies on its queries and answers cookies on the queries it receives. If you run a modern resolver, you are almost certainly speaking the protocol right now.
But sending and answering cookies is not the same as requiring them, and requiring is where the protection lives. BIND’s require-server-cookie — the switch that says “no valid cookie, no UDP answer,” the one that actually shuts down reflection — defaults to no. The default posture is to accept a cookie and never insist on one. And a cookie you accept but never demand is theater: you’ve paid the bytes-on-the-wire cost and bought almost none of the defense, because an attacker simply omits the cookie and you answer anyway.
This is the part I find genuinely frustrating. The mechanism isn’t stuck in a draft. It isn’t waiting on browser vendors or a flag day. It shipped, it’s enabled, it’s on the wire between millions of servers — and it’s idling in the one mode that costs interoperability headaches while delivering the least security.
Why it idled
The honest answer isn’t pure negligence. For years, cookies had a real interoperability problem that gave operators a legitimate reason to leave enforcement off.
Server cookies are computed from a secret. That’s fine on a single box. But most serious DNS lives behind anycast or load balancers — dozens of machines answering for one IP address. If each machine computes its server cookie with its own secret, a client that gets its cookie from machine A and comes back to machine B gets rejected, and enforcement turns your own infrastructure into the attacker you were guarding against. The original 2016 spec left the server cookie’s construction unspecified, so implementations diverged and clusters couldn’t agree.
RFC 9018 fixed that in 2021 by pinning down an interoperable server cookie: a defined construction using SipHash-2-4 over the client’s address and a shared secret, so every machine in a cluster computes the same cookie and a client can bounce between them freely. That removed the last good excuse. It also arrived five years after the original, into an internet that had already filed DNS cookies under “supported, ignore,” and defaults are sticky. Nobody schedules a maintenance window to enable a feature that’s technically already on.
Shipped, present, and idling
I keep a mental taxonomy of DNS security features by how they actually fared. DNSSEC shipped and mostly didn’t matter — twenty-odd years in, most zones still aren’t signed, because it demanded a key-management commitment nobody wanted. QNAME minimization shipped and did matter — it was a resolver-side change with no cost to anyone else, so it just quietly spread until it was everywhere. DNS cookies are a third, stranger category: shipped, present, running on millions of servers, and doing a fraction of their job because the switch that turns “present” into “protecting” defaults to off.
The fix costs one line of config on an authoritative server that already supports it — turn on require-server-cookie, watch for the handful of legacy clients that choke, and you’ve meaningfully raised the cost of spoofing your resolver and volunteered your server out of the global amplification pool. It is one of the highest ratios of protection-to-effort available in DNS, and it’s been sitting on the shelf, enabled-but-not-armed, since before half the current internet’s certificates were issued. Turn it on.