【実務・中級編】 digにおけるDNSキャッシュのバイパスと再帰問い合わせフラグ(RDフラグ)の制御 – トラブルシューティング&ネットワーク運用監視実践ガイド

おう、お前ら!今日も元気にパケット捌いてるか?大規模データセンターのネットワーク、まるで血管網のようによくできてるとは言え、たまには血栓が詰まることもある。そんな時、一番厄介なのがDNSだ。

「Webサイトが見れない」「APIが応答しない」「SSHが繋がらない」――。
こんな時、最初に疑われるのがネットワーク。そして、ネットワークが正常に見えるのに解決しない場合、たいていDNSが絡んでくる。俺もNOCで数え切れないほどの夜をDNSのせいで過ごしてきた。

今回は、そんなDNSのトラブルシューティングで、お前らが「おっ、さすがシニア!」と唸るような強力な武器の一つ、dig +norecurseについて徹底的に解説してやる。単なるコマンドマニュアルのコピペじゃねぇ。現場の泥臭い経験から得た、生きた知恵を授けよう。

—

DNSトラブルシューティングの奥義:dig +norecurseでDNSキャッシュの闇を暴け!

1. 「名前解決できない」その一言に潜むDNSの深淵

お前らは日々、Web APIを設計したり、インフラを構築したりしている中で、「example.comに繋がらない」という事態に遭遇したことはないか?
pingは通る。curlでIPアドレス直打ちなら応答がある。なのに、ホスト名ではダメ。
こんな時、十中八九、DNSが怪しい。

しかし、いざdig example.comと打ってみても、正常な応答が返ってくる。
「あれ?どこもおかしくないじゃん?」
そう思って、さらに時間を浪費する。これではダメだ。

問題は、お前らが普段使っているdigが、実は裏で「再帰問い合わせ」という便利な機能を使っていることにある。この便利さが、時にトラブルの真因を隠してしまうんだ。

2. DNS解決の基本をおさらい:フルサービスリゾルバと権威サーバー

まずは、DNSの名前解決の基本的な流れを軽くおさらいしよう。これは非常に重要だ。

1. クライアント(お前らのPCやサーバー):example.comのIPアドレスを知りたい。
2. フルサービスリゾルバ(DNSキャッシュサーバー):クライアントからの問い合わせを受け取る。お前らのOSやルーターに設定されているDNSサーバーだ。ISPが提供するものや、Google Public DNS (8.8.8.8)、Cloudflare DNS (1.1.1.1) などがあるな。
3. ルートサーバー:フルサービスリゾルバが知らないドメインの場合、まずルートサーバーに「.comドメインのネームサーバーはどこだ?」と聞く。
4. TLD(トップレベルドメイン)サーバー:.comドメインのネームサーバーは、「example.comのネームサーバーはここだ」と教えてくれる。
5. 権威サーバー:example.comのネームサーバーが、最終的に「example.comのIPアドレスは192.0.2.1だ」と教えてくれる。

この一連の問い合わせを、フルサービスリゾルバがルートから権威サーバーまで順々にたどっていくのが「再帰問い合わせ(Recursive Query)」だ。そして、フルサービスリゾルバがクライアントに対して「はい、これ答えね」と返すのが、クライアントから見れば「再帰的な応答」になる。

RDフラグ(Recursion Desired)の役割

この再帰問い合わせを希望するかどうかを示すのが、DNSクエリヘッダーに含まれるRD(Recursion Desired)フラグだ。

  • RD=1(ON):再帰問い合わせを希望する。つまり、フルサービスリゾルバに「俺の代わりに答えを探してきてくれ」と依頼する。
  • RD=0(OFF):再帰問い合わせを希望しない。つまり、問い合わせ先のDNSサーバーに「お前が知ってる範囲で教えてくれ。知らなければ、次に聞くべきサーバーを教えてくれ」と依頼する。これを「非再帰問い合わせ(Iterative Query)」と呼ぶ。

