rfc8499.json has status BEST CURRENT PRACTICE and obsoleted_by RFC 9499; rfc9499.json has pub_date March 2024. Command in dns.This is an old revision of the document!
You are about to measure names: which resolver answers, what it answers, whether the answer was true, whether it was the same answer someone else would have got. That is a different instrument from TLS certificates (which measures what a host presents once you have connected to it) and from IP classification (which measures what an address is once you already have it). This page is for the step before both: getting the address, and knowing what you can claim about how you got it.
A DNS answer is a property of the query, not only of the name. It depends on which resolver you asked, where you asked from, what your source port happened to be, and what was already in a cache. In a run on 2026-08-27 from one machine, Google, Cloudflare and Quad9 did not all return the same addresses for 29 of the 75 Tranco top-100 names that had an address at all (38.7%), and for 8 of those no two of the three shared a single address. Another 25 of the 100 had no A record at the apex at all. Details and the script are in Measure your own resolver dependence.
Everything below about “the literature” is a claim about seven venues — CCS, IMC, NDSS, PETS, USENIX Security, TheWebConf and IEEE S&P, 2010–2026, 5,859 papers with extracted full text. EuroS&P, ACSAC, RAID, AsiaCCS, CHI and SOUPS are not in it, and neither is the DNS-operations literature (DNS-OARC, RIPE, IETF). A DNS venue survey would look different; see Corpus for what this corpus is and is not.
| If your question is… | Then read |
|---|---|
| which resolver answers, what it answers, whether the answer was manipulated, how encrypted DNS changes the measurement | this page |
| what certificate a host presents, CT logs, HTTPS deployment | TLS certificates |
| what an IP address is — ASN, country, datacenter vs residential | IP classification |
| which names to resolve in the first place | Website selection, Sampling, Tranco |
| whether to crawl, scan, or unpack an app at all | Automated measurements |
| who owns a name (WHOIS, RDAP, the Public Suffix List) | not here — see Website classification and IP classification. Those answer registry questions, not resolution questions, and this page's tool inventory excludes them on purpose |
| the ethics of probing other people's resolvers | Ethics, then Active DNS measurement is scanning below |
What this page is not. It is not an explanation of what a resource record is, how delegation works, or what NS and CNAME mean. RFC 9499 is the current terminology reference (it obsoleted RFC 8499 in March 2024 — a citation to 8499 is out of date).1)
Two populations, defined before anything was counted. A: DNS is named in the paper's own title or slug — DNS is the subject. B: the paper used or produced a DNS-specific instrument — a query tool, resolver software, a public resolver, a passive-DNS dataset — DNS is the instrument. The page's population is A ∪ B = 244 papers. Generic port scanners (ZMap), RIPE Atlas, and WHOIS/RDAP/PSL are deliberately not in the membership rule; they are reported separately below.
| Population | Papers |
|---|---|
| A — DNS in the title | 108 |
| B — used or produced a DNS instrument | 202 |
| both | 66 |
| A ∪ B — this page's population | 244 |
| of which posters | 14 |
full text merely mentions DNS or DNSSEC | 1,218 of 5,855 readable (20.8%) — an upper bound, not a population |
| Venue | Papers | In the DNS population | Share of that venue |
|---|---|---|---|
| IMC | 638 | 84 | 13.2% |
| USENIX Security | 1,410 | 61 | 4.3% |
| NDSS | 701 | 26 | 3.7% |
| CCS | 990 | 26 | 2.6% |
| PETS | 510 | 14 | 2.7% |
| IEEE S&P | 767 | 17 | 2.2% |
| TheWebConf | 843 | 16 | 1.9% |
IMC is where this work lives: 13.2% of its output against 4.3% for the next venue. In absolute terms the 244 split IMC 84, USENIX Security 61, NDSS and CCS 26 each, IEEE S&P 17, TheWebConf 16, PoPETs 14 — so IMC, USENIX Security and NDSS together are 70.1% of it, and TheWebConf and PoPETs together are 12.3%. If you come to this topic from web measurement and read only the web venues, you are reading about an eighth of what these seven venues contain — and, as the scope note above says, these seven venues are not where most DNS research appears either.
| Window | Papers | DNS population | Share |
|---|---|---|---|
| 2010–2013 | 511 | 16 | 3.1% |
| 2014–2017 | 769 | 39 | 5.1% |
| 2018–2021 | 1,439 | 76 | 5.3% |
| 2022–2024 | 1,955 | 74 | 3.8% |
| 2025–2026* | 1,185 | 39 | 3.3% |
(*) 2025–2026 venue-years are provisional: CCS and IMC 2026 have not been held, and IEEE S&P and TheWebConf 2026 abstracts are under-selected by construction. Do not read the last row as a completed period.
The web-platform slice is real but small, and you should know that before you frame a paper. Of the 244, 82 (33.6%) are tagged web — and that is only 5.1% of the 1,622 web-platform papers in the corpus. 186 (76.2%) are tagged network-scan-or-probe and only 40 (16.4%) automated-web-crawl. DNS measurement in these venues is overwhelmingly network-infrastructure measurement. If your contribution is “what DNS does to a web measurement”, you are in a thin part of the literature — which is an opportunity, but it also means your reviewers will be network people. Pick your venue accordingly (Conferences).
Folded from tools[] over the whole 5,859-paper corpus, counting papers, used or produced. The fold, its self-tests and its two-string residue are on dns. Currency verified 2026-08-27 against each project's own repository or site.
| Instrument | Papers | State on 2026-08-27 | Use it when |
|---|---|---|---|
| ZDNS [1Izhikevich, Liz; Akiwate, Gautam; Berger, Briana; Drakontaidis, Spencer; Ascheman, Anna; Pearce, Paul; Adrian, David; Durumeric, Zakir (2022): "ZDNS: a fast DNS toolkit for internet measurement", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] | 13 | Current. zmap/zdns v2.1.1 (2026-05-28), pushed 2026-08-24. | You need to resolve millions of names. Its own paper reports “90K lookups per second when using an external recursive resolver” and resolving “50M domains in 10 minutes”. This is the current default for large active DNS measurement. |
dig / drill / nslookup / tor-resolve | 18 | Current, but they are debugging tools. | Reproducing one answer, or checking a claim by hand. Not a measurement pipeline. Note the family is 13 dig/Dig, 2 nslookup, 1 drill, 1 tor-resolve and one string that is not a lookup tool at all (a paper's own “host name matching tool”); no paper in this corpus names host(1), which is why the family label does not. |
DNS libraries — dnspython, ldns, miekg/dns, getdns | 16 | Current. dnspython 2.8.0 (2025-09-07); miekg/dns v1.1.73 (2026-08-19) — it publishes no GitHub Release objects, only git tags, so a check that reads the releases API concludes there is nothing to pin. Pin the tag. | You need control over the wire format — EDNS options, 0x20 encoding, malformed queries, per-query source ports. |
| massdns | 5 | Maintained but slow-moving: v1.1.0 (2024-03-09), last push 2026-04-15. | Bulk stub resolution against a resolver you already have. ZDNS supersedes it for new work. |
| fpdns | 7 | Old (last upstream activity long predates this window) but not abandoned in practice: 1 use in 2018, 3 in 2023, 3 in 2024. | Inferring which resolver implementation is answering, from its behaviour, when it will not tell you (see failure mode 2). Every recent use is in an attack paper that needed to know what it was attacking. |
| DNSViz | 2 | Current: v0.11.1 (2025-04-21). | DNSSEC chain validation and diagnosis, one zone at a time. The fold's dnssec-tool family is 3 papers, but the third is a 2014 use of the Extended DNSSEC Validator Firefox extension as a code base — a different artefact, and this row is not its currency. |
| home-grown code | 34 | — | See the warning below. |
A seventh of the population wrote its own DNS code, and almost no two called it the same thing. 34 papers name a tool the fold maps to “home-grown”, under 34 distinct strings; only two of those strings are used by more than one paper, and both are generic descriptions rather than a shared tool (DNS crawler, authoritative DNS server). The rest are one-offs: custom DNS measurement code, active DNS crawler, large-scale active DNS measurement system, custom full-graph DNS resolver, Stanford::DNSserver. One paper, one name, no reuse. That is not a criticism — many of these needed behaviour no library offers — but it does mean there is almost no shared, comparable active-DNS methodology in this literature, and it is why a methods section that says “we resolved the domains” tells a replicator nothing.
| Source | Papers | State on 2026-08-27 | What it is |
|---|---|---|---|
| Farsight DNSDB (now DomainTools) | 28 | Renamed and re-homed. farsightsecurity.com no longer resolves at all; dnsdb.info still resolves and 301s to domaintools.com/platform, as does the old /products/farsight-dnsdb/ URL. (Its address rotates between checks, which is a small joke at this page's expense — do not pin it.) Commercial, with an academic access route. | The largest passive-DNS archive in this literature. A 2019 citation now points at a dead brand. |
| OpenINTEL | 14 | Current and actively extended: forward and reverse DNS, zone-based and top-list-based, ccTLD apex lists, a real-time zone-change feed. Academic, by request. | Daily active measurement of large zones since 2015. The right source for “what did this name resolve to on that day” at zone scale. |
| Rapid7 Open Data (Sonar FDNS / RDNS) | 8 | Still live, contrary to what a 2022-era memory will tell you: sonar.fdns_v2 last updated 2026-08-23, sonar.rdns_v2 2026-08-26. The host has moved: every path under opendata.rapid7.com now 301s to sonardata.rapid7.com. | Bulk forward and reverse DNS over the IPv4 space. |
| ICANN CZDS / zone files | 3 | Current. Per-TLD application. | The authoritative list of registered names in participating zones. It is the denominator a “we resolved the top 1M” study does not have. |
| Active DNS Project; 360 / 114DNS; Spamhaus; DNS Observatory | 2 / 4 / — / 1 | Various. | Regional and vendor feeds; the Chinese feeds are the only route to some of that namespace. |
RIPE Atlas appears in 75 papers corpus-wide and 24 of the 244 here — and 21 of those 24 are IMC papers, which is another way of saying the same thing as the venue table. It is not in the membership rule because most of the 75 use it for traceroute, not DNS. But it is the standard way to ask the same DNS question from thousands of vantage points, and DNS is one of its native measurement types: [2Randall, Audrey; Liu, Enze; Padmanabhan, Ramakrishna; Akiwate, Gautam; Voelker, Geoffrey M.; Savage, Stefan; Schulman, Aaron (2021): "Home is where the hijacking is: understanding DNS interception by residential routers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] localises interception with it, [3Moura, Giovane C. M.; Heidemann, John S.; Schmidt, Ricardo de Oliveira; Hardaker, Wes (2019): "Cache Me If You Can: Effects of DNS Time-to-Live", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] probes recursive resolvers worldwide with it, [4Al-Dalky, Rami; Rabinovich, Michael; Schomp, Kyle (2019): "A Look at the ECS Behavior of DNS Resolvers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] uses it for globally distributed probes, and [5Randall, Audrey; Liu, Enze; Akiwate, Gautam; Padmanabhan, Ramakrishna; Voelker, Geoffrey M.; Savage, Stefan; Schulman, Aaron (2020): "Trufflehunter: Cache Snooping Rare Domains at Large Public DNS Resolvers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] uses it to emulate geographically distributed users filling resolver caches. [6Bhaskar, Abhishek; Pearce, Paul (2022): "Many Roads Lead To Rome: How Packet Headers Influence DNS Censorship Measurement", in: Proceedings of the USENIX Security Symposium. (Link)], by contrast, does not use RIPE Atlas — it sends packets from its own vantage point, which is exactly why source-address choice is the variable it can manipulate.
Generic scanners — ZMap and XMap (112 papers), ZGrab (31), Scapy (33) — appear in 38 of the 244. ZMap is how you find open resolvers; it is not a DNS toolkit. Automated measurements covers the scan branch generally.
This is the oldest finding on the page and it has never stopped being true. Ager et al. [7Ager, Bernhard; Mühlbauer, Wolfgang; Smaragdakis, Georgios; Uhlig, Steve (2010): "Comparing DNS resolvers in the wild", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] sent the same queries to the local resolver, Google and OpenDNS from 60-plus vantage points in 2010 and found that “the answers to the DNS resolvers differ in terms of subnets for approximately 2,000 out of our 10,000 host names. In half of these cases, the returned IP addresses even belong to different ASs and countries.”2) Sixteen years later the run in Measure your own resolver dependence gets 38.7% not-identical on a 75-name comparable set, with 10.7% of them sharing no address between any pair of resolvers.
Two mechanisms do most of it. CDNs and GeoDNS answer according to where they think you are, and EDNS Client Subnet (RFC 7871 — note it is Informational, not a standard) is how a public resolver tells them. Al-Dalky et al. [4Al-Dalky, Rami; Rabinovich, Michael; Schomp, Kyle (2019): "A Look at the ECS Behavior of DNS Resolvers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] measured how resolvers actually use it and found the behaviour is not uniform: “3382 out of 4147 resolvers in the CDN dataset send 100% of their A and AAAA queries with an ECS option”, and that “103 recursive resolvers, or over half of all recursive resolvers we could study”, “don't control caching based on scope at all” — they reuse a cached response outside the prefix it was scoped for — a caching bug that hands the wrong client the wrong answer. Anycast does the rest; IP classification covers why geolocating an anycast address is meaningless.
What to do. Decide, in writing, whether your question is “what does this name resolve to” (then say which resolver, and preferably use more than one) or “what does it resolve to for a client in prefix X” (then you are doing an ECS study and your denominator is prefixes, not names).
Liu et al. [8Liu, Baojun; Lu, Chaoyi; Duan, Haixin; Liu, Ying; Li, Zhou; Hao, Shuang; Yang, Min (2018): "Who Is Answering My Queries: Understanding and Characterizing Interception of the DNS Resolution Path", in: Proceedings of the USENIX Security Symposium. (Link)] sent DNS queries from 148,478 residential and cellular addresses and found that “259 of the 3,047 ASes (8.5%) that we inspect exhibit DNS interception behavior”. Randall et al. [2Randall, Audrey; Liu, Enze; Padmanabhan, Ramakrishna; Akiwate, Gautam; Voelker, Geoffrey M.; Savage, Stefan; Schulman, Aaron (2021): "Home is where the hijacking is: understanding DNS interception by residential routers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] localised the interception, identifying “over 200 occurrences of such transparent interception” on RIPE Atlas and showing that the customer-premises router — not the ISP — is a significant fraction of it.
The consequence for a measurement is precise and easy to miss: “we used 8.8.8.8” is a statement about the destination address in your packets, not about who answered them. If your paper compares public resolvers against ISP resolvers, interception is a confound that inverts the comparison for some of your vantage points. Detecting it needs a resolver that will tell you who it is — the NSID option (RFC 5001), a CHAOS-class id.server query, or a debugging endpoint the resolver operator publishes.
Every recursive resolver between you and the authoritative server is a state you did not set and cannot see. Moura et al. [3Moura, Giovane C. M.; Heidemann, John S.; Schmidt, Ricardo de Oliveira; Hardaker, Wes (2019): "Cache Me If You Can: Effects of DNS Time-to-Live", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] is the paper on what TTL actually does in deployment. Randall et al. [5Randall, Audrey; Liu, Enze; Akiwate, Gautam; Padmanabhan, Ramakrishna; Voelker, Geoffrey M.; Savage, Stefan; Schulman, Aaron (2020): "Trufflehunter: Cache Snooping Rare Domains at Large Public DNS Resolvers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] turn the same property into an instrument — cache snooping tells you what other people looked up, which is why it is also a privacy result.
The practical failure: a “we repeated the crawl to check stability” robustness check, run minutes apart through the same resolver, measures the cache. The script below prints the TTL on an immediate second query for exactly this reason — a smaller TTL is the resolver telling you it served you from cache.
The least intuitive one, and the most important if you are measuring censorship or manipulation. Bhaskar and Pearce [6Bhaskar, Abhishek; Pearce, Paul (2022): "Many Roads Lead To Rome: How Packet Headers Influence DNS Censorship Measurement", in: Proceedings of the USENIX Security Symposium. (Link)] varied only the ephemeral source port and the source IP within a subnet and found “that 37% of IPs across 56% ASes measured show some change in censorship behavior depending on source port and local source IP. This behavior is frequently all-or-nothing”. Load balancers route on the five-tuple; different routes meet different middleboxes.
So a censorship measurement that fixes one source address and lets the OS pick ports is not measuring “does country X block Y” — it is measuring one path. Report the source addresses and the port selection policy, and vary them.
Pearce et al.'s Iris [9Pearce, Paul; Jones, Ben; Li, Frank; Ensafi, Roya; Feamster, Nick; Weaver, Nick; Paxson, Vern (2017): "Global Measurement of DNS Manipulation", in: Proceedings of the USENIX Security Symposium. (Link)] is the method paper: of 13,594,683 responses they identify “41,778 responses (0.31%) as manipulated, spread across 58 countries (and dependent territories) and 1,408 domains”. The 0.31% is worth staring at — manipulation is rare enough that a small systematic error in your consistency metric swamps it.
Date this one. The 2017 approach is consistency-and-independent-verifiability over the DNS answer itself. The method that carried later work adds a second, harder-to-forge signal: Tsai et al.'s CERTainty [10Tsai, Elisa; Kumar, Deepak; Sundara Raman, Ram; Li, Gavin; Eiger, Yael; Ensafi, Roya (2023): "CERTainty: Detecting DNS Manipulation at Scale using TLS Certificates", Proceedings on Privacy Enhancing Technologies 2023(3):122-137. (DOI)] detects manipulation by connecting to the returned address and checking the TLS certificate, which is how they separate censorship from CDN behaviour. Their headline is a verdict on the older design: “previous work using consistency-based heuristics is inaccurate, allowing for 72.45% false positives in the cases detected as DNS manipulation” — the CDN and localisation behaviour of failure mode 1, misread as censorship. CERTainty itself identifies “17 TLS proxy vendors in 52 countries” and “55 ASes in 26 countries with ISP-level DNS manipulation”. If you are designing a manipulation study in 2026, start from the certificate-verification design, not from response comparison alone. Wu et al. [11Wu, Wenhao; Wang, Zhaohua; Li, Zihan; Li, Qinxin; Xia, Yiming; Gao, Chuan; Zhang, Guangxing; Li, Zhenyu (2026): "Tracking the Stray Sheep: Understanding DNS Response Manipulation in the Wild", in: Proceedings of the ACM Web Conference. (DOI)] is the most recent corpus entry on manipulation in the wild.
Dates matter here more than anywhere else on the page, because a 2019 encrypted-DNS methods section is describing a different deployment. Corpus columns are full-text paper counts within the 244; they are upper bounds (one sentence in related work counts) and are best read as arrival dates.
| Protocol | RFC and status (checked 2026-08-27) | Papers in the 244, by window | Where it stands |
|---|---|---|---|
| DoT — DNS over TLS | RFC 7858, May 2016, Proposed Standard, updated by RFC 8310 | 0 / 0 / 23 / 7 / 4 | Shipped and stable, but check what your Android handset is actually speaking: Private DNS launched as DoT in Android 9, and since a July 2022 Google Play system update “Android devices from Android 11 onwards will use DoH3 instead of DoT for well-known DNS servers which support it”.3) DoH3 is HTTP/3-framed DNS — not DoT, and not RFC 9250 DoQ either. |
| DoH — DNS over HTTPS | RFC 8484, October 2018, Proposed Standard | 0 / 0 / 23 / 16 / 9 | The one the browsers deployed. The most-measured of the encrypted transports and still the default assumption. |
| DoQ — DNS over QUIC | RFC 9250, May 2022, Proposed Standard | 0 / 0 / 4 / 1 / 1 | Standardised, barely measured: 6 of the 244 mention it at all (10 across the whole 5,859). An open area, not a settled method. |
| DoC — DNS over CoAP | RFC 9953, March 2026, Proposed Standard | 0 / 0 / 0 / 0 / 0 | Five months old at the time of writing and not measured in this corpus at all. It targets constrained IoT networks, so it is unlikely to matter for a web crawl — but it is on the transport list now, and a page that omitted it would be describing 2024. |
| ODoH — Oblivious DoH | RFC 9230, June 2022, Experimental | 0 / 0 / 5 / 4 / 2 | Experimental in the RFC sense as well as the deployment sense. Do not describe it as a deployed default. |
| DDR — Discovery of Designated Resolvers | RFC 9462, November 2023, Proposed Standard (with DNR, RFC 9463) | 0 / 0 / 0 / 0 / 1 | The frontier, and the answer to “how does a client find an encrypted resolver at all”. Exactly one paper in this corpus measures it. |
| DNSSEC | RFC 4033–4035, March 2005, Proposed Standard | 3 / 12 / 36 / 37 / 16 | The steady background. Never stopped being measured; never finished deploying. |
| DNSCrypt / DNSCurve | Not an RFC | 0 / 1 / 8 / 1 / 1 | Pre-standard, still operating (dnscrypt-proxy 2.1.18, 2026-07-18), largely superseded in the literature by DoH. |
DDR is where the measurement question now is, and what it measures is unsettled. Ververis et al. [12Ververis, Vasilis; Sassala, Steffen; Roth, Felix; Bajpai, Vaibhav (2025): "Path to Encrypted DNS with DDR: Adoption, Configuration Patterns, and Privacy Implications", Proceedings on Privacy Enhancing Technologies 2025(4):465-484. (DOI)] — the one paper in this corpus that measures DDR — report “widespread misconfigurations, including incomplete and incorrect DDR configurations that prevent clients from successfully transitioning to encrypted resolvers. In over 99% of observed cases, DDR-compliant clients may fail to upgrade to DoE due to these deployment issues”.
One paper is not a literature, and this one's central finding is contested outside these seven venues. Nosyk, Duda and Korczyński analysed “over 321k DDR-enabled open resolvers” and found not brokenness but centralisation: “an apparent dominance of the Google Public DNS with 80.8% of DDR-enabled resolvers designating dns.google”, Cloudflare second at 12.4%, and “these are designated by 97.4% of DDR-enabled resolvers, highlighting the reliance on a handful of big operators”.4) The same group then published The Illusion of DDR Deployment at ANRW 2026, arguing that resolvers scored as misconfigured are in many cases transparent forwarders — a different explanation for the same observation.5)
So: if you want an encrypted-DNS topic that is not already crowded, this is it — and neither the misconfiguration rate nor its interpretation is settled. Cite the 99% as one measurement under dispute, not as a rate. Note also where all of this is published: PoPETs, ANRW and an operator blog. A DNS measurement literature review confined to these seven venues would have found one of the three.
One trust boundary the table above does not cover. Every protocol in it encrypts the stub-to-recursive hop — your machine to the resolver. The recursive-to-authoritative hop is a separate problem with a separate document, RFC 9539, Unilateral Opportunistic Deployment of Encrypted Recursive-to-Authoritative DNS (February 2024, Experimental). If your paper says “we measured encrypted DNS”, say which hop; a 100%-DoH client can still have every one of its queries travel the second hop in the clear.
Two dated facts a student will otherwise get wrong.
network.trr.mode has value 0 in modules/libpref/init/StaticPrefList.yaml on mozilla-firefox/firefox main, and network.trr.uri is the empty string in all.js. The rollout is server-side: Mozilla's Remote Settings doh-providers collection is public and returns 5 provider records today — Cloudflare (the only one with autoDefault true), Shaw, Comcast Xfinity, NextDNS and CIRA Canadian Shield.6) If your paper says “Firefox sends DNS to Cloudflare by default”, say in which region and on what date, and check it against that collection rather than against the tree.This is the slice a web-measurement student actually needs, and it is where the corpus is thinnest (82 of 244 papers are web-platform). Four established uses:
undns, the RIPE IPmap engines); do not rebuild it here.
Two things a naive list-driven pipeline gets wrong, both visible in the run below. First, a quarter of the Tranco top 100 has no A record at its apex — akamaiedge.net, cloudfront.net, gtld-servers.net, root-servers.net and 21 others. If your crawler resolves the registrable domain and drops what fails, you have silently deleted the infrastructure names from your population, which is exactly the set an infrastructure question is about. Second, resolving through one resolver bakes that resolver's CDN mapping into every downstream geolocation, ASN and co-hosting claim.
Sending queries to resolvers you do not own is a scan, and it has a failure mode the rest of the scan branch does not: an open resolver will amplify whatever you send it at somebody else. Kührer et al. [19Kührer, Marc; Hupperich, Thomas; Bushart, Jonas; Rossow, Christian; Holz, Thorsten (2015): "Going Wild: Large-Scale Classification of Open DNS Resolvers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] “find up to 20 million open resolvers in the weekly IPv4 scans in 2015”, and 58 of the 244 papers here match a full-text probe for amplification or reflection (15 of them say so in the title). If your measurement queries open resolvers, you are one misconfigured source address away from being the attack.
The DNS population states more about ethics than the corpus average on three of four fields, and less on the one that names an ethics board — which is the pattern you would expect from work that mitigates harm operationally and does not go to an IRB because it has no human subjects:
| Field | DNS population (241 empirical of the 244) | Whole corpus (5,118 empirical) |
|---|---|---|
ethics.harmMitigation | 186 (77.2%) | 3,186 (62.3%) |
ethics.notifiedAffectedParties | 147 (61.0%) | 2,354 (46.0%) |
ethics.regulatorContact | 132 (54.8%) | 2,129 (41.6%) |
ethics.reviewOutcome | 72 (29.9%) | 1,728 (33.8%) |
The operational checklist — identify yourself, publish an opt-out page, rate-limit, never spoof a source address — lives on Ethics; telling an operator afterwards is Notifying websites. Two DNS-specific additions that checklist does not carry:
robots.txt does not: only 4.7% of the 1,120 crawling papers in this corpus state anything about robots.txt at all, and for a DNS probe the file is not even the right channel.The corpus is not reassuring here. Denominators are the 244-paper DNS population, with the scan branch and the crawling population beside it for comparison.
| The paper… | DNS population (244) | scan branch (930) | crawled population (1,120) |
|---|---|---|---|
| names a vantage location | 124 (50.8%) | 487 (52.4%) | 301 (26.9%) |
| names any used/produced tool version | 103 (42.2%) | 401 (43.1%) | 506 (45.2%) |
| states an artifact availability | 124 (50.8%) | 511 (54.9%) | 721 (64.4%) |
Half of the DNS papers in this corpus do not say where they measured from, and DNS is the measurement where that matters most. Counting distinct stated vantage locations per paper: 120 of 244 (49.2%) state none, 46 state exactly one, 40 state two or three, 35 state four to ten, and 3 state more than ten. A single-vantage DNS result is a result about one path — see failure modes 1, 2 and 4 above — and the field reports it as if it were a result about the Internet.
A DNS methods section is complete when a replicator can answer all of these. The extraction has a field for only the first two, and the table above is how often those are answered (50.8% and 42.2%); for the rest the schema has no field, so this list is a standard, not a measurement:
Before you write “we resolved the domains”, run this against your own name list from your own machine. It is standard-library Python 3.8+ — no dnspython, no dig, nothing to install — because the point is that you can run it on the measurement host itself.
#!/usr/bin/env python3 """Measure how much a DNS answer depends on which resolver you asked. Why this exists: a web measurement that resolves names -- to pick an IP to connect to, to geolocate a host, to decide whether two sites share infrastructure -- inherits whatever the resolver decided. Different resolvers return different addresses for the same name, at the same instant, from the same machine. This prints how often, for your name list, from your vantage point, today. Run it before you claim anything that rests on a resolution. Python 3.8+, standard library only: no dnspython, no dig, nothing to install. What it reports, per name: * the A-record set each resolver returned * whether all resolvers agree, agree on the /24, or disagree outright * the TTL each resolver returned, and the TTL on an immediate second query That second query is the point of the TTL columns. A resolver that answers the repeat with a smaller TTL served it from cache; the two measurements are not independent samples, and a "we resolved every domain twice" robustness check that does not account for this is measuring the cache, not the world. Usage: python3 resolver_disagreement.py example.com wikipedia.org python3 resolver_disagreement.py --names names.txt --json out.json python3 resolver_disagreement.py --resolvers 8.8.8.8,1.1.1.1,192.168.1.1 example.com Exit status is 0 even when resolvers disagree: disagreement is the measurement, not an error. """ import argparse import itertools import json import random import socket import struct import sys import time # Public resolvers are the usual comparison set. The one that matters most is # often the one NOT in this list: the resolver your machine was handed by DHCP. # --resolvers lets you add it; `cat /etc/resolv.conf` tells you what it is. DEFAULT_RESOLVERS = [ ("Google", "8.8.8.8"), ("Cloudflare", "1.1.1.1"), ("Quad9", "9.9.9.9"), ] QTYPE_A = 1 QCLASS_IN = 1 RCODE = {0: "NOERROR", 1: "FORMERR", 2: "SERVFAIL", 3: "NXDOMAIN", 4: "NOTIMP", 5: "REFUSED"} def build_query(name, qtype=QTYPE_A, txid=None): """A single-question query with RD set and no EDNS0. No EDNS0 is deliberate and is itself a measurement decision: adding an EDNS0 OPT record changes which resolvers answer you at all, and adding an EDNS Client Subnet option (RFC 7871, Informational) changes the *answer* you get from any resolver that honours it. If your study needs ECS, you are no longer asking "what does this name resolve to" -- you are asking "what does it resolve to for a client in this prefix", which is a different question with a different denominator. """ if txid is None: txid = random.randint(0, 0xFFFF) header = struct.pack(">HHHHHH", txid, 0x0100, 1, 0, 0, 0) qname = b"".join(bytes([len(part)]) + part.encode("idna") for part in name.rstrip(".").split(".")) return txid, header + qname + b"\x00" + struct.pack(">HH", qtype, QCLASS_IN) def _skip_name(buf, off): """Advance past a (possibly compressed) domain name. Returns new offset.""" while True: if off >= len(buf): raise ValueError("truncated name") length = buf[off] if length == 0: return off + 1 if length & 0xC0 == 0xC0: # compression pointer: two bytes, then done return off + 2 off += 1 + length def parse_answers(buf, txid): """Return (rcode, [(ttl, ip), ...]) for the A records in a response.""" if len(buf) < 12: raise ValueError("short response") rid, flags, qd, an, _ns, _ar = struct.unpack(">HHHHHH", buf[:12]) if rid != txid: raise ValueError("transaction id mismatch: possible off-path answer") rcode = flags & 0x0F off = 12 for _ in range(qd): off = _skip_name(buf, off) + 4 out = [] for _ in range(an): off = _skip_name(buf, off) rtype, _rclass, ttl, rdlen = struct.unpack(">HHIH", buf[off:off + 10]) off += 10 if rtype == QTYPE_A and rdlen == 4: out.append((ttl, socket.inet_ntoa(buf[off:off + 4]))) off += rdlen return rcode, out def query(server, name, timeout=3.0): """One UDP query. Returns a dict; never raises.""" txid, pkt = build_query(name, txid=None) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) started = time.time() deadline = started + timeout try: sock.sendto(pkt, (server, 53)) # Accept only a datagram from the address we asked. This does not make # the measurement safe from an on-path forger -- nothing at this layer # does -- but it stops a stray answer from a different box being # recorded as this resolver's. # # The timeout is a TOTAL deadline, not a per-datagram one. Calling # settimeout() once outside the loop restarts the clock on every packet # received, so a stream of unrelated datagrams arriving faster than the # timeout keeps one query alive indefinitely -- which is a bad property # for a script whose subject is off-path packets. while True: remaining = deadline - time.time() if remaining <= 0: raise socket.timeout("total deadline exceeded") sock.settimeout(remaining) data, peer = sock.recvfrom(4096) if peer[0] == server: break rcode, answers = parse_answers(data, txid) return {"ok": True, "rcode": RCODE.get(rcode, str(rcode)), "rtt_ms": round((time.time() - started) * 1000, 1), "ttls": [t for t, _ in answers], "ips": sorted({ip for _, ip in answers})} except Exception as exc: # timeout, refused, malformed return {"ok": False, "error": f"{type(exc).__name__}: {exc}", "rtt_ms": round((time.time() - started) * 1000, 1), "ttls": [], "ips": []} finally: sock.close() def slash24(ip): return ip.rsplit(".", 1)[0] + ".0/24" def verdict(per_resolver): """Classify one name's answers across resolvers. The comparison is PAIRWISE, deliberately. An earlier version of this function intersected all answering resolvers at once, which meant that with three resolvers, two agreeing exactly and one differing produced an empty three-way intersection and was labelled "disjoint" -- contradicting the word itself. On a 100-name run, 17 of 28 names labelled "disjoint" had two of the three resolvers returning byte-identical addresses. If you extend this to more resolvers, keep the comparison pairwise. Verdicts, in order of severity: identical every answering resolver returned exactly the same set partial some pair shares an address, but not all sets are equal same-/24 no pair shares an address, but some pair shares a /24 disjoint no pair shares an address or even a /24 no-A-record every resolver said NOERROR and returned no A record one-answered exactly one resolver returned an address and the others returned NOERROR with none -- the most extreme disagreement there is, and easy to mistake for a dead name insufficient fewer than two resolvers answered, for some other reason """ answering = {n: r for n, r in per_resolver.items() if r["ok"] and r["ips"]} if len(answering) < 2: # Distinguish "the name has no address here" from "the measurement # failed". The first is a property of the zone and is the single most # common way a list-driven crawl loses rows without noticing: CDN and # infrastructure apexes in a domain popularity list frequently have no # A record at all. if all(r["ok"] and r["rcode"] == "NOERROR" and not r["ips"] for r in per_resolver.values()): return "no-A-record" if (len(answering) == 1 and all(r["ok"] and r["rcode"] == "NOERROR" for r in per_resolver.values())): return "one-answered" return "insufficient" sets = [set(r["ips"]) for r in answering.values()] if all(a == b for a, b in itertools.combinations(sets, 2)): return "identical" if any(a & b for a, b in itertools.combinations(sets, 2)): return "partial" nets = [{slash24(ip) for ip in s} for s in sets] if any(a & b for a, b in itertools.combinations(nets, 2)): return "same-/24" return "disjoint" def _self_test(): """Cases from a real run, including the ones that caught the bug this function used to have. `python3 resolver_disagreement.py --self-test`.""" def r(ips, ok=True, rcode="NOERROR"): return {"ok": ok, "rcode": rcode, "rtt_ms": 1.0, "ttls": [60] * len(ips), "ips": sorted(ips)} cases = [ # all three identical ({"a": r(["1.1.1.1"]), "b": r(["1.1.1.1"]), "c": r(["1.1.1.1"])}, "identical"), # facebook.com in the 2026-08-27 run: two byte-identical, one different. # The all-at-once intersection called this "disjoint". It is not. ({"a": r(["157.240.17.35"]), "b": r(["157.240.17.35"]), "c": r(["157.240.0.35"])}, "partial"), # wikipedia.org: two identical, third differs and is in another /24 ({"a": r(["185.15.58.224"]), "b": r(["185.15.58.224"]), "c": r(["185.15.59.224"])}, "partial"), # google.com: three mutually disjoint /16s -- genuinely disjoint ({"a": r(["74.125.29.100"]), "b": r(["172.217.208.100"]), "c": r(["142.250.154.100"])}, "disjoint"), # no address in common but a shared /24 ({"a": r(["9.9.9.1"]), "b": r(["9.9.9.2"]), "c": r(["9.9.9.3"])}, "same-/24"), # every resolver says NOERROR with no A record: a property of the zone ({"a": r([]), "b": r([]), "c": r([])}, "no-A-record"), # a real failure is not the same thing ({"a": r([], ok=False), "b": r([]), "c": r([])}, "insufficient"), # NXDOMAIN everywhere is also not "no A record at the apex" ({"a": r([], rcode="NXDOMAIN"), "b": r([], rcode="NXDOMAIN"), "c": r([], rcode="NXDOMAIN")}, "insufficient"), # only one resolver answered ({"a": r(["1.2.3.4"]), "b": r([]), "c": r([])}, "one-answered"), # two resolvers, sets overlap partially ({"a": r(["1.2.3.4", "1.2.3.5"]), "b": r(["1.2.3.5", "9.9.9.9"])}, "partial"), ] bad = 0 for i, (given, want) in enumerate(cases, 1): got = verdict(given) if got != want: bad += 1 print(f" FAIL case {i}: got {got!r}, want {want!r}") print(f"self-test: {len(cases) - bad}/{len(cases)} pass") return 0 if bad == 0 else 1 def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("names", nargs="*", help="domain names to resolve") ap.add_argument("--names-file", help="file with one name per line") ap.add_argument("--resolvers", help="comma-separated resolver IPs, or name=IP pairs, " "replacing the built-in set") ap.add_argument("--repeat-after", type=float, default=0.0, help="seconds to wait before a second query per name, to " "expose caching via the TTL (default 0 = immediately)") ap.add_argument("--timeout", type=float, default=3.0) ap.add_argument("--json", help="write the full per-query record here") ap.add_argument("--self-test", action="store_true", help="check verdict() against known cases and exit") args = ap.parse_args() if args.self_test: raise SystemExit(_self_test()) names = list(args.names) if args.names_file: with open(args.names_file) as fh: names += [ln.strip() for ln in fh if ln.strip() and not ln.startswith("#")] if not names: ap.error("give at least one name, or --names-file") if args.resolvers: resolvers = [] for item in args.resolvers.split(","): item = item.strip() if "=" in item: label, addr = item.split("=", 1) else: label, addr = item, item resolvers.append((label, addr)) else: resolvers = DEFAULT_RESOLVERS print(f"# resolvers: {', '.join(f'{n} ({a})' for n, a in resolvers)}") print(f"# names: {len(names)} run at {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}") print() record = [] tally = {} for name in names: first = {label: query(addr, name, args.timeout) for label, addr in resolvers} if args.repeat_after: time.sleep(args.repeat_after) second = {label: query(addr, name, args.timeout) for label, addr in resolvers} v = verdict(first) tally[v] = tally.get(v, 0) + 1 print(f"{name} -> {v}") for label, _addr in resolvers: a, b = first[label], second[label] if not a["ok"]: print(f" {label:<12} FAILED {a['error']}") continue ttl1 = a["ttls"][0] if a["ttls"] else "-" ttl2 = b["ttls"][0] if b.get("ttls") else "-" cached = "" if isinstance(ttl1, int) and isinstance(ttl2, int): if ttl2 < ttl1: cached = f" (2nd query TTL {ttl2}: served from cache)" elif ttl2 == ttl1: cached = f" (2nd query TTL {ttl2}: unchanged)" print(f" {label:<12} {a['rcode']:<9} {a['rtt_ms']:>6.1f} ms " f"ttl={ttl1:<6} {', '.join(a['ips']) or '(no A record)'}{cached}") record.append({"name": name, "verdict": v, "first": first, "second": second}) print() print(f"summary A: all {len(names)} names") for v in ("identical", "partial", "same-/24", "disjoint", "one-answered", "no-A-record", "insufficient"): if v in tally: print(f" {v:<14} {tally[v]:>4} {100 * tally[v] / len(names):5.1f}%") comparable = sum(tally.get(v, 0) for v in ("identical", "partial", "same-/24", "disjoint")) disagree = comparable - tally.get("identical", 0) print() print(f"summary B: the {comparable} names where at least two resolvers " f"returned an address") for v in ("identical", "partial", "same-/24", "disjoint"): if v in tally and comparable: print(f" {v:<14} {tally[v]:>4} {100 * tally[v] / comparable:5.1f}%") if comparable: print(f" {'NOT identical':<14} {disagree:>4} {100 * disagree / comparable:5.1f}%" f" <- the headline: at least one resolver disagreed with another") print() print("Summary B is the denominator to quote. Summary A mixes two different") print("findings: how often resolvers disagree, and how often a name in your") print("list has no address to disagree about.") print() print("'disjoint' means NO PAIR of resolvers returned an address in common, and") print("no pair even shared a /24. 'partial' means some pair overlapped and some") print("pair did not. The number to quote for \"how much does my choice of") print("resolver matter\" is NOT identical: that is the share of your name list") print("where resolvers did not all agree -- from this vantage point, at this") print("instant. 'disjoint' alone understates it; quoting 'disjoint' as though") print("it meant \"any disagreement\" overstates it.") if args.json: with open(args.json, "w") as fh: json.dump(record, fh, indent=1) print(f"\nwrote {args.json}") if __name__ == "__main__": main()
Real output, run on 2026-08-27 from a single European vantage point against the Tranco top 100 (list ID 46W9X), abridged mechanically by scripts/build_dns_codeblock.mjs — the two header lines, the first stanza whose verdict is disjoint with each address list cut after two entries, and both summary blocks, all verbatim. The example is selected by verdict rather than pinned to a name, because a name's verdict changes between runs. The full 100-name output and the JSON are on dns:
# resolvers: Google (8.8.8.8), Cloudflare (1.1.1.1), Quad9 (9.9.9.9)
# names: 100 run at 2026-08-27T22:56:27Z
gstatic.com -> disjoint
Google NOERROR 36.6 ms ttl=300 172.217.208.120, 172.217.208.94 (2nd query TTL 300: unchanged)
Cloudflare NOERROR 22.0 ms ttl=90 74.125.29.120, 74.125.29.94 (2nd query TTL 22: served from cache)
Quad9 NOERROR 122.9 ms ttl=160 142.250.154.120, 142.250.154.94
summary A: all 100 names
identical 46 46.0%
partial 21 21.0%
disjoint 8 8.0%
no-A-record 25 25.0%
summary B: the 75 names where at least two resolvers returned an address
identical 46 61.3%
partial 21 28.0%
disjoint 8 10.7%
NOT identical 29 38.7% <- the headline: at least one resolver disagreed with another
Four things to take from it, none of which is a claim about the Internet — it is one machine, one instant, three resolvers, one hundred names:
gstatic.com above is one, three different Google ranges from three resolvers. Quoting the disjoint figure as though it meant “any disagreement” understates it by more than half; quoting “any disagreement” as though it meant disjoint overstates it by more than three times. The script prints both and says which to use.--self-test checks it against ten cases including that one. If you extend the script to more resolvers, keep the comparison pairwise.
Run it with --resolvers and add the resolver your machine was actually handed by DHCP, from /etc/resolv.conf. That one is usually the most interesting row, and it is the one no public comparison includes.
| Paper | Why it is here |
|---|---|
| Liu et al., USENIX Security 2018, Who Is Answering My Queries [8Liu, Baojun; Lu, Chaoyi; Duan, Haixin; Liu, Ying; Li, Zhou; Hao, Shuang; Yang, Min (2018): "Who Is Answering My Queries: Understanding and Characterizing Interception of the DNS Resolution Path", in: Proceedings of the USENIX Security Symposium. (Link)] | The single most load-bearing methodological result on this page: the resolver you addressed may not be the one that answered. 8.5% of 3,047 ASes. |
| Bhaskar and Pearce, USENIX Security 2022, Many Roads Lead To Rome [6Bhaskar, Abhishek; Pearce, Paul (2022): "Many Roads Lead To Rome: How Packet Headers Influence DNS Censorship Measurement", in: Proceedings of the USENIX Security Symposium. (Link)] | Your source port changes your result. 37% of IPs across 56% of ASes. Read before designing any remote manipulation study. |
| Pearce et al., USENIX Security 2017, Global Measurement of DNS Manipulation [9Pearce, Paul; Jones, Ben; Li, Frank; Ensafi, Roya; Feamster, Nick; Weaver, Nick; Paxson, Vern (2017): "Global Measurement of DNS Manipulation", in: Proceedings of the USENIX Security Symposium. (Link)] | Iris: the design for measuring manipulation ethically and at scale, and the 0.31% base rate. |
| Tsai et al., PoPETs 2023, CERTainty [10Tsai, Elisa; Kumar, Deepak; Sundara Raman, Ram; Li, Gavin; Eiger, Yael; Ensafi, Roya (2023): "CERTainty: Detecting DNS Manipulation at Scale using TLS Certificates", Proceedings on Privacy Enhancing Technologies 2023(3):122-137. (DOI)] | The method that superseded response-comparison alone: verify with the TLS certificate. |
| Izhikevich et al., IMC 2022, ZDNS [1Izhikevich, Liz; Akiwate, Gautam; Berger, Briana; Drakontaidis, Spencer; Ascheman, Anna; Pearce, Paul; Adrian, David; Durumeric, Zakir (2022): "ZDNS: a fast DNS toolkit for internet measurement", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] | The instrument paper for large-scale active resolution, and the reason you should not write another one. |
| Dimova et al., PoPETs 2021, The CNAME of the Game [15Dimova, Yana; Acar, Gunes; Olejnik, Lukasz; Joosen, Wouter; Van Goethem, Tom (2021): "The CNAME of the game: Large-scale analysis of DNS-based tracking evasion", Proceedings on Privacy Enhancing Technologies 2021:394–412. (DOI) (Link)] | Why a web-tracking measurement needs its own resolution step. 10,474 websites. |
| Ager et al., IMC 2010, Comparing DNS resolvers in the wild [7Ager, Bernhard; Mühlbauer, Wolfgang; Smaragdakis, Georgios; Uhlig, Steve (2010): "Comparing DNS resolvers in the wild", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] | The oldest statement of failure mode 1, and still the cleanest. |
| Ververis et al., PoPETs 2025, Path to Encrypted DNS with DDR [12Ververis, Vasilis; Sassala, Steffen; Roth, Felix; Bajpai, Vaibhav (2025): "Path to Encrypted DNS with DDR: Adoption, Configuration Patterns, and Privacy Implications", Proceedings on Privacy Enhancing Technologies 2025(4):465-484. (DOI)] | Where the encrypted-DNS question is now, and how thin the evidence still is. |
Also worth your time, by topic: [20Lu, Chaoyi; Liu, Baojun; Li, Zhou; Hao, Shuang; Duan, Hai-Xin; Zhang, Mingming; Leng, Chunying; Liu, Ying; Zhang, Zaifeng; Wu, Jianping (2019): "An End-to-End, Large-Scale Measurement of DNS-over-Encryption: How Far Have We Come?", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] and [21Chhabra, Rishabh; Murley, Paul; Kumar, Deepak; Bailey, Michael D.; Wang, Gang (2021): "Measuring DNS-over-HTTPS performance around the world", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on the cost and performance of encrypted DNS; [22Li, Ruixuan; Liu, Baojun; Lu, Chaoyi; Duan, Haixin; Shao, Jun (2024): "A Worldwide View on the Reachability of Encrypted DNS Services", in: Proceedings of the ACM Web Conference. (DOI)] on its reachability; [19Kührer, Marc; Hupperich, Thomas; Bushart, Jonas; Rossow, Christian; Holz, Thorsten (2015): "Going Wild: Large-Scale Classification of Open DNS Resolvers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on open resolvers; [4Al-Dalky, Rami; Rabinovich, Michael; Schomp, Kyle (2019): "A Look at the ECS Behavior of DNS Resolvers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on ECS behaviour; [3Moura, Giovane C. M.; Heidemann, John S.; Schmidt, Ricardo de Oliveira; Hardaker, Wes (2019): "Cache Me If You Can: Effects of DNS Time-to-Live", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on TTL and [5Randall, Audrey; Liu, Enze; Akiwate, Gautam; Padmanabhan, Ramakrishna; Voelker, Geoffrey M.; Savage, Stefan; Schulman, Aaron (2020): "Trufflehunter: Cache Snooping Rare Domains at Large Public DNS Resolvers", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on what a cache leaks; [23Liu, Guannan; Jin, Lin; Hao, Shuai; Zhang, Yubao; Liu, Daiping; Stavrou, Angelos; Wang, Haining (2023): "Dial "N" for NXDomain: The Scale, Origin, and Security Implications of DNS Queries to Non-Existent Domains", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on the scale of NXDOMAIN traffic; [24Nosyk, Yevheniya; Korczynski, Maciej; Duda, Andrzej (2023): "Extended DNS Errors: Unlocking the Full Potential of DNS Troubleshooting", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on Extended DNS Errors (RFC 8914), which give you a machine-readable reason for a failure instead of a bare SERVFAIL; [25Dong, Hongying; Zhang, Yizhe; Lee, Hyeonmin; Huque, Shumon; Sun, Yixin (2024): "Exploring the Ecosystem of DNS HTTPS Resource Records: An End-to-End Perspective", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on the HTTPS resource record; [26Hounsel, Austin; Borgolte, Kevin; Schmitt, Paul; Holland, Jordan; Feamster, Nick (2020): "Comparing the Effects of DNS, DoT, and DoH on Web Performance", in: Proceedings of the ACM Web Conference. (DOI)] on what DoT and DoH do to page-load time; [27Ashiq, Md. Ishtiaq; Hureau, Olivier; Deccio, Casey T.; Chung, Tijay (2025): "Decoding DNSSEC Errors at Scale: An Automated DNSSEC Error Resolution Framework using Insights from DNSViz Logs", in: Proceedings of the ACM Internet Measurement Conference. (DOI)] on automated DNSSEC error diagnosis; and [28Xia, Ruoxuan; Li, Bingyu; Chen, Zhenyu; Yuan, Pengyu; Wang, Yunjia; Zheng, Xiaofeng; Lin, Jingqiang (2026): "Starlink in the Wild: Multi-Perspective Measurements via DNS", in: Proceedings of the ACM Web Conference. (DOI)] for DNS used as the instrument to measure something else entirely.
Figures from scripts/report_dns.mjs over the 5,859-paper extraction. Every count is of papers, with its denominator in the table header or the sentence. The tool fold is scripts/dns_fold.mjs (85 self-tests, 2 unmapped strings out of 976 candidate tuples); it names its off-topic families rather than discarding them silently, because a loose enough candidate sweep for hnsd and resolvectl also catches every speech-recognition tool in the corpus.
| Kind | Papers naming ≥1 (corpus-wide) | In the 244 | Counts as “a DNS paper” |
|---|---|---|---|
| passive-dns | 65 | 65 | yes |
| resolver-software | 62 | 62 | yes |
| query-tool | 60 | 60 | yes |
| custom / home-grown | 34 | 34 | yes |
| public-resolver | 29 | 29 | yes |
| dnssec-tool | 3 | 3 | yes |
| open-resolver-census | 1 | 1 | yes |
| scanner-generic (ZMap, ZGrab, Scapy) | 156 | 38 | no |
| vantage-platform (RIPE Atlas 75, MobileAtlas 2) | 77 | 24 | no |
Rows do not sum to 244: a paper naming BIND and Unbound is in one row twice, and most papers name instruments in several kinds.
For the papers that stood up a resolver — to test it, to control it, or to serve crafted answers — the ranking over the whole corpus is BIND 29, Unbound 25, PowerDNS 11, dnsmasq 9, Knot 7, NSD 5, Microsoft DNS and Simple DNS Plus 5, MaraDNS 3, and single-digit counts for CoreDNS, djbdns, systemd-resolved, gdnsd and hnsd. Six of the papers in this family run the software as part of an attack or lab rig rather than to measure DNS in the world, and all six are dnsmasq — a rogue access point, a malicious 4G base station, a spoofing VPN test bed. A seventh lab-rig case sits in the DNSSEC-tool family. All seven are listed individually on dns so the count can be adjusted.
16 of the 244 match a case-sensitive full-text probe for /\b(?:LLM|large language model|GPT-[345]|ChatGPT|Llama|Gemini|Claude)\b/, rising 0 / 0 / 3 / 5 / 8 across the five windows. The case sensitivity is load-bearing and the report prints both: matching case-insensitively gives 19 papers and 0/0/3/7/9, because it also fires on ordinary words. A probe hit is not a method, so all sixteen case-sensitive hits were read. What they actually do:
So: an LLM is a defensible labelling aid for the *semantics of hostnames*, where the input is a string and a human could do the job. It is not, on this evidence, current practice for classifying DNS behaviour, and nothing in the 2025–2026 slice supports writing one into a methods section as the standard approach. Note where the signal lives: the 8 is the 2025–2026 window, which is provisional — see the caveat on the year table — so the growth is real but rests on the thinnest years in the corpus.
Every number above is a count of papers from the 5,859-paper extraction, with its denominator named in the same sentence or table header. Sentinels (not-stated, none-mentioned) are never counted as answers. Free-text tool names are folded before counting and the residue is published. studyTypes is the least stable field in the schema and its shares are rankings, not precise percentages. Full-text probes read paper.cols.txt (4 of 5,859 papers have none and count as negatives) and are upper bounds: a mention in related work matches. 2025–2026 venue-years are provisional; see Corpus.
Three specific limits worth stating on the page rather than burying:
The full query log, the fold with its residue and self-tests, the hand-read verdicts, every external check with its command and output, and the unedited report script output are on dns.
rfc8499.json has status BEST CURRENT PRACTICE and obsoleted_by RFC 9499; rfc9499.json has pub_date March 2024. Command in dns.security.googleblog.com/2022/07/dns-over-http3-in-android.html; fetched and the sentence re-read on 2026-08-27.blog.apnic.net/2025/09/02/discovering-the-discovery-of-designated-resolvers/, fetched 2026-08-27.10.1145/3822163.3827932. Confirmed via Crossref on 2026-08-27: title, the three authors, Proceedings of the 2026 Applied Networking Research Workshop, published 2026-07-20. The full text is behind an ACM 403 from this host, so this sentence rests on the metadata plus the authors' APNIC write-up, not on a reading of the paper.