TL;DR

  • Until today our resolver filled a truncated UDP datagram with 1,023 to 1,256 bytes before setting the TC bit. Google, Cloudflare and Quad9 send a bare header on the same names. As of 1 September 2026 we send one too.
  • Fifteen of the eighteen names in our own census dropped from 1,023-1,256 bytes to 63-71. Re-measured with the published script, from the same host, after the deploy.
  • Our worst case is unchanged at 20.1×, and that is not a failure of the change. The three names that still come back whole are the ones small enough to fit, and no resolver truncates what fits. Google and Cloudflare sit at 19.6× on the same names.
  • The residual 28 bytes is our DNS Cookie, measured rather than assumed: aol.com is 1,256 bytes with it and 1,228 without, which is byte-for-byte Google's number.
  • A correction in the same pass. We wrote that Cloudflare and Quad9 behave identically to Google. Quad9 truncates paypal.com to 39 bytes where the other two send all 1,237.

What the clamp was not doing

The EDNS buffer clamp we deployed in July decides when an answer is truncated. It says nothing about how much of the answer is reflected once that decision is made, and the census we published on 31 August in Is Your AdGuard Home or Pi-hole a DNS Amplification Weapon? is what made the difference visible: on the fourteen fattest TXT names, Google returned 35 to 43 bytes and we returned over a thousand.

RFC 2181 section 9 settles which of the two is right, and it settled it in 1997: the partial record set may be left in the response, and a client receiving TC is told to ignore that response and ask again over TCP. May, not must - and the bytes are to be thrown away unread. So nothing legitimate loses anything when they are not sent. The only party who gets value from them is the one who never asked.

The change

In our dnsproxy fork, once TC is set on a plain-UDP answer the answer, authority and additional sections are dropped and only the OPT record survives. OPT is kept deliberately: it carries the DNS Cookie the client needs for return-routability on the TCP retry, and the EDNS echo RFC 6891 requires. TCP, DoT, DoQ and DoH are structurally unaffected, because TC is never set on any of them.

Before and after, same script, same host

Measured from a second machine, not from the resolver itself - this one drops fragmented UDP inbound, so a census run on it prints zeroes that read like refusals.

  • cisco, sony, adobe, intel, amazon, microsoft, hp, samsung, oracle, ibm, salesforce, ebay, cloudflare, apple, paypal - 63 to 71 bytes each, where they were 1,023 to 1,256.
  • aol.com 1,256, google.com 1,215, yahoo.com 824 - unchanged, because they fit under the clamp and are never truncated at all.

One mismatch to expect if you run it yourself against both: our google.com row read 1,215 bytes against their 1,103. That is not a size behaviour. We returned seventeen TXT records for the name where Google, Cloudflare and Quad9 all returned sixteen, and one extra TXT record is 112 bytes - which looks exactly like a truncation difference until you count records.

Correction, and then a correction to the correction. This paragraph first said the extra record was one Google had added and the others had not caught up with, and called it cache freshness. Checking the authoritative servers overturned that: at 17:36 all four of ns1 through ns4.google.com returned sixteen records and none carried the extra name. So the second version said the opposite - a withdrawn record that we alone were still handing out, re-supplied from above our caches on every refresh, since it survived a TTL crossing at two layers. Better evidenced, still wrong.

At 21:22 those four servers no longer agreed with each other. Five samples each from one host: ns1 and ns2 returned sixteen records, ns3 and ns4 returned seventeen, every time. From a second host in a different network, all four returned sixteen. The addresses are identical from both vantages, so this is one anycast address answering with two versions of the same record set depending on which instance you reach.

Nothing was stale in our chain and nothing was being withdrawn. The record set was inconsistent across the zone's own authoritative fleet, and our resolver and the large public ones reached different halves of it. For anyone re-running the census: a byte count for a name in that state is a snapshot of which server your resolver happened to ask. Ours read 1,215 on the seventeen-record view and 1,131 on the sixteen-record one - Google's 1,103 plus our 28-byte cookie exactly. The number tracked the record set, not our behaviour.

The same instability shows up inside a resolver chain, which is what made this hard to pin down. Sampling our own three layers together, they do not always agree with each other: at 21:31 the DNSCrypt layer returned seventeen records for three consecutive samples while the caching layer above it and the edge in front of it both returned sixteen. Each cache holds whichever half it reached when it last refilled, so a reading at any one layer - or one moment when all of them happen to agree - is a snapshot of a single fetch and not a state. An earlier reading of sixteen everywhere looked exactly like the problem resolving itself, and was three caches drawing the same half in the same window.

Closed, 2 September 2026. Twenty-four hours later the fleet agrees with itself: ns1 through ns4 all return seventeen records including the extra name, five samples each from both hosts, forty readings with no sixteen among them. Google, Cloudflare and Quad9 return seventeen as well, our three layers return seventeen, and the record set is byte-identical across all of them. It closed onto the seventeen-record side, so nothing was ever being withdrawn. The census row moved with it, and only on their side: google.com now reads 1,187 bytes from Google, Cloudflare and Quad9 against our 1,215, which is 1,187 plus our 28-byte cookie exactly. Our figure never moved.

One number that is still over our own clamp

Our aol.com answer is 1,256 bytes, which is 24 more than the 1,232 we advertise. The message is 1,228 before the cookie and fits; the server cookie is appended after the truncation budget has been decided, so an answer that only just fits can end up over. It is unreachable through the truncation path, which needs an answer that is truncated at all, and the same slack shows in Google's and Cloudflare's 1,237-byte paypal.com. It is recorded as an open item rather than fixed, because the fix belongs in the cookie path.

The same 28 bytes cut the other way on paypal.com: 1,237 plus a cookie is 1,265, over the clamp, so we truncate to 67 bytes a name that Google and Cloudflare send whole.

The correction

The census section said, of Google's bare-header behaviour, that "Cloudflare and Quad9 behave identically". Cloudflare does, row for row. Quad9 does not: it truncates paypal.com to 39 bytes where Google and Cloudflare both send all 1,237. That is the only disagreement among the three across eighteen names, and the sentence is now corrected on the page rather than removed from it. The full census, the resolver comparison table and the dated update all sit in the amplification runbook.