design:dns
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| design:dns [2026/08/27 22:52] – Second self-review pass: the six lab-rig cases in the resolver-software family are all dnsmasq and the seventh sits in a different family (was wrongly attributed to seven resolver-software papers); and the LLM growth signal lives in the provisional 2025-2 karel.kubicek.claude | design:dns [2026/08/27 23:21] (current) – Generic (fable) review applied. Worst finding: 'What to report' claimed the extraction measures which resolver a paper used, which the provenance page says in bold it cannot — the 42.2% is a tool-version rate, and the page now publishes no resolver-identi karel.kubicek.claude | ||
|---|---|---|---|
| Line 4: | Line 4: | ||
| <WRAP important> | <WRAP important> | ||
| - | **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, **28 of the 75** Tranco top-100 names that had an address at all got **completely disjoint** answers from Google, Cloudflare and Quad9 — no address | + | **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, |
| </ | </ | ||
| - | Everything below about "the literature" | + | Everything below about "the literature" |
| ===== Is this your page? ===== | ===== Is this your page? ===== | ||
| Line 66: | Line 66: | ||
| ^ Instrument ^ Papers ^ State on 2026-08-27 ^ Use it when ^ | ^ Instrument ^ Papers ^ State on 2026-08-27 ^ Use it when ^ | ||
| | **ZDNS** {[izhikevich2022_zdns]} | 13 | Current. '' | | **ZDNS** {[izhikevich2022_zdns]} | 13 | Current. '' | ||
| - | | '' | + | | '' |
| - | | **DNS libraries** — '' | + | | **DNS libraries** — '' |
| | **massdns** | 5 | Maintained but slow-moving: | | **massdns** | 5 | Maintained but slow-moving: | ||
| - | | **fpdns** | 7 | Legacy. Fingerprints | + | | **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 |
| - | | **DNSViz** | 3 | Current: v0.11.1 (2025-04-21). | DNSSEC chain validation and diagnosis, one zone at a time. | | + | | **DNSViz** | 2 | Current: v0.11.1 (2025-04-21). | DNSSEC chain validation and diagnosis, one zone at a time. The fold's '' |
| | **home-grown code** | 34 | — | See the warning below. | | | **home-grown code** | 34 | — | See the warning below. | | ||
| Line 80: | Line 80: | ||
| ^ Source ^ Papers ^ State on 2026-08-27 ^ What it is ^ | ^ Source ^ Papers ^ State on 2026-08-27 ^ What it is ^ | ||
| - | | **Farsight DNSDB** (now DomainTools) | 28 | **Renamed and re-homed.** '' | + | | **Farsight DNSDB** (now DomainTools) | 28 | **Renamed and re-homed.** '' |
| | **OpenINTEL** | 14 | Current and actively extended: forward and reverse DNS, zone-based and top-list-based, | | **OpenINTEL** | 14 | Current and actively extended: forward and reverse DNS, zone-based and top-list-based, | ||
| - | | **Rapid7 Open Data** (Sonar FDNS / RDNS) | 8 | **Still live**, contrary to what a 2022-era memory will tell you: '' | + | | **Rapid7 Open Data** (Sonar FDNS / RDNS) | 8 | **Still live**, contrary to what a 2022-era memory will tell you: '' |
| | **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. | | | **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. | | + | | **Active DNS Project**; **360 / 114DNS**; **DNS Observatory** | 2 / 4 / 1 | Various. | Regional and vendor feeds; the Chinese feeds are the only route to some of that namespace. Spamhaus' |
| ==== Where you ask from ==== | ==== Where you ask from ==== | ||
| - | **RIPE Atlas** appears in 77 papers corpus-wide and 24 of the 244 here. It is not in the membership rule because most of those 77 use it for traceroute, not DNS — but it is the standard way to ask the same question from thousands of vantage points, and DNS is one of its native measurement types. Two papers on this page are built on it: {[randall2021_home]} | + | **RIPE Atlas** appears in **75** |
| **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. [[Design: | **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. [[Design: | ||
| Line 96: | Line 96: | ||
| ==== 1. The answer depends on which resolver you asked ==== | ==== 1. The answer depends on which resolver you asked ==== | ||
| - | This is the oldest finding on the page and it has never stopped being true. Ager et al. {[ager2010_comparing]} 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."// | + | This is the oldest finding on the page and it has never stopped being true. Ager et al. {[ager2010_comparing]} 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."// |
| 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**, | 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**, | ||
| Line 131: | Line 131: | ||
| ^ Protocol ^ RFC and status (checked 2026-08-27) ^ Papers in the 244, by window ^ Where it stands ^ | ^ 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. Android's "Private DNS" is DoT, which is why it matters for mobile measurement even though browser work ignores | + | | **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 |
| | **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. | | | **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, | | **DoQ** — DNS over QUIC | RFC 9250, May 2022, Proposed Standard | 0 / 0 / 4 / 1 / 1 | Standardised, | ||
| + | | **DoC** — DNS over CoAP | RFC 9953, **March 2026**, Proposed Standard | 0 / 0 / 0 / 0 / 0 | Five months old at the time of writing. The zero is a **run probe, not an assumption**: | ||
| | **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. | | | **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.** | | | **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.** | | ||
| Line 140: | Line 141: | ||
| <WRAP tip> | <WRAP tip> | ||
| - | **DDR is where the measurement question now is, and the one paper that looked says the deployment | + | **DDR is where the measurement question now is, and what it measures |
| + | |||
| + | One paper is not a literature, and this one's central finding is contested outside these seven venues. Nosyk, Duda and Korczyński analysed //" | ||
| + | |||
| + | 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 | ||
| </ | </ | ||
| + | |||
| + | **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, // | ||
| **Two dated facts a student will otherwise get wrong.** | **Two dated facts a student will otherwise get wrong.** | ||
| Line 161: | Line 168: | ||
| ===== Active DNS measurement is scanning ===== | ===== Active DNS measurement is scanning ===== | ||
| - | 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. {[khrer2015_going]} //" | + | 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. {[khrer2015_going]} //" |
| - | 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: | + | 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. A plausible reading |
| ^ Field ^ DNS population (241 empirical of the 244) ^ Whole corpus (5,118 empirical) ^ | ^ Field ^ DNS population (241 empirical of the 244) ^ Whole corpus (5,118 empirical) ^ | ||
| Line 189: | Line 196: | ||
| </ | </ | ||
| - | A DNS methods section is complete when a replicator can answer all of these. | + | A DNS methods section is complete when a replicator can answer all of these. |
| * **Which resolver**, by address and by name, including whether it was your own recursive server, the system resolver, or a public one. If more than one, which for which measurement. | * **Which resolver**, by address and by name, including whether it was your own recursive server, the system resolver, or a public one. If more than one, which for which measurement. | ||
| * **Where from** — the source addresses and networks, and whether they were datacenter or residential ([[Design: | * **Where from** — the source addresses and networks, and whether they were datacenter or residential ([[Design: | ||
| - | * **The transport** — Do53 over UDP or TCP, DoT, DoH, DoQ — and whether you fell back. | + | * **The transport** — Do53 over UDP or TCP, DoT, DoH, DoQ — and whether you fell back, including what you did when a resolver set the **TC bit** on a UDP answer. Only **9.0%** of the 244 mention truncation or TCP fallback at all. |
| + | * **Which record types, and which address family.** A study that queries A only and a study that queries A and AAAA produce different "did not resolve" | ||
| * **EDNS0 and ECS**: whether you sent an OPT record, and whether you sent a client-subnet option and with what prefix. This changes the answer, so an unstated setting makes the result unreproducible. | * **EDNS0 and ECS**: whether you sent an OPT record, and whether you sent a client-subnet option and with what prefix. This changes the answer, so an unstated setting makes the result unreproducible. | ||
| * **Query rate and retry policy**, and what you did on SERVFAIL and timeout. A timeout silently recorded as "no record" | * **Query rate and retry policy**, and what you did on SERVFAIL and timeout. A timeout silently recorded as "no record" | ||
| Line 216: | Line 224: | ||
| Python 3.8+, standard library only: no dnspython, no dig, nothing to install. | Python 3.8+, standard library only: no dnspython, no dig, nothing to install. | ||
| + | |||
| + | **This script queries A records only.** That is a deliberate limit and a real | ||
| + | one: a name with only AAAA records, or one reachable through an HTTPS/SVCB | ||
| + | record, is reported here as having no address. If your study is dual-stack, | ||
| + | query both families and report both -- "the name did not resolve" | ||
| + | has no IPv4 address" | ||
| + | the second. | ||
| What it reports, per name: | What it reports, per name: | ||
| - | * the A-record set each resolver returned | + | * the A-record set each resolver returned, and whether the answer was |
| + | truncated (TC set) rather than complete | ||
| * whether all resolvers agree, agree on the /24, or disagree outright | * whether all resolvers agree, agree on the /24, or disagree outright | ||
| * the TTL each resolver returned, and the TTL on an immediate second query | * the TTL each resolver returned, and the TTL on an immediate second query | ||
| Line 237: | Line 253: | ||
| import argparse | import argparse | ||
| + | import itertools | ||
| import json | import json | ||
| import random | import random | ||
| Line 292: | Line 309: | ||
| def parse_answers(buf, | def parse_answers(buf, | ||
| - | """ | + | """ |
| + | |||
| + | The TC (truncation) bit matters here and is easy to skip. This script sends | ||
| + | no EDNS0 OPT record on purpose (see build_query), | ||
| + | capped at 512 bytes; a resolver with more addresses than fit sets TC and | ||
| + | returns a partial answer. Recording that as the resolver' | ||
| + | would manufacture " | ||
| + | is set the result is flagged rather than silently trusted -- a real | ||
| + | measurement would retry over TCP, which this script deliberately does not | ||
| + | do, because the point is to show you the condition, not to paper over it. | ||
| + | | ||
| if len(buf) < 12: | if len(buf) < 12: | ||
| raise ValueError(" | raise ValueError(" | ||
| Line 299: | Line 326: | ||
| raise ValueError(" | raise ValueError(" | ||
| rcode = flags & 0x0F | rcode = flags & 0x0F | ||
| + | truncated = bool(flags & 0x0200) | ||
| off = 12 | off = 12 | ||
| for _ in range(qd): | for _ in range(qd): | ||
| Line 310: | Line 338: | ||
| out.append((ttl, | out.append((ttl, | ||
| off += rdlen | off += rdlen | ||
| - | return rcode, out | + | return rcode, truncated, out |
| def query(server, | def query(server, | ||
| """ | """ | ||
| - | txid, pkt = build_query(name, | ||
| sock = socket.socket(socket.AF_INET, | sock = socket.socket(socket.AF_INET, | ||
| - | sock.settimeout(timeout) | ||
| started = time.time() | started = time.time() | ||
| + | deadline = started + timeout | ||
| try: | try: | ||
| + | # Inside the try: an unencodable name must return a dict like every | ||
| + | # other failure, not raise. The docstring promises that. | ||
| + | txid, pkt = build_query(name, | ||
| sock.sendto(pkt, | sock.sendto(pkt, | ||
| # Accept only a datagram from the address we asked. This does not make | # Accept only a datagram from the address we asked. This does not make | ||
| Line 325: | Line 355: | ||
| # does -- but it stops a stray answer from a different box being | # does -- but it stops a stray answer from a different box being | ||
| # recorded as this resolver' | # recorded as this resolver' | ||
| + | # | ||
| + | # 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: | while True: | ||
| + | remaining = deadline - time.time() | ||
| + | if remaining <= 0: | ||
| + | raise socket.timeout(" | ||
| + | sock.settimeout(remaining) | ||
| data, peer = sock.recvfrom(4096) | data, peer = sock.recvfrom(4096) | ||
| if peer[0] == server: | if peer[0] == server: | ||
| break | break | ||
| - | rcode, answers = parse_answers(data, | + | rcode, truncated, answers = parse_answers(data, |
| return {" | return {" | ||
| + | " | ||
| " | " | ||
| " | " | ||
| Line 336: | Line 377: | ||
| except Exception as exc: # timeout, refused, malformed | except Exception as exc: # timeout, refused, malformed | ||
| return {" | return {" | ||
| + | " | ||
| " | " | ||
| " | " | ||
| Line 347: | Line 389: | ||
| def verdict(per_resolver): | def verdict(per_resolver): | ||
| - | """ | + | """ |
| + | |||
| + | 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 " | ||
| + | itself. On a 100-name run, 17 of 28 names labelled " | ||
| + | three resolvers returning byte-identical addresses. If you extend this to | ||
| + | more resolvers, keep the comparison pairwise. | ||
| + | |||
| + | Verdicts, in order of severity: | ||
| + | identical | ||
| + | partial | ||
| + | same-/ | ||
| + | disjoint | ||
| + | no-A-record | ||
| + | one-answered exactly one resolver returned an address and the others | ||
| + | | ||
| + | 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[" | answering = {n: r for n, r in per_resolver.items() if r[" | ||
| if len(answering) < 2: | if len(answering) < 2: | ||
| Line 358: | Line 420: | ||
| for r in per_resolver.values()): | for r in per_resolver.values()): | ||
| return " | return " | ||
| + | if (len(answering) == 1 | ||
| + | and all(r[" | ||
| + | return " | ||
| return " | return " | ||
| - | sets = [frozenset(r[" | + | sets = [set(r[" |
| - | if len(set(sets)) | + | if all(a == b for a, b in itertools.combinations(sets, 2)): |
| return " | return " | ||
| - | if set.intersection(*[set(s) | + | if any(a & b for a, b in itertools.combinations(sets, 2)): |
| - | return "overlapping" | + | return "partial" |
| - | nets = [frozenset(slash24(ip) for ip in s) for s in sets] | + | nets = [{slash24(ip) for ip in s} for s in sets] |
| - | if set.intersection(*[set(n) | + | if any(a & b for a, b in itertools.combinations(nets, 2)): |
| return " | return " | ||
| return " | return " | ||
| + | |||
| + | |||
| + | |||
| + | def _self_test(): | ||
| + | """ | ||
| + | function used to have. `python3 resolver_disagreement.py --self-test`.""" | ||
| + | def r(ips, ok=True, rcode=" | ||
| + | return {" | ||
| + | " | ||
| + | cases = [ | ||
| + | # all three identical | ||
| + | ({" | ||
| + | # facebook.com in the 2026-08-27 run: two byte-identical, | ||
| + | # The all-at-once intersection called this " | ||
| + | ({" | ||
| + | " | ||
| + | # wikipedia.org: | ||
| + | ({" | ||
| + | " | ||
| + | # google.com: three mutually disjoint /16s -- genuinely disjoint | ||
| + | ({" | ||
| + | " | ||
| + | # no address in common but a shared /24 | ||
| + | ({" | ||
| + | # every resolver says NOERROR with no A record: a property of the zone | ||
| + | ({" | ||
| + | # a real failure is not the same thing | ||
| + | ({" | ||
| + | # NXDOMAIN everywhere is also not "no A record at the apex" | ||
| + | ({" | ||
| + | " | ||
| + | # only one resolver answered | ||
| + | ({" | ||
| + | # two resolvers, sets overlap partially | ||
| + | ({" | ||
| + | ] | ||
| + | bad = 0 | ||
| + | for i, (given, want) in enumerate(cases, | ||
| + | got = verdict(given) | ||
| + | if got != want: | ||
| + | bad += 1 | ||
| + | print(f" | ||
| + | print(f" | ||
| + | return 0 if bad == 0 else 1 | ||
| Line 383: | Line 492: | ||
| ap.add_argument(" | ap.add_argument(" | ||
| ap.add_argument(" | ap.add_argument(" | ||
| + | ap.add_argument(" | ||
| + | help=" | ||
| args = ap.parse_args() | args = ap.parse_args() | ||
| + | |||
| + | if args.self_test: | ||
| + | raise SystemExit(_self_test()) | ||
| names = list(args.names) | names = list(args.names) | ||
| Line 431: | Line 545: | ||
| elif ttl2 == ttl1: | elif ttl2 == ttl1: | ||
| cached = f" | cached = f" | ||
| + | trunc = " | ||
| print(f" | print(f" | ||
| - | f" | + | f" |
| record.append({" | record.append({" | ||
| print() | print() | ||
| print(f" | print(f" | ||
| - | for v in (" | + | for v in (" |
| - | " | + | |
| if v in tally: | if v in tally: | ||
| print(f" | print(f" | ||
| comparable = sum(tally.get(v, | comparable = sum(tally.get(v, | ||
| - | | + | |
| + | disagree = comparable - tally.get(" | ||
| print() | print() | ||
| print(f" | print(f" | ||
| f" | f" | ||
| - | for v in (" | + | for v in (" |
| if v in tally and comparable: | if v in tally and comparable: | ||
| print(f" | print(f" | ||
| + | if comparable: | ||
| + | print(f" | ||
| + | f" | ||
| print() | print() | ||
| print(" | print(" | ||
| Line 454: | Line 573: | ||
| print(" | print(" | ||
| print() | print() | ||
| - | print("' | + | print("' |
| - | print(" | + | print(" |
| - | print(" | + | print(" |
| - | print(" | + | print(" |
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| if args.json: | if args.json: | ||
| Line 469: | Line 591: | ||
| </ | </ | ||
| - | Real output, run on 2026-08-27 from a single European vantage point against the **Tranco top 100** (list ID '' | + | Real output, run on 2026-08-27 from a single European vantage point against the **Tranco top 100** (list ID '' |
| < | < | ||
| # resolvers: Google (8.8.8.8), Cloudflare (1.1.1.1), Quad9 (9.9.9.9) | # resolvers: Google (8.8.8.8), Cloudflare (1.1.1.1), Quad9 (9.9.9.9) | ||
| - | # names: 100 run at 2026-08-27T22:31:01Z | + | # names: 100 run at 2026-08-27T23:17:26Z |
| - | google.com -> disjoint | + | googleapis.com -> disjoint |
| - | Google | + | Google |
| - | Cloudflare | + | Cloudflare |
| - | Quad9 NOERROR | + | Quad9 NOERROR |
| summary A: all 100 names | summary A: all 100 names | ||
| identical | identical | ||
| - | | + | |
| - | disjoint | + | disjoint |
| no-A-record | no-A-record | ||
| summary B: the 75 names where at least two resolvers returned an address | summary B: the 75 names where at least two resolvers returned an address | ||
| identical | identical | ||
| - | | + | |
| - | disjoint | + | disjoint |
| + | NOT identical | ||
| </ | </ | ||
| - | Three 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: | + | Five 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: |
| - | - **'' | + | - **The number to quote is "not identical", |
| + | - **And "not identical" | ||
| - **25 of the 100 names have no A record at the apex.** They are CDN and infrastructure zones. A pipeline that resolves and drops failures loses them silently. | - **25 of the 100 names have no A record at the apex.** They are CDN and infrastructure zones. A pipeline that resolves and drops failures loses them silently. | ||
| - | - **Summary B is the number to quote**, and the script | + | - **This is IPv4 only.** The script |
| + | - **Read | ||
| Run it with '' | Run it with '' | ||
| Line 529: | Line 654: | ||
| | open-resolver-census | 1 | 1 | yes | | | open-resolver-census | 1 | 1 | yes | | ||
| | scanner-generic (ZMap, ZGrab, Scapy) | 156 | 38 | no | | | scanner-generic (ZMap, ZGrab, Scapy) | 156 | 38 | no | | ||
| - | | vantage-platform (RIPE Atlas) | 77 | 24 | 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. | 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. | ||
| Line 539: | Line 664: | ||
| ==== LLMs have arrived, but not as a DNS classifier ==== | ==== LLMs have arrived, but not as a DNS classifier ==== | ||
| - | 16 of the 244 match a full-text probe for LLM or a named model, rising 0 / 0 / 3 / 5 / 8 across the five windows. **A probe hit is not a method**, so all sixteen were read. What they actually do: | + | 16 of the 244 match a **case-sensitive** |
| * **One classifies DNS with an LLM**: a CCS 2024 poster on DoH tunnel detection. | * **One classifies DNS with an LLM**: a CCS 2024 poster on DoH tunnel detection. | ||
design/dns.1787871179.txt.gz · Last modified: by karel.kubicek.claude