普段お前らがdig example.comと打つと、デフォルトでこのRDフラグはONになる。だから、設定されたフルサービスリゾルバが頑張って答えを探してきてくれるわけだ。RFC1034やRFC1035を紐解けば、この仕様が詳細に記されているぞ。

3. dig +norecurseが明かすDNSキャッシュの真実

ここからが本題だ。なぜdig +norecurseが強力な武器になるのか?

それは、このオプションを付けることで、RDフラグをOFF(RD=0)にして問い合わせを行うからだ。

つまり、dig +norecurse example.comと打つと、お前らが指定したDNSサーバー(指定しなければ、resolv.confに書かれたデフォルトのDNSサーバー)に対して「再帰問い合わせなしで頼むぜ」と命令していることになる。

これにより、何がわかるのか?

1. フルサービスリゾルバのキャッシュ状態を正確に把握できる

  • もしフルサービスリゾルバがexample.comの情報をキャッシュしていれば、それを即座に返してくれる。
  • キャッシュしていなければ、フルサービスリゾルバは答えを返さず、次に問い合わせるべき権威サーバーの情報を返してくる。これが通常のdigとの決定的な違いだ。
  • これを見れば、「あ、うちのリゾルバ、あのドメインのキャッシュ持ってねぇな」とか、「なんでキャッシュ持ってんのに変な応答返すんだ?」といったことが一目瞭然になる。

2. 権威サーバーが正しい情報を返しているかを直接確認できる

  • フルサービスリゾルバを経由せず、直接権威サーバーに「お前んとこのドメインの情報、教えてくれ」と聞くことができる。
  • これにより、フルサービスリゾルバが誤ったキャッシュを持っているのか、それとも権威サーバー自体がおかしな設定になっているのかを切り分けられる。

これは、通常のdigが「最終的な答え」しか教えてくれないのに対し、dig +norecurseは「問い合わせの途中経過」や「キャッシュの有無」まで見せてくれる、まさにレントゲン写真のようなものだ。

4. 実践!dig +norecurseによるトラブルシューティング

さあ、具体的なシナリオで+norecurseの使い方を見ていこう。

シナリオ1: フルサービスリゾルバのキャッシュ確認と挙動調査

お前らのアプリケーションが使っているフルサービスリゾルバ(例えば、192.168.1.1)が、特定のドメインapi.example.comの名前解決に失敗しているとしよう。しかし、お前らのPCからdig api.example.comと打つと正常に解決する。

まずは通常のdigで確認してみよう。
この時、普段使っているフルサービスリゾルバ(resolv.confに設定されているDNSサーバー)ではなく、問題のアプリケーションが使っているリゾルバを指定することが重要だ。

# アプリケーションが使っているフルサービスリゾルバが 192.168.1.1 だと仮定
# 通常のdig(RDフラグON)
dig @192.168.1.1 api.example.com

出力例(正常な場合):

; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.1.1 api.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36761
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;api.example.com.		IN	A

;; ANSWER SECTION:
api.example.com.	300	IN	A	192.0.2.100

;; Query time: 1 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Mon Jan 01 12:34:56 JST 2023
;; MSG SIZE  rcvd: 49

flags: qr rd ra;に注目だ。rdはRecursion Desired(再帰要求)が設定されていたこと、raはRecursion Available(再帰利用可能)で、フルサービスリゾルバが再帰問い合わせに応じてくれたことを示す。

次に、+norecurseオプションを付けてみる。

# アプリケーションが使っているフルサービスリゾルバ 192.168.1.1 に対して、非再帰問い合わせ
dig @192.168.1.1 api.example.com +norecurse

出力例1(フルサービスリゾルバがapi.example.comをキャッシュしている場合):

; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.1.1 api.example.com +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45678
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 
                                  # ここに注目!raフラグがない。AAフラグがあるのはおかしいが、
                                  # フルサービスリゾルバが自身のキャッシュを権威情報として返す挙動の場合もある。
                                  # 通常はraフラグがないことでキャッシュ応答だと判断できる。

