【実務・中級編】 pingによるMTUパスディスカバリー(Path MTU Discovery)とDFビットの活用 – トラブルシューティング&ネットワーク運用監視実践ガイド

夜中の3時、PagerDutyの甲高いアラート音で飛び起きた経験はないだろうか。「一部のクライアントから、巨大なJSONペイロードを含むWeb APIへのリクエストがタイムアウトする」という連絡。ダッシュボードを見れば、CPU使用率は正常、APIサーバーも生きている。ロードバランサーのログを覗くと、特定の拠点からのリクエストだけが無言の死(ブラックホール)を迎えている。

こういう現場で、若手エンジニアが真っ先に疑うのは「アプリケーションのバグ」や「ファイアウォールの過剰なブロック」だ。しかし、百戦錬磨のNOCエンジニアである私たちが真っ先に疑うべきは、もっと物理層に近い、そしてインターネットのあちこちに潜む「MTUの不一致」である。

今回は、ネットワークトラブルシューティングの最終兵器の一つでありながら、意外と使いこなせていないエンジニアが多い 「pingによるPath MTU Discovery(PMTUD)とDFビットの活用法」 について、現場のリアルな知見を交えて徹底解説しよう。

—

1. パケットはなぜ途中で消えるのか? 現場が直面する「ブラックホール」の正体

インターネットは一枚岩ではない。社内LANのギガビットイーサネット(MTU 1500)から始まり、途中のプロバイダ網ではPPPoE(MTU 1492)やトンネリング技術(GRE, VXLAN, IPsec等)を経由し、最終的な宛先に届く。

ここで問題になるのが 「MTU(Maximum Transmission Unit)」 のサイズ違いだ。

ルーターの処理能力を超えるサイズのパケットがやってきた時、本来であればルーターはパケットを分割(フラグメンテーション)して転送する。しかし、現代のインフラ設計において、フラグメンテーションは極力避けたい「悪者」だ。なぜなら、ルーターにとってフラグメンテーションの処理はCPU負荷が高く、さらにパケットの一部が欠損しただけで全体を再送しなければならないため、スループットが劇的に低下するからだ。

そこで登場するのが、IPヘッダーに含まれる 「DF(Don’t Fragment)ビット」 である。「このパケットは絶対に分割するな」という強い意志表示だ。

PMTUDの基本と「ICMPの沈黙」

送信元から宛先までの経路において、許容される最小のMTU(Path MTU)を動的に探る仕組みが Path MTU Discovery(PMTUD) だ。

1. 送信元は DF=1 を立ててパケットを送信する。
2. 途中のルーターで「MTUよりデカい、しかも DF=1 だから分割できない!」という事態に直面する。
3. ルーターはパケットを破棄し、送信元へ 「ICMP Destination Unreachable (Fragmentation Needed)」 というお叱りのメッセージ(タイプ3、コード4)を送り返す。
4. 送信元はそのメッセージを受け取り、送信パケットのサイズを小さく再調整する。

……これが教科書通りの美しいストーリーだ。しかし、現実のインターネットはそう甘くない。セキュリティ上の理由(DDoS対策やスキャン防止)から、企業のファイアウォールやセキュリティアプライアンスが すべてのICMPメッセージを容赦なくドロップ しているケースが多々ある。

ICMPが届かないと、送信元は「自分のパケットが途中で捨てられたこと」すら気づけない。結果として、ローディングが無限に続く「PMTUDブラックホール問題」が完成する。この悪夢を現場で迅速に暴くために、私たちには ping を駆使した手動のPath MTU探索スキルが絶対に不可欠なのだ。

—

2. 実践! ping コマンドでDFビットを操りPath MTUを暴く

OSによって ping のオプションや挙動が微妙に異なるため、実務でよく使う環境(macOS / Linux / Windows)ごとのコマンド構文を整理しておこう。

macOS / Linux 環境での検証

Linux等では、-M オプションでPMTUDの挙動(DFビットの制御)を指定できる。

# パケットサイズを1472バイト(IPヘッダー20バイト + ICMPヘッダー8バイト = 合計1500バイト)に指定し、
# DFビットを立ててフラグメンテーションを禁止する
ping -M do -s 1472 192.168.100.1

ここでピンと来たかもしれない。なぜ 1500 ではなく 1472 なのか?

  • IPヘッダー: 20バイト
  • ICMPヘッダー: 8バイト
  • ペイロード(データ部分): 1472バイト
  • 合計: 1500バイト(標準的なイーサネットMTU)

もし、経路上に MTU 1492 の区間(PPPoE等)が存在する場合、上記コマンドを実行すると以下のような悲しいレスポンスが返ってくる。

PING 192.168.100.1 (192.168.100.1) 1472(1500) bytes of data.
ping: local error: message too long, mtu=1492

「おっと、この先のネットワークは1492までしか受け付けないぞ」とOSが教えてくれる。では、ペイロードを調整してみよう。

# ペイロードを 1464 バイト(合計 1492 バイト)に縮小して再挑戦
ping -M do -s 1464 192.168.100.1

これで無事に応答が返ってくれば、そのパスの最大MTUは 1492 であると特定できる。

Windows 環境での検証

Windowsの ping コマンドは仕様が少し異なり、-f オプションでDFビットを立てる。また、サイズ指定はパケット全体ではなくペイロードサイズ(-l)を指定する。

:: ペイロードを 1472 バイト、DFビット有効(-f)で送信
ping -f -l 1472 192.168.100.1

経路のMTUを超えている場合、Windowsでは次のようなメッセージが表示される。

パケットは断片化する必要がありますが、DF が設定されています。

この日本語メッセージを見たら、サイズを徐々に小さく(1400、1300、1200……)落としていき、応答が返る限界のサイズを探るのが現場の泥臭いデバッグ手法だ。

—

3. Web API設計・インフラ運用への実務的なフィードバック

ネットワーク層のMTU問題は、しばしば上位レイヤー(HTTP / TLS)の障害として表面化する。特に、最近のモダンなWeb APIでは、次のような設計上の配慮が求められる。

① MSS Clamp (Maximum Segment Size Clamping) の活用

ルーターやロードバランサー(Nginx, AWS ALB, Cloudflare等)の境界で、TCPの SYN パケットに含まれるMSSを強制的に書き換える設定(MSS Clamping)が有効だ。これにより、TCPセッション確立の段階で、経路のMTUに応じた適切なセグメントサイズに強制調整され、PMTUDブラックホールの罠を回避できる。

② APIクライアント(Fetch / curl / Python)からの検証

「特定のクライアントから巨大なJSONをPOSTするとタイムアウトする」という問い合わせを受けた際、インフラエンジニアとしてAPIサーバー側や踏み台サーバーから、該当クライアントのネットワーク環境を模した検証を行う。

以下は、Pythonを用いてネットワークインターフェースやソケットレベルでDFビット(IP_MTU_DISCOVER)を制御するスニペットの例だ。実務での自動診断ツール作成の参考にしてほしい。

import socket
import struct
import sys

def check_path_mtu(target_host, payload_size):
    """
    指定したターゲットに対し、特定のペイロードサイズとDFビットを有効にして
    ソケット通信が可能か(MTUが許容範囲内か)をテストする関数
    """
    # IPv4, RAWソケットを作成 (ICMPプロトコルを指定)
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
    except PermissionError:
        print("[-] エラー: RAWソケットの作成にはroot権限(管理者権限)が必要です。")
        sys.exit(1)

    # Linux環境におけるDFビット(IP_MTU_DISCOVER_DO: フラグメンテーション禁止)の設定
    # IPPROTO_IP レベルの IP_MTU_DISCOVER (値は通常 10) を設定する
    try:
        IP_MTU_DISCOVER = 10
        IP_PMTUD_DO = 2  # 常にDFビットを立てる
        sock.setsockopt(socket.IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUD_DO)
    except AttributeError:
        print("[-] 警告: お使いのOS環境ではソケットレベルでのDF制御がサポートされていない可能性があります。")

    # 簡易的なICMP Echo Requestパケットの組み立て(タイプ8、コード0)
    # ヘッダーのチェックサムなどは省略した簡易実装です
    icmp_header = struct.pack('bbHHh', 8, 0, 0, 1, 1)
    payload = b'A' * payload_size
    packet = icmp_header + payload

    sock.settimeout(3.0)
    
    try:
        print(f"[*] 宛先 {target_host} へ ペイロードサイズ {payload_size} バイト (合計約 {payload_size + 28} バイト) で送信中...")
        sock.sendto(packet, (target_host, 0))
        
        # 応答の受信待ち
        data, addr = sock.recvfrom(1024)
        print(f"[+] 成功: {target_host} から応答を受信しました。このMTUサイズは許容されています。")
        return True

    except socket.timeout:
        print("[-] タイムアウト: 応答がありません。ファイアウォールでICMPがブロックされているか、パケットが破棄されています。")
        return False
    except OSError as e:
        # Linux等でメッセージサイズが大きすぎて拒否された場合(Errno 90: Message too long)
        if e.errno == 90:
            print(f"[-] 失敗: パケットが大きすぎます(Message too long)。経路のMTU制限を超えています。")
        else:
            print(f"[-] 予期せぬOSエラーが発生しました: {e}")
        return False
    finally:
        sock.close()

if __name__ == "__main__":
    # 使用例: ローカルゲートウェイに対してMTU 1500相当(ペイロード1472)をテスト
    target = "192.168.1.1"
    check_path_mtu(target, 1472)

③ クラウド環境(AWS / GCP / Azure)における注意点

パブリッククラウド上のVPC設計においても、MTUの考慮は極めて重要だ。
例えば、AWSの標準的なEC2インスタンスのVPC内MTUは 9001(ジャンボフレーム)に設定されていることが多い。しかし、それをインターネット側のクライアント(標準MTU 1500)に向けてそのまま巨大なパケットで返そうとすると、AWSの仮想ゲートウェイやインターネット境界でフラグメンテーションやPMTUDのやり取りが発生する。

もしクラウド上のロードバランサー(ALB)やNATゲートウェイの背後でAPIが頻繁にストールするなら、インスタンス側の仮想インターフェース(OS側)でMTUを適切に小さく制限するか、前述のMSS Clampingが正しく機能しているかをインフラのルーティングテーブルやセキュリティグループと合わせて見直す必要がある。

—

4. シニアエンジニアからのメッセージ

ネットワークのトラブルシューティングにおいて、マジックワードや一発で全てを解決してくれる魔法のコマンドなど存在しない。あるのは、「レイヤー1からレイヤー7まで、パケットが今どこをどう流れていて、どこで息絶えているのかを論理的に追跡する泥臭い執念」 だけだ。

「なぜこのサイズだと通り、このサイズを超えると沈黙するのか?」
その疑問に直面したとき、今日紹介した ping によるDFビットの制御とPath MTU Discoveryの知識は、必ず君の強力な武器になるはずだ。

障害対応の夜、静まり返ったオフィス(あるいは自宅のデスク)でモニターの光を見つめながら、パケットの挙動を脳内に描き出す――これだから、インフラエンジニアはやめられない。さあ、次のトラブルシューティングへ向かおう。

コメント

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