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 65,536 possibilities into billions. 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. It got switched on almost everywhere, and put to work almost nowhere.

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 increasingly common denial-of-service and 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 also has to guess 64 bits it has never seen. That’s not “harder.” That’s over — as long as the server on the other end speaks cookies too.

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 lets a server tell a client it has really talked to from a forged address. A client that shows up with only a client cookie can be handed a small response — BADCOOKIE, extended RCODE 23 — carrying a fresh server cookie and an instruction to come back with it. A real client does. A spoofed source address can’t, because the reply goes to the forged victim, not the attacker. The catch is written into the RFC itself: a query that carries no cookie at all is answered as if the server didn’t implement cookies (RFC 7873, section 5.2.1). An attacker just leaves the option out. So the reflection defense isn’t the handshake on its own; it’s what the handshake lets you do to everyone else. Once your real clients carry valid server cookies, you can rate-limit and shrink the UDP answers to cookieless traffic hard, knowing the resolvers that matter are exempt.

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 using them, and using them is where the protection lives. BIND’s require-server-cookie — which sends BADCOOKIE instead of a full UDP answer to a cookie-aware client without a valid server cookie — defaults to no, and even when it’s on it applies only to clients that sent a cookie in the first place. The lever that actually matters for reflection is response rate limiting, which in BIND exempts clients with a valid server cookie and which you have to configure yourself; out of the box there is none. The default posture is to exchange cookies and then treat cookie-bearing and cookieless traffic exactly the same. A cookie you exchange but never act on is mostly 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 a large share of resolvers and authoritative servers — and it’s idling in the mode that pays the costs 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 to each implementation — it offered only examples — so implementations diverged and mixed 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 cookie, a timestamp and the client’s address, keyed with 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 quietly spread until the major resolvers turned it on by default. DNS cookies are a third, stranger category: shipped, present, running on a large share of DNS servers, and doing a fraction of their job because the configuration that turns “present” into “protecting” is left to the operator.

The fix is a few lines of config on an authoritative server that already speaks cookies — turn on response rate limiting so cookieless UDP gets squeezed while cookie-bearing resolvers sail through, consider require-server-cookie for the cookie-aware ones, watch for the legacy clients that choke, and you’ve made your server a much worse amplifier. It is one of the best ratios of protection-to-effort available in DNS, and it’s been sitting on the shelf, enabled-but-not-armed, since 2016. Arm it.