;; QUESTION SECTION:
;api.example.com.		IN	A

;; ANSWER SECTION:
api.example.com.	300	IN	A	192.0.2.100

;; Query time: 0 msec # 問い合わせ時間が非常に短い
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Mon Jan 01 12:34:57 JST 2023
;; MSG SIZE  rcvd: 49

flagsからrdとraが消えていることに注目だ。Query timeが0 msecに近いのも、キャッシュからの応答の証拠。これは「フルサービスリゾルバはキャッシュを持っていたので、即座にそれを返してくれた」ことを意味する。

出力例2(フルサービスリゾルバがapi.example.comをキャッシュしていない、または解決できない場合):

; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.1.1 api.example.com +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56789
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0 
                                # ここに注目!ANSWERが0。つまり答えを持っていない。

;; QUESTION SECTION:
;api.example.com.		IN	A

;; AUTHORITY SECTION:
example.com.		86400	IN	NS	ns1.example.com.
example.com.		86400	IN	NS	ns2.example.com.

;; Query time: 50 msec # 問い合わせ時間がある程度かかる
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Mon Jan 01 12:34:58 JST 2023
;; MSG SIZE  rcvd: 100

ANSWER SECTIONが空で、代わりにAUTHORITY SECTIONにexample.comのネームサーバー(ns1.example.com, ns2.example.com)が表示されているだろう?これは「俺(フルサービスリゾルバ)はapi.example.comの答えを知らないけど、example.comのネームサーバーはこいつらだから、次はお前がこいつらに聞いてくれ」というメッセージだ。

このパターンが出たら、フルサービスリゾルバ自体は問題なく動いているが、api.example.comのキャッシュを持っていないか、または何らかの理由で権威サーバーへの再帰問い合わせに失敗している可能性がある、と判断できる。

シナリオ2: 権威サーバーからの直接応答確認

シナリオ1で、フルサービスリゾルバがapi.example.comの情報をキャッシュしておらず、権威サーバーの情報を返してきたとしよう。今度は、その権威サーバーが本当に正しい情報を返しているかを確認する番だ。

先の出力例2で示されたネームサーバー、ns1.example.comとns2.example.comに対して直接問い合わせてみる。この時も必ず+norecurseを使う。なぜなら、権威サーバーは基本的に再帰問い合わせには応じない(RFCの推奨)。+norecurseを付けないと、権威サーバーが再帰問い合わせを拒否し、REFUSEDやSERVFAILといったエラーが返ってくる可能性が高い。

# ns1.example.com のIPアドレスを事前に調べるか、直接ドメインで指定
# まずは ns1.example.com のIPアドレスを調べる(これは通常のdigでOK)
dig ns1.example.com

# 例えば ns1.example.com のIPが 203.0.113.1 だと判明したとする
# 権威サーバー 203.0.113.1 に対して、非再帰問い合わせ
dig @203.0.113.1 api.example.com +norecurse

出力例(権威サーバーが正しい情報を返している場合):

; <<>> DiG 9.16.1-Ubuntu <<>> @203.0.113.1 api.example.com +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 67890
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 0 
                                  # ここに注目!aaフラグ(Authoritative Answer)がある

;; QUESTION SECTION:
;api.example.com.		IN	A

;; ANSWER SECTION:
api.example.com.	300	IN	A	192.0.2.100

;; AUTHORITY SECTION:
example.com.		86400	IN	NS	ns1.example.com.
example.com.		86400	IN	NS	ns2.example.com.

;; Query time: 5 msec
;; SERVER: 203.0.113.1#53(203.0.113.1)
;; WHEN: Mon Jan 01 12:34:59 JST 2023
;; MSG SIZE  rcvd: 110

flagsにaa(Authoritative Answer)フラグがあるのがポイントだ。これは「この応答は、このサーバーが権威を持っている情報だぜ」という意味だ。
これで、権威サーバーが正しく設定されていることが確認できる。もしここでエラーになったり、間違ったIPアドレスが返ってきたら、ゾーンファイルの設定ミスや、DNSSECの設定ミスなどを疑うべきだ。

シナリオ3: ネームサーバーの委譲(Delegation)確認

DNSの名前解決は、ルートからTLD、そして権威サーバーへと「委譲」されていく。この委譲チェーンのどこかで設定ミスがあると、名前解決は失敗する。+norecurseを使えば、このチェーンを一つずつたどって確認できる。

例えば、example.comの解決がうまくいかない場合。

1. ルートサーバーに問い合わせて、.comのTLDネームサーバーを探す

# ルートサーバーのIPアドレスは「dig . ns」で調べられる
    # 例えば、a.root-servers.net のIPが 198.41.0.4 だとする
    dig @198.41.0.4 com. NS +norecurse

これで.comのTLDネームサーバー群が返ってくる。

2. .comのTLDネームサーバーに問い合わせて、example.comの権威サーバーを探す

# 前のステップで得られた .com のTLDネームサーバーのIP(例: 192.0.2.200)を使う
    dig @192.0.2.200 example.com NS +norecurse

これでexample.comの権威サーバー(ns1.example.com, ns2.example.comなど)が返ってくる。同時に、ADDITIONAL SECTIONにはこれらのネームサーバーのIPアドレス(グルーレコード)も含まれているはずだ。

3. example.comの権威サーバーに問い合わせて、Aレコードを確認する

# 前のステップで得られた example.com の権威サーバーのIP(例: 203.0.113.1)を使う
    dig @203.0.113.1 example.com A +norecurse

これで最終的なexample.comのIPアドレスが返ってくる。

このプロセスを一つずつたどることで、どこで委譲が途切れているのか、どこで間違った情報が返されているのかをピンポイントで特定できる。これは、大規模なDNS環境や、他社が管理するDNSをデバッグする際に非常に役立つ。

5. RDフラグの制御とAPI設計への応用

お前らがWeb APIを設計したり、スクリプトでDNS問い合わせをしたりする場合、このRDフラグの挙動を理解しておくことは非常に重要だ。

一般的なHTTPクライアント(curl, Fetch API)の場合

curlやブラウザのFetch APIは、直接DNS問い合わせを行うわけではない。これらはOSの提供する名前解決ライブラリ(getaddrinfoなど)を利用する。そのため、これらのツールやAPIから直接RDフラグを制御する機能はない。

OSの名前解決ライブラリは、通常、resolv.confに設定されたフルサービスリゾルバに対して再帰問い合わせ(RD=1)を行う。つまり、これらからDNS問い合わせを行う場合、常にフルサービスリゾルバのキャッシュや挙動に依存することになる。

例えば、curlで特定のドメインにアクセスする場合:

# OSのDNSリゾルバ経由で example.com にアクセス
curl https://example.com/api/v1/data

もし、特定のDNSサーバーを使って名前解決したい場合は、curlの--resolveオプションを使うか、OSのresolv.confを一時的に変更するなどの方法が考えられるが、これはあくまでcurlが使用する名前解決の対象を変更するだけであり、RDフラグを制御するものではない。

# ホスト名を特定のIPに解決させる(DNSは使わない)
curl --resolve example.com:443:192.0.2.1 https://example.com/api/v1/data

プログラミング言語でのDNS問い合わせ(Python dnspythonの例)

プログラミング言語でより低レベルなDNS問い合わせを行う場合、RDフラグを直接制御できるライブラリが多数存在する。Pythonのdnspythonは、その代表例だ。

dnspythonを使えば、特定のDNSサーバーに対して、再帰問い合わせを希望するかどうかを明示的に指定できる。これは、APIサーバーが特定のDNSリゾルバに依存している環境で、そのリゾルバのキャッシュ状態をプログラム的に確認したい場合などに非常に有効だ。

import dns.resolver
import dns.query
import dns.message

# 問い合わせるドメインとレコードタイプ
domain = "api.example.com"
record_type = "A"

# 問い合わせ先のフルサービスリゾルバのIPアドレス
# 例えば、Google Public DNS (8.8.8.8) やアプリケーションが使うリゾルバ
nameserver_ip = "8.8.8.8" 

print(f"--- {nameserver_ip} に対して再帰問い合わせ (RD=1) ---")
# 再帰問い合わせを行う (デフォルトの動作)
# dns.resolver はデフォルトで再帰問い合わせを行う
try:
    answers = dns.resolver.resolve(domain, record_type)
    for rdata in answers:
        print(f"  Resolved IP (RD=1): {rdata.address}")
except dns.resolver.NoAnswer:
    print(f"  No A record found for {domain} with RD=1")
except dns.resolver.NXDOMAIN:
    print(f"  Domain {domain} does not exist with RD=1")
except Exception as e:
    print(f"  Error with RD=1: {e}")


print(f"\n--- {nameserver_ip} に対して非再帰問い合わせ (RD=0) ---")
# 非再帰問い合わせを行う (+norecurse に相当)
# dns.message を使ってクエリを構築し、rd フラグを False に設定
try:
    # クエリメッセージを構築
    query = dns.message.make_query(domain, record_type)
    query.flags &= ~dns.flags.RD # RDフラグをOFFにする

    # 指定したネームサーバーにUDPでクエリを送信
    response = dns.query.udp(query, nameserver_ip, timeout=5)

    # 応答の解析
    # ANSWER SECTIONがあれば表示
    if response.answer:
        for rrset in response.answer:
            for rdata in rrset:
                if rdata.rdtype == dns.rdata.A: # Aレコードの場合
                    print(f"  Resolved IP (RD=0): {rdata.address}")
    # ANSWER SECTIONがなく、AUTHORITY SECTIONがある場合(キャッシュなしで委譲情報を返したケース)
    elif response.authority:
        print(f"  No direct answer, but got authority section:")
        for rrset in response.authority:
            print(f"    {rrset}")
    else:
        print(f"  No answer or authority section found for {domain} with RD=0")

    # フラグの確認 (rdフラグがオフになっていること)
    print(f"  Response flags: {dns.flags.to_text(response.flags)}")
    
except dns.exception.Timeout:
    print(f"  Query to {nameserver_ip} timed out with RD=0")
except Exception as e:
    print(f"  Error with RD=0: {e}")

このコードを見れば、dnspythonを使ってRDフラグを制御し、フルサービスリゾルバの生の状態をプログラマブルに確認できることがわかるだろう。

6. まとめ:dig +norecurseはDNSトラブルシューティングの切り札

お前ら、どうだ?dig +norecurseのパワフルさが伝わったか?

  • 通常のdig(RD=1): フルサービスリゾルバに「答え全部探してきてくれ」と依頼する。最終的な結果はわかるが、途中のキャッシュ状態などは隠蔽される。
  • dig +norecurse(RD=0): フルサービスリゾルバや権威サーバーに「お前が持ってる情報だけ教えてくれ。知らなきゃ次を聞くべき奴を教えてくれ」と依頼する。これにより、キャッシュの有無、権威サーバーの応答、委譲チェーンの問題などを直接確認できる。

「名前解決できない」という一言で片付けず、この+norecurseを使いこなせば、DNSの深い闇に隠された真犯人を効率的に追い詰めることができる。

DNSは、ネットワークの縁の下の力持ちだ。普段は意識されないが、一度トラブルが起きると、何もかもが止まってしまう。そんな時、今回紹介したdig +norecurseのような低レベルなデバッグ手法が、お前らを窮地から救ってくれるだろう。

常に「なぜ?」という疑問を持ち、パケットレベルの挙動を想像しながらトラブルシューティングに取り組むんだ。それが、本物のNOCエンジニアへの道だぜ。

じゃあな、また次の障害現場で会おう!
—

コメント

タイトルとURLをコピーしました