Old-School Networking / 2026 ・ No.09
いにしえのネットワーク入門
第9回 DNS ── 名前から住所を引く、インターネットの電話帳
公開
第8回まで、curl には --resolve で「expenses.example は 203.0.113.20」と教えていました。今回はそれをやめ、本物の仕組み、DNS に名前を引かせます。
練習用に、ルート・.example・expenses.example の3段の DNS サーバーと、プロバイダの役のリゾルバーを立てました。名前の木をたどる道のり、答えを覚えておく時間(TTL)、hosts との食い違い、DNS が止まったときの症状まで、実測で確かめます。
名前の層
新井前回の --resolve、本当はどうやって名前から住所がわかるんですか?
古井世界中の名前を1台が知っているわけではない。名前を木の形に分けて、枝ごとに担当を決めてある。知らなければ「あっちに聞いて」と紹介する。それをたどるんだ。
新井たらい回しじゃないですか。
古井そう、よくできたたらい回しだ。しかも、一度回った答えはしばらく覚えておく。
全12回の路線図(いまここ:第9回)
- 名前の木 ── ルート、.example、expenses.example と、段ごとに担当がいる
- リゾルバー ── PC の代わりに木をたどる。はじめては 0.166 秒(実測)
- TTL とキャッシュ ── 覚えている間は 0 ミリ秒、切れたら聞き直し(実測)
- 開発者の落とし穴 ── hosts と dig の食い違い、DNS が止まったときの症状(実測)
DNS は、TCP/IP の層で言えば第7層、アプリの層の仕組みです。それでもアプリの通信より先に必ず動くので、ここで扱います。練習用ネットワークは、PC とプロバイダのリゾルバー(Unbound)を線の手前に、3段の DNS サーバー(NSD)と申請のサーバーを、片道 20 ミリ秒のいたずら線の向こうに置きました。
名前の木
名前は木の形に分けられ、枝ごとに担当の DNS サーバーがいる
www.expenses.example は、右から読みます。いちばん右の見えない点がルート、その下に example、さらに expenses、最後に www。ルートの DNS サーバーは example の担当を知っていて、example の担当は expenses の担当を知っている。この「担当を知っている」ことを委任と呼びます。
本物のインターネットでは、ルートは a から m までの 13 の名前を持ち、12 の組織が運用しています。実際には同じ中身の写しが世界中に千台以上置かれていて、近くのものが答えます。この回の練習では、ルートも筆者が作りました(a.root.lab、203.0.113.1)。
リゾルバーがたどる道
PC は、木をたどる仕事を自分ではしません。家のルーターやプロバイダが用意したリゾルバー(キャッシュ DNS サーバー)に「expenses.example の住所は?」と1回聞くだけです。リゾルバーの側で記録しました。
# リゾルバーで記録:PC の1つの問い合わせのために、リゾルバーは3か所に順に聞いた(片道 20 ms の線の向こう) 0.000 PC → resolver A expenses.example 0.000 resolver → root NS <Root> 0.041 root → resolver NS <Root> NS a.root.lab A 203.0.113.1 0.042 resolver → root A expenses.example 0.082 root → resolver A expenses.example NS ns1.example A 203.0.113.2 0.083 resolver → .example A expenses.example 0.124 .example → resolver A expenses.example NS ns1.expenses.example A 203.0.113.3 0.124 resolver → expenses.example A expenses.example 0.165 expenses.example → resolver A expenses.example A 203.0.113.20 NS ns1.expenses.example A 203.0.113.3 0.166 resolver → PC A expenses.example A 203.0.113.20
PC は1回聞くだけ。たどるのはリゾルバー(実測の時刻)
リゾルバーは、まずルートが生きているかを確かめ(ルートの NS)、それからルートに聞きます。ルートの答えは「知らない。.example は ns1.example(203.0.113.2)に聞いて」という紹介です(表紙の写真)。.example も同じように expenses.example を紹介し、最後に expenses.example の DNS サーバーが 203.0.113.20 と答えました。線を4往復して 0.166 秒です。
同じ道のりを、PC から順にたどって見せてくれるのが dig +trace です。
# dig +trace:PC が自分でルートから順にたどる(← は答えた DNS サーバー)
pc$ dig +trace expenses.example
. 86368 IN NS a.root.lab.
← 198.51.100.53 (198.51.100.53)
example. 86400 IN NS ns1.example.
← a.root.lab (203.0.113.1)
expenses.example. 3600 IN NS ns1.expenses.example.
← ns1.example (203.0.113.2)
expenses.example. 30 IN A 203.0.113.20
expenses.example. 3600 IN NS ns1.expenses.example.
← ns1.expenses.example (203.0.113.3)
本物のリゾルバーの多くは、ルートには「example のことだけ」、.example には「expenses.example のことだけ」と、必要な分だけ名前を見せて聞きます(QNAME minimisation)。この回は流れを見やすくするため、その設定を切っています。
覚えておく:TTL とキャッシュ
毎回 0.166 秒かけて木をたどっていたら、Web は遅くてたまりません。そこでリゾルバーは、答えを覚えておきます。どのくらい覚えてよいかは、答えについている TTL(秒)が決めます。expenses.example の住所は、練習のため 30 秒にしました。
# PC からリゾルバー(198.51.100.53)へ。答えの TTL(あと何秒覚えておくか)と、かかった時間
pc$ dig expenses.example @198.51.100.53
expenses.example. 30 IN A 203.0.113.20 168 ms ← はじめて
expenses.example. 29 IN A 203.0.113.20 0 ms ← 1 秒後
expenses.example. 19 IN A 203.0.113.20 0 ms ← 10 秒後
expenses.example. 10 IN A 203.0.113.20 0 ms ← 20 秒後
expenses.example. 30 IN A 203.0.113.20 44 ms ← 30 秒後(切れた)
TTL が減っていき、切れたら聞き直す(実測)
覚えている間は 0 ミリ秒で、TTL は 29、19、10 と減っていきました。30 秒で切れると、リゾルバーは聞き直しますが、ルートと .example の紹介はまだ覚えている(TTL は1日と1時間)ので、expenses.example にだけ聞いて 0.044 秒でした。
サーバーの引っ越しで住所を変えるとき、古い答えは TTL が切れるまで世界中のリゾルバーに残ります。俗に「DNS の浸透待ち」と呼ばれるのは、この期限切れ待ちのことです。引っ越しの数日前に TTL を短くしておくのが定石です。
いろいろなレコード
DNS が答えるのは住所だけではありません。名前に結びつけた1行1行をレコードと呼び、種類があります。
# expenses.example のいろいろなレコード(数字は TTL、秒)
expenses.example. 29 IN A 203.0.113.20
expenses.example. 30 IN AAAA 2001:db8::20
www.expenses.example. 300 IN CNAME expenses.example.
expenses.example. 3600 IN MX 10 mail.expenses.example.
expenses.example. 3600 IN TXT "kiji practice zone for part 9"
expenses.example. 3600 IN NS ns1.expenses.example.
| 種類 | 意味 | 開発者が出会う場面 |
|---|---|---|
| A / AAAA | IPv4 / IPv6 の住所 | サーバーの住所を登録する |
| CNAME | 別名。「この名前は、あの名前と同じ」 | www をドメインのてっぺんに向ける、クラウドのサービスに向ける |
| MX | メールを受け取るサーバー(数字は優先順位) | 独自ドメインでメールを受ける |
| TXT | 自由な文字列 | ドメインの持ち主の確認、送信元の確認(SPF など) |
| NS | このゾーンの担当の DNS サーバー | ドメインを買ったあと、使う DNS サービスを指定する |
| SOA | ゾーンの基本情報(「ない」答えの期限など) | 次の章 |
CNAME の名前には、ほかのレコードを置けません。ドメインのてっぺん(ここでは expenses.example そのもの)には NS や SOA が必ずあるので、てっぺんを CNAME にはできない、という決まりは知っておくとよいでしょう。
ない名前
# ない名前を聞く
pc$ dig nosuch.expenses.example
status: NXDOMAIN
expenses.example. 60 IN SOA ns1.expenses.example. admin.expenses.example. 2026102501 1800 900 604800 60
# 「ない」という答えも、SOA の最後の数(60 秒)のあいだ覚えておく
1回目: 40 ms 2回目: 0 ms
ない名前を聞くと、答えは NXDOMAIN(そんな名前はない)です。答えには SOA が添えられていて、その最後の数(60 秒)が「ない」という答えを覚えておいてよい時間です。2回目は 0 ミリ秒でした。打ち間違いの名前に何度アクセスしても、毎回木をたどらずに済みます。逆に、新しい名前を登録する前にうっかり引いてしまうと、「ない」をしばらく覚えられてしまいます。
アプリが名前を引くとき
いよいよ --resolve なしで、curl に申請を送らせます。リゾルバーのキャッシュを空にしてから2回続けました。
# curl で HTTPS の申請(--resolve なし)。リゾルバーのキャッシュを空にしてから2回
1回目 namelookup 0.1667 connect 0.208 tls 0.252 total 0.294 (201)
2回目 namelookup 0.0006 connect 0.041 tls 0.087 total 0.144 (201)
1回目は、名前を引くのに 0.167 秒。そのあと第6回の TCP、第8回の TLS と続きます。2回目は 0.0006 秒で、全体も 0.294 秒から 0.144 秒になりました。初めてのサイトがほんの少し遅いのは、こういうことです。
名前引きは、手前から順に探す
アプリ(curl やブラウザ)は、DNS に聞く前に、手前の場所から探します。中でも hosts ファイルは、DNS より先に見られます。開発中に書いた1行が残っていると、こうなります。
# PC の hosts に、古い住所 203.0.113.21 が書き残されていたら
/etc/hosts: 203.0.113.21 expenses.example
# OS の名前引き(hosts を先に見る)
pc$ getent ahostsv4 expenses.example
203.0.113.21 STREAM expenses.example
# dig(DNS にしか聞かない)
pc$ dig +short expenses.example
203.0.113.20
# curl(OS の名前引きを使う)
curl: (28) Failed to connect to expenses.example port 3443 after 3002 ms: Timeout was reached
dig は 203.0.113.20 と正しく答えるのに、curl は hosts の古い住所 203.0.113.21 へつなぎに行って、3 秒で時間切れになりました。dig は DNS にしか聞かないので、hosts を見ません。「dig では合っているのに、アプリだけつながらない」ときは、hosts と、OS やブラウザのキャッシュを疑いましょう。
DNS が止まると
# リゾルバーのプログラムが止まっている(「そこには誰もいない」と ICMP が返る)
curl: (6) Could not resolve host: expenses.example
total 0.0001 s
# リゾルバーまで届かない(線が切れている)
curl: (6) Could not resolve host: expenses.example
total 6.023 s
リゾルバーのプログラムだけが止まっている場合は、OS が「そこには誰もいない」(第7回の ICMP)と知らせるので、curl はすぐ Could not resolve host になりました。ところがリゾルバーまで届かない場合は、6 秒待たされてから同じエラーになりました。ここでは PC がリゾルバーの MAC を ARP で6回尋ねた分です。途中で消える場合は、Linux の既定(resolv.conf の timeout 5 秒、attempts 2 回)に従って、さらに長く待つこともあります。
DNS が止まると、住所で書いたものは動くのに、名前で書いたものがすべて止まります。利用者からは「ネットにつながらない」「遅い」に見えます。切り分けは、名前と住所の両方で試すのが第一歩です(第12回)。
DNS の中身を隠す
この回の記録はすべて、UDP の 53 番で、聞いた名前がそのまま読める DNS でした。第8回で、HTTPS にしても名前(SNI)が見えると書きましたが、DNS の問い合わせからも名前はわかります。
DNS の問い合わせを包む方法
そこで、DNS の問い合わせ自体を TLS で包む DoT(853 番)や、HTTPS で包む DoH(443 番)が使われるようになりました。いまの主なブラウザや OS は、設定で DoH を使えます。ただし、リゾルバー自身には名前が見えるので、どのリゾルバーを信頼するかは別の問題として残ります。なお、答えが大きいと UDP から TCP に切りかえて聞き直すしくみもあり(第7回の 1232 バイト)、TCP の 53 番も DNS の番号です。
自分の PC で確かめる
| 調べたいこと | Mac | Windows | Linux |
|---|---|---|---|
| 名前を引く | dig example.com | Resolve-DnsName example.com | dig example.com |
| 道のりをたどる | dig +trace example.com | (nslookup で順に) | dig +trace example.com |
| OS としての答え(hosts 込み) | dscacheutil -q host -a name example.com | ping -n 1 example.com | getent ahosts example.com |
| 使っているリゾルバー | scutil --dns | ipconfig /all | resolvectl status |
| OS のキャッシュを消す | sudo killall -HUP mDNSResponder | ipconfig /flushdns | resolvectl flush-caches |
dig の答えの2列目が TTL です。同じ名前を何度か引いて、数が減っていくのを見てみましょう。
結 — 一枚まとめと、次回
- 名前は右から読む木。ルート → .example → expenses.example と、段ごとに担当がいる(委任)。
- PC はリゾルバーに1回聞くだけ。リゾルバーが紹介をたどり、はじめては線を4往復して 0.166 秒。
- 答えは TTL の間覚えておく。覚えている間は 0 ミリ秒、切れたら必要な所にだけ聞き直して 0.044 秒。
- A・AAAA・CNAME・MX・TXT・NS・SOA。てっぺんは CNAME にできない。
- ない名前(NXDOMAIN)も SOA の数だけ覚えておく。
- アプリは hosts を DNS より先に見る。dig は hosts を見ないので、食い違うことがある。
- リゾルバーまで届かないと、名前で書いたものはすべて、待たされたあと止まる。DoT・DoH で問い合わせを隠せる。
新井名前がわかって、つながって、鍵もかかって。やっと申請の中身の番ですね。
古井次回は HTTP。第1回からずっと送ってきた POST /api/expenses を、1行ずつ読む。
参考
- RFC 1034 — Domain Names: Concepts and Facilities
- RFC 1035 — Domain Names: Implementation and Specification
- RFC 2308 — Negative Caching of DNS Queries
- RFC 9156 — DNS Query Name Minimisation to Improve Privacy
- RFC 7858 — DNS over Transport Layer Security (TLS)
- RFC 8484 — DNS Queries over HTTPS (DoH)
- RFC 2606 — Reserved Top Level DNS Names(.example)
- Root Server Technical Operations Association — root-servers.org
- IANA — Root Zone Database
- resolv.conf(5) — timeout と attempts の既定値
- NLnet Labs — Unbound
- NLnet Labs — NSD
記録は、筆者がクラウド上の Linux(Ubuntu 24.04、カーネル 6.18)にネットワーク名前空間で作った練習用ネットワークで、2026年10月25日に取ったものです。リゾルバーは Unbound 1.19.2(DNSSEC の検証と QNAME minimisation は切ってある)、ルート・.example・expenses.example の DNS サーバーは NSD 4.8.0 で、名前と住所はすべて説明用に予約されたもの、または練習用に作ったものです。リゾルバーと DNS サーバーの間は、第6回のいたずら線(片道 20 ミリ秒)です。表の数字はすべて、この記録とそのときのコマンドの出力から読んだ実際の値です。ルートサーバーの数は root-servers.org によります。登場人物は架空の人物です。
いにしえのネットワーク入門 第9回 — DNS / 2026.10