【実務・中級編】 DNS応答メッセージのヘッダーフィールド(Flags, RCODE)の解析 – トラブルシューティング&ネットワーク運用監視実践ガイド

深夜3時、ピッチリと冷房が効いたNOCルーム。突然、監視モニターの一角が真っ赤に染まった。
「おい、APIの死活監視が全滅だ。上流のクラウドサービスか?」
若手エンジニアが青い顔をして振り向く。しかし、私は慌てず騒がせず、手元マグカップの冷めたブラックコーヒーを一口啜ってこう言った。
「落ち着け。まずはトランスポート層を見る前に、すべての通信の羅針盤である『あいつ』を疑うんだ。――そう、DNSだ」

Web APIの設計やインフラ運用において、エンドポイントの名前解決トラブルは日常茶飯事だ。だが、多くのエンジニアは「繋がらない」と聞くと、すぐに ping を打ったり、HTTPのステータスコードだけに気を取られたりする。
本当に見るべきなのは、DNSサーバーが返してくる「言葉」の裏側、すなわちDNS応答メッセージのヘッダーフィールド(FlagsとRCODE)だ。今回は、百戦錬磨の現場で私たちがどのようにDNSのパケットを読み解き、障害の根源を特定しているのか、その実戦的なノウハウを伝授しよう。

—

1. RFCが定めた「DNS応答」の裏側とパケットの現実

DNSは、私たちが普段意識することなく使っているが、裏側ではUDP(またはTCP)のポート53番を使い、極めて厳格なプロトコル仕様(RFC 1035など)に基づいてやり取りされている。

DNSのクエリ(要求)に対し、フルリゾルバや権威DNSサーバーが返すレスポンスメッセージは、大別して以下のセクションで構成されている。

1. Header(ヘッダー): トランザクションID、各種フラグ(Flags)、セクションごとのレコード数
2. Question(質問): 問い合わせたドメイン名、クエリタイプ、クラス
3. Answer(応答): 問い合わせに対する実際のレコード(AレコードやCNAMEなど)
4. Authority(権威): 権威を持つネームサーバーの情報
5. Additional(追加): 関連する追加情報(EDNS0のバッファサイズやGlueレコードなど)

この中で、トラブルシューティングの成否を握るのが、Headerセクションに刻まれた「Flags」と「RCODE(Response Code)」だ。ここには、DNSサーバーがそのリクエストをどう解釈し、なぜその結果になったのかという「言い訳」のすべてが詰まっている。

—

2. dig コマンドでヘッダーの深層を覗き見る

実務において、名前解決のデバッグツールとして nslookup を使っているなら、今すぐ卒業して dig(Domain Information Groper)を相棒にしよう。情報の解像度が圧倒的に違う。

例えば、存在しないドメインをあえて dig で叩いてみよう。

$ dig +noall +answer +comments @8.8.8.8 non-existent-api.example.com A

このコマンドを実行した際、出力されるヘッダー部分に注目してほしい。

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 45123
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

この数行の中に、障害の切り分けに必要な情報の9割が詰まっている。ここから読み解くべき重要パラメーターを解説しよう。

ステータスコード(RCODE)の読み方

ヘッダーの status: [RCODE] 部分は、DNSサーバーからの通信結果の「通知表」だ。代表的なものを頭に叩き込んでおいてほしい。

  • NOERROR: 正常終了。おめでとう、データはそこにある。ただし、Answerセクションが空(ANSWER: 0)の場合は、ドメインは存在するが該当するレコードタイプ(AやAAAAなど)が存在しない「NODATA」状態を意味する。
  • NXDOMAIN(Non-Existent Domain): 「そんなドメインは宇宙のどこにも存在しない」という権威サーバーからの冷徹な返答。タイポ、設定漏れ、あるいはドメインの失効を疑え。
  • SERVFAIL(Server Failure): 権威サーバー側でのダウン、ゾーンファイルの構文エラー、あるいはDNSSECの検証失敗など、サーバー側で何らかの致命的な障害が発生している状態。自社管理のゾーンであれば即座に権威DNSのログを確認すべきだ。
  • REFUSED: ポリシーやアクセス制限(ACL)によって、DNSサーバーがリクエストを拒絶した状態。転送設定(Forwarding)の不備や、ゾーン転送の権限エラーでよく遭遇する。

フラグ(Flags)の深層

flags: qr rd ra の部分も極めて重要だ。現場でよく見るフラグの意味を整理する。

  • qr (Query Response): これが立っていれば「応答パケット」であることを示す。
  • aa (Authoritative Answer): この応答を返したサーバーが、そのゾーンの「権威(マスター/スレーブ)」自身であることを示す。キャッシュサーバーからの応答であればこのフラグは落ちている。
  • tc (Truncated): 応答メッセージがUDPの制限サイズ(一般的に512バイト、EDNS0有効時はそれ以上)を超えたため、途中で切り捨てられたことを示す。これが立っている場合、通常は自動的にTCPでの再接続にフォールバックするが、ファイアウォールがTCP 53番をブロックしていると名前解決がランダムに失敗する「嫌な障害」を引き起こす。
  • rd (Recursion Desired): クライアントが「再帰的問い合わせ(代わりに調べてきてくれ)」を要求しているフラグ。
  • ra (Recursion Available): 問い合わせ先のDNSサーバーが「再帰的問い合わせに対応しているよ」と返しているフラグ。パブリックDNS(8.8.8.8 や 1.1.1.1)には必ず立っているが、社内ニッチなフォワーダーサーバーでこれが立っていない場合、外部への名前解決が一切できなくなる。

—

3. 実務で遭遇する「迷宮入り」トラブルと解決シナリオ

ここで、過去に私がNOCで対応した実際のインシデントをベースにした、典型的なトラブルシューティングのシナリオを紹介しよう。

シナリオ:APIクライアントからの「突然のSERVFAIL」

あるマイクロサービス基盤で、特定の外部決済APIへの接続が断続的に失敗するというアラートが上がった。アプリケーションログには getaddrinfo ESERVFAIL が並んでいる。

1. 初動:パケットとRCODEの確認

まずはローカルのフルリゾルバ(BINDやCoreDNSなど)をバイパスして、直接外部の権威サーバー、あるいはパケットキャプチャ(tcpdump)で状態を確認する。

# パブリックDNSを直接指定して、どのRCODEが返っているか確認する
$ dig @1.1.1.1 api.payment-gateway.example.com A +noall +comments

出力結果のステータスが SERVFAIL になっていることを確認。さらに、デバッグ用の詳細出力を得る。

$ dig @1.1.1.1 api.payment-gateway.example.com +trace

2. 原因究明:DNSSECとEDNS0の闇

+trace コマンドでルートサーバーから順に追っていくと、決済APIの親ドメイン(example.com)の権威DNSサーバーの応答において、特定のゾーンで署名検証エラー(DNSSEC Validation Failure)が起きていることが判明した。
相手方のインフラチームが、キーロールオーバー(暗号鍵の更新)の際に古いDSレコードを親ゾーンに登録し忘れた、あるいはキャッシュサーバー側とのEDNS0バッファサイズ(UDPパケットサイズ)の不整合によるパケットドロップが原因だった。

3. 一時回避策と恒久対策

  • 一時回避: アプリケーション側のコンテナが参照している /etc/resolv.conf のネームサーバーを、厳格なDNSSEC検証を行うパブリックDNSから、検証を一時的に緩和・スキップする社内フォワーダーへ切り替える。
  • 恒久対策: 相手方(API提供元)のインフラ担当者に SERVFAIL が発生している旨と、dig +trace の出力を添えてチケットを起票。DNSSECの署名整合性を修正してもらった。

—

4. アプリケーションコード(Python / Node.js)でのDNSエラーハンドリング

インフラエンジニアだけでなく、Web APIを設計・実装するアプリケーションエンジニアも、コードレベルでDNSの挙動を意識する必要がある。単に「接続エラー」として一括りにキャッチするのではなく、DNS特有のエラーをハンドリングできるようにしよう。

以下は、Pythonの dnspython ライブラリを使用して、明示的にRCODEやフラグを検証し、障害時に適切なログとメトリクスを出力する実装例だ。

import dns.exception
import dns.message
import dns.query
import dns.rdatatype


def check_api_dns_health(domain_name: str, nameserver_ip: str) -> bool:
    """指定されたドメインのDNSレコードをチェックし、

    RCODEやフラグを検証してヘルスステータスを返す。
    """
    try:
        # クエリメッセージの作成(Aレコードを要求)
        q = dns.message.make_query(domain_name, dns.rdatatype.A)

        # タイムアウトを2秒に設定し、UDPで問い合わせを実行
        # 実務ではTCPフォールバックの考慮も必要
        response = dns.query.udp(q, nameserver_ip, timeout=2.0)

        # ヘッダーのRCODE(応答コード)をチェック
        rcode = response.rcode()
        rcode_str = dns.rcode.to_text(rcode)

        print(f"[INFO] Query for {domain_name} to {nameserver_ip} -> RCODE: {rcode_str}")

        if rcode == dns.rcode.NOERROR:
            # 正常終了だが、Answerセクションが空(NODATA)の可能性をチェック
            if len(response.answer) == 0:
                print(
                    f"[WARN] NODATA: Domain exists, but no A record for {domain_name}"
                )
                return False
            # 正常にレコードが存在する
            return True

        elif rcode == dns.rcode.NXDOMAIN:
            print(f"[ERROR] NXDOMAIN: Domain {domain_name} does not exist.")
            # TODO: アラート発報やメトリクス加算の処理をここに記述
            return False

        elif rcode == dns.rcode.SERVFAIL:
            print(
                f"[ERROR] SERVFAIL: Nameserver internal failure or DNSSEC error."
            )
            # TODO: アップストリームの障害としてハンドリング
            return False

        else:
            print(f"[ERROR] Unexpected RCODE received: {rcode_str}")
            return False

    dns.exception.Timeout:
        print(
            f"[FATAL] DNS query timed out against nameserver {nameserver_ip}."
        )
        # ネットワーク層(ファイアウォール、ルーティング)の障害を疑う
        return False

    except Exception as e:
        print(f"[CRITICAL] An unexpected error occurred: {e}")
        return False


# 実行例のシミュレーション
if __name__ == "__main__":
    target_domain = "api.example.com"
    resolver = "8.8.8.8"

    is_healthy = check_api_dns_health(target_domain, resolver)
    if not is_healthy:
        print("-> DNS Health Check FAILED. Investigate headers and network paths.")

このように、コード側でも単なる例外キャッチではなく、SERVFAIL なのか NXDOMAIN なのかを識別できるようにしておくことで、障害時の自動復旧ロジック(リトライすべきか、即座にフェイルオーバーすべきかの判断)が劇的に洗練される。

—

5. シニアエンジニアからの実務Tips:詰まったときのデバッグチェックリスト

最後に、現場であなたがDNSトラブルの荒海に放り出されたときのために、私の頭の中にある「トラブルシューティング・チェックリスト」を授けよう。

1. まずはキャッシュを疑え
ローカルOSのキャッシュ(nscdやsystemd-resolved)、あるいはアプリケーションランタイム(JavaのJVM DNSキャッシュなど)が古い NXDOMAIN や古いIPアドレスを掴んでいるケースがあまりに多い。dig を直叩きして「生」の応答を確認するのが鉄則だ。
2. -x や +trace を使いこなせ
フルリゾルバの挙動がおかしい時は dig +trace でルートから順にパケットの足取りを追う。誰も教えてくれない「どこで権威が途切れているか」を教えてくれる。
3. EDNS0とUDP/TCP 53番のファイアウォールを確認せよ
応答パケットの tc フラグが見えたら、ネットワークのレイヤー3/4(セキュリティグループやFW)でTCP 53番がブロックされていないか即座に確認しろ。これが隠れたパケットロスやレイテンシー増大の元凶だ。
4. 構造化ログにRCODEを残せ
APIのゲートウェイやリバースプロキシ(NginxやEnvoyなど)のアクセスログ・エラーログには、可能な限りDNS解決時のエラーコード(あるいはそれに準ずる詳細)を出力させよ。夜中のインシデント対応で「なぜ繋がらなかったのか」の証拠がそこにある。

DNSは、目に見えない。だからこそ、パケットが語るヘッダーの微細なサイン(FlagsとRCODE)を読み解く能力が、インフラエンジニア、そしてWeb APIを支えるすべてのエンジニアの「腕の見所」となる。
さあ、コンソールを開き、コマンドの海へ飛び出そう。パケットは決して嘘をつかない。

コメント

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