A distributed database with no center
Humans use names, and packets need addresses. DNS (domain name system) is the lookup service in between, and it is worth understanding as a database design, because its shape explains every one of its behaviors.
A name is read right to left, from the most general label to the most specific. The full form ends in an invisible dot for the root.
www.docs.example.com. | | | | | | | | | root, the empty label | | | top level domain, com | | the domain somebody registered | a subdomain the owner created the host or service name
That full form is an FQDN (fully qualified domain name). Each dot separated piece is a label, at most 63 characters, and the whole name is at most 253.
The hierarchy is not decoration, it is delegation. The root operators do not
know where example.com lives. They only know which servers are authoritative for
com. The com servers do not know about www.docs.example.com, they only know
the servers for example.com. Each level knows only the next level down, which is
exactly the property that lets one system serve hundreds of millions of names with
no central database and no single owner.
The four actors
| Actor | Role |
|---|---|
| Stub resolver | the tiny client in your operating system, asks one question |
| Recursive resolver | does the legwork and caches, run by your ISP or 1.1.1.1 |
| Root servers | 13 named server sets that answer "who handles this TLD" |
| TLD servers | answer "who is authoritative for this domain" |
| Authoritative servers | hold the actual records for the domain |
Two query styles exist and the difference matters.
- A recursive query says "get me the final answer, I will wait". Your stub resolver sends this to the recursive resolver. - An iterative query says "answer if you can, otherwise tell me who to ask next". The recursive resolver sends these to the root, the TLD, and the authoritative servers in turn.
So there is exactly one recursive query and several iterative ones per full
lookup. Walk the whole thing for www.example.com on a cold cache:
1. Your browser asks the stub resolver, which asks the recursive resolver at
192.168.1.1 or 1.1.1.1, port 53.
2. The resolver asks a root server: "A record for www.example.com?" The root
replies with a referral: "I do not know, but the com servers are
a.gtld-servers.net and others, here are their addresses."
3. The resolver asks a com server the same question, and gets another
referral: "the servers for example.com are ns1.example.com and
ns2.example.com."
4. The resolver asks ns1.example.com, which is authoritative, and finally
gets an answer: "www.example.com is 93.184.216.34, TTL 3600."
5. The resolver caches every step it learned and returns the answer to your stub
resolver, which hands it to the browser.
Four queries for one name, on a cold cache. Almost every real lookup skips most of them, because the resolver has the root and TLD referrals cached already, which is the subject of Lesson 8-2.
Watching the chain yourself
dig +trace performs the iterative walk itself instead of asking a resolver, so
you see every referral. Run it on any name you like.
dig +trace www.example.com
. 518400 IN NS a.root-servers.net. . 518400 IN NS b.root-servers.net. ;; Received 811 bytes from 192.168.1.1#53(192.168.1.1) com. 172800 IN NS a.gtld-servers.net. com. 172800 IN NS b.gtld-servers.net. ;; Received 1174 bytes from 198.41.0.4#53(a.root-servers.net) example.com. 172800 IN NS ns1.example.com. example.com. 172800 IN NS ns2.example.com. ;; Received 576 bytes from 192.5.6.30#53(a.gtld-servers.net) www.example.com. 3600 IN A 93.184.216.34 ;; Received 60 bytes from 199.43.135.53#53(ns1.example.com)
Read the Received from lines and the whole hierarchy is visible: the root
answered with com name servers, a com server answered with example.com name
servers, and an example.com server answered with the address. NS records are
the delegations, and the number before IN is the TTL in seconds.
For everyday work, the short form is enough:
dig +short www.example.com A
93.184.216.34
Two transport notes. DNS normally uses UDP port 53, because a query and answer fit in one small datagram and a retry is cheaper than a handshake. It falls back to TCP port 53 when the answer is too large, which is common for DNSSEC signed zones and long record sets. Encrypted variants exist and are now widespread, DoT on TCP port 853 and DoH over HTTPS on 443, and both hide your lookups from the network while making them invisible to your own troubleshooting tools.
The failures and what they mean
DNS returns a status code, and each one points somewhere specific.
| Status | Name | Means |
|---|---|---|
| NOERROR | success | an answer, or an empty answer for that record type |
| NXDOMAIN | no such name | authoritatively, this name does not exist |
| SERVFAIL | server failure | the resolver could not complete, or DNSSEC failed |
| REFUSED | refused | the server will not answer you, often a policy filter |
| Timeout | no reply | port 53 blocked, wrong resolver address, server down |
Three distinctions that resolve most confusion:
- NXDOMAIN is not NOERROR with no records. NXDOMAIN says the name is absent. NOERROR with an empty answer says the name exists but has no record of that type, for example an A query for a name that only has MX records. Different problems, different fixes. - SERVFAIL is usually not your fault. It commonly means the authoritative servers are unreachable or the zone's DNSSEC signatures do not validate. - A lame delegation is when the parent points at a server that is not actually authoritative for the zone. Lookups then work intermittently, depending on which name server the resolver happened to try, and intermittent DNS is nearly always either this or one of several name servers being out of sync.
A practical habit. When a name behaves strangely, query the authoritative server directly and compare with what a public resolver returns.
dig @ns1.example.com www.example.com +norecurse dig @1.1.1.1 www.example.com
If the authoritative answer is right and the public one is wrong, you are looking at a cache, which is the next lesson.