【実務・中級編】 MTUとMSSの相互関係とパケット断片化 – ネットワーク基礎とWebセキュリティ実践ガイド

【ネットワークの深層】MTUとMSSの泥沼:Web API開発者が知るべきパケット断片化の罠とPMTUD崩壊の処方箋

こんにちは、シニアネットワークエンジニアの私です。

夜な夜なWeb APIのパフォーマンスチューニングやインフラのクラウド移行に明け暮れる皆さん、こんな怪奇現象に遭遇したことはありませんか?
「ローカルの検証環境や単体テストでは何の問題もなく爆速で返ってくるJSONレスポンスが、AWSとオンプレミスを繋いだ本番のVPNトンネル(IPsec/GRE)を経由した途端、なぜか特定の大きなペイロードだけタイムアウトする」
「APIゲートウェイ経由でPOSTリクエストを送ると、クライアントがハングアップしたように固まり、サーバー側のログにはリクエストすら届いていない」

アプリケーションのコードをいくらデバッガーで追っても、エラーハンドリングを見直しても原因は一向に見つからない。なぜなら、そのバグはコードの中ではなく、はるか下層のネットワークの暗き泥沼、OSI参照モデルのレイヤー3(ネットワーク層)とレイヤー4(トランスポート層)の境界線に潜んでいるからです。

今回は、ネットワークの基礎でありながら、実務の現場で幾多のエンジニアを絶望の淵に追い込んできた「MTU(Maximum Transmission Unit)」と「MSS(Maximum Segment Size)」の相互関係、そして現代のインターネットを揺るがす「Path MTU Discovery(PMTUD)の失敗」について、実例を交えて徹底的に解説します。

—

1. そもそもMTUとMSSとは何か? 階層モデルの視点から解き明かす

まずは、言葉の定義と、パケットがOSI参照モデルを駆け下りる(あるいは駆け上る)ときのリアルな挙動をおさらいしておきましょう。

MTU(最大転送単位):レイヤー2/3の物理的な器

MTUとは、データリンク層(レイヤー2)が1回に送信できるパケットの最大バイト数です。一般的に、イーサネットの標準MTUは 1500バイト に設定されています。
もし、トランスポート層(レイヤー4)から送られてきたデータがこの 1500バイト のイーサネットフレームのペイロード(IPヘッダーやTCPヘッダーを含む)を超過する場合、ルーターや送信元ホストはパケットを分割(断片化:Fragmentation)しなければなりません。

MSS(最大セグメントサイズ):レイヤー4の協調動作

一方、MSSはTCP(トランスポート層)における1つのセグメントに格納できるデータ(ペイロード)の最大サイズを指します。
TCPの3ウェイハンドシェイク(SYNパケットの交換)の際、お互いのクライアントとサーバーは Maximum Segment Size オプションを交換し、「私はこれ以上のサイズのTCPセグメントは一度に受け取れません」と宣言し合います。

一般的なイーサネット(MTU 1500バイト)の場合、計算式は以下のようになります。

MSS = MTU - (IPヘッダーサイズ) - (TCPヘッダーサイズ)
MSS = 1500 - 20(IPv4ヘッダー) - 20(TCPヘッダー) = 1460バイト

つまり、TCPがやり取りするデータの単位は通常 1460バイト が上限となります。これがレイヤー3のIPヘッダー(20バイト)と包まれ、レイヤー2のイーサネットフレーム(1500バイト)にすっぽり収まることで、パケットは分断されることなくネットワークの荒海を渡っていくのです。

—

2. パケットの断片化(Fragmentation)と、それが嫌われる理由

もし、送信しようとするデータがMTUを超えている場合、何が起きるでしょうか。

1. 送信元のOSや途中のルーターが、IPパケットを物理的なMTUサイズに合わせて強引に分割(断片化)します。
2. 分割された各パケットには、元のパケットのどこに位置するかを示すオフセット情報が付与されて宛先に飛びます。
3. 受信側のOSは、バラバラに届いた断片パケット(Fragments)をメモリ上で再組立(Reassembly)し、元のパケットに戻してから上位層へ渡します。

「お、ちゃんと再組立してくれるなら便利じゃないか」と思ったそこのあなた、甘い。実務の現場において、IPパケットの断片化は「百害あって一利なしの禁忌」とされています。理由は主に3つあります。

  • CPU負荷の増大: ルーターや受信ホストが断片の管理と再組立のために余計なメモリとCPUサイクルを消費します。
  • セキュリティ上の脆弱性: 不正に細工された断片パケットを送りつけることで、ファイアウォールやIDS/IPSの検査をすり抜ける攻撃(Teardrop攻撃など)の温床になります。
  • パケットロス時の致命傷: 1つの断片が途中でドロップ(消失)すると、TCPは「パケットが失われた」と判断して再送を行います。このとき、断片の1つでも欠けていると、受信側はパケット全体を破棄するため、実質的にすべての断片を再送する羽になり、スループットが劇的に低下します。

こうした背景から、現代のモダンなネットワークでは、IPパケットの断片化を極力回避するための仕組みが備わっています。それが Path MTU Discovery(PMTUD) です。

—

3. Path MTU Discovery(PMTUD)の仕組みと「ブラックホール問題」

PMTUDは、通信経路(Path)全体の最小MTU(Path MTU)を動的に検出し、送信元がそのサイズに合わせたMSSでTCP通信を行うための仕組みです。

正常なPMTUDのフロー

1. 送信元ホストは、IPヘッダーの DFフラッグ(Don’t Fragment: 断片化禁止) を 1 に立ててパケットを送出します。
2. 途中のルーターのMTUが、パケットのサイズよりも小さい場合、そのルーターはパケットを破棄します。
3. 同時に、送信元に対して ICMP Type 3 Code 4(Fragmentation Needed and DF was Set) というエラーメッセージを返します。「お前の送ってきたパケット、うちの回線にはデカすぎてDFが立ってるから捨てたぞ。次のMTUは〇〇にしろよ」と親切に教えてくれるわけです。
4. エラーを受け取った送信元は、自身のパケットサイズ(およびMSS)をその値に縮小し、再び通信を行います。

悪夢の「ICMPブラックホール問題」

しかし、ここで実務上最大のトラップが牙を剥きます。セキュリティポリシーの厳しい企業ネットワークやクラウド環境のセキュリティグループ、ファイアウォールでは、ICMPパケットをすべて遮断(Drop)していることが多いのです。

ICMPが途中でドロップされると、送信元ホストは「途中のルーターでパケットが弾かれたこと」を知る術を失います。
結果として何が起きるか?

  • クライアントは大きなパケット(DF=1)を送り続ける。
  • 途中のルーターは「デカすぎて通せない、でもICMPで返信しても捨てられる(あるいはそもそもセキュリティでブロックされている)」ため、ただ黙ってパケットを闇に葬る(ブラックホール化)。
  • 送信元は返事がないためタイムアウトまで待ち続け、APIリクエストは永遠に完了しない。

これが、「特定の環境からだけWeb APIがフリーズする」という悪名高いパケットドロップ問題の正体です。VPN(IPsecトンネル等)を経由するとオーバーヘッド(暗号化ヘッダー等)の分だけ実効MTUが 1500 から 1420 や 1300 に縮小するため、この問題が非常に頻発します。

—

4. 実務での処方箋:どうやってこの障害を防ぎ、解決するか?

この地獄のようなパケットロス問題に直面したとき、インフラエンジニアやWeb API開発者が取るべき具体的な対策は主に2つあります。

対策A:TCP MSS Clamping(ルーター・ファイアウォールでの強制書き換え)

もっとも確実で、現場のインフラエンジニアが真っ先に設定するのが MSS Clamping です。
通信経路上にあるルーターやファイアウォール(Cisco, Yamaha, Juniper, あるいはLinuxベースのルーターなど)で、通過するTCPのSYNパケットを監視し、その中のMSS値を強制的に小さく書き換える手法です。

例えば、VPNトンネルの都合でPath MTUが 1400 の場合、ルーター側で以下のように設定します。

# Cisco IOSでのMSS Clamping設定例(インターフェースコンフィグ)
interface Tunnel0
 ip tcp adjust-mss 1360

*(※ 1360 は、MTU 1400 からIP/TCPヘッダーの 40バイト を引いた値です)*

これにより、アプリケーション側がどんなに大きなデータを送ろうとしても、TCP層の段階で小さなセグメントに分割されるため、そもそもMTUオーバーが発生せず、ICMPに頼る必要がなくなります。

対策B:OS・コンテナ・クラウド環境でのMTU/MSSチューニング

もしあなたがKubernetesクラスターやDockerコンテナ、あるいはLinuxサーバーのインフラを構築・運用しているなら、インターフェースのMTUを適切に設定する必要があります。

例えば、DockerのデフォルトブリッジネットワークやCNI(CalicoやFlannelなど)のオーバーレイネットワークでは、トンネルプロトコルのオーバーヘッドを考慮して、仮想インターフェースのMTUを 1450 や 1400 に明示的に落とす設定が必須となります。

Linuxホスト上で現在のパケットのPath MTUや、意図したMSSで通信できているかを確認するには、以下のコマンドラインツールが非常に強力です。

1. ping コマンドによるPMTUDの手動テスト(DFフラッグ付き)

Linux環境で、特定のサイズ以上のパケットが断片化なしに通るかをチェックします。

# パケットサイズ1472バイト(IPヘッダー20 + ICMPヘッダー8 = 1500バイト)でDFフラッグを立てて送信
ping -M do -s 1472 api.example.com

# もし途中のMTUが1400であれば、「ping: local error: Message too long, mtu=1400」といったエラーが返ってきます。

2. tcpdump によるパケットキャプチャでの確認

APIサーバー側や踏み台サーバーでパケットをキャプチャし、SYNパケットのMSSオプションを確認します。

# ネットワークインターフェース(例: eth0)でポート443のTCPハンドシェイクを監視
sudo tcpdump -i eth0 -nn -vv 'tcp[tcpflags] & (tcp-syn) != 0'

# 出力例(MSSが1460に設定されていることを確認)
# IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
#     192.168.1.50.54321 > 10.0.0.10.443: Flags [S], cksum 0x1234 (correct), seq 1000, win 64240, options [mss 1460,sackOK,TS val 123456 ecr 0,nop,wscale 7], length 0

ここで flags [DF] が立っており、mss 1460 がどのようにネゴシエーションされているかをリアルタイムで追うことができます。

—

5. アプリケーション層(Web API)からのアプローチ

ネットワーク層やインフラ層の調整権限がないアプリケーション開発者であっても、Web APIの設計においてこの問題に対する防衛策を講じることができます。

例えば、Pythonの requests ライブラリや Node.js の fetch を用いて外部APIと通信する際、タイムアウトやコネクションプールの設定を適切に行うことが重要です。また、巨大なペイロードを一度にPOSTするのではなく、チャンク分割(Chunked Transfer Encoding)やページネーションを導入して、1リクエストあたりのセグメントサイズを自然に抑える設計が、結果的にネットワークの不安定さに対する強靭さを生みます。

以下に、Pythonでタイムアウトやコネクションエラーを適切にハンドリングしつつ、堅牢にAPIリクエストを投げる実装例を示します。

import requests
from requests.exceptions import Timeout, RequestException

def robust_api_request(url, payload):
    """
    ネットワークの微小な揺らぎやMTU問題によるパケットロスを想定し、
    適切なタイムアウトと例外ハンドリングを組み込んだAPIクライアントの例
    """
    headers = {
        'Content-Type': 'application/json',
        'User-Agent': 'RobustAPIClient/1.0'
    }
    
    try:
        # 接続タイムアウト(connect)と読み込みタイムアウト(read)を個別に設定
        # パケットロスによる再送待ちは read タイムアウトに影響します
        response = requests.post(
            url, 
            json=payload, 
            headers=headers, 
            timeout=(3.1, 10.0)
        )
        
        # ステータスコードのチェック
        response.raise_for_status()
        
        return response.json()

    except Timeout as e:
        # パケットドロップやPMTUD崩壊によるハングアップをここで検知・ログ記録
        print(f"[ERROR] APIリクエストがタイムアウトしました。ネットワーク経路のMTU/MSS問題の可能性があります: {e}")
        raise
        
    except RequestException as e:
        print(f"[ERROR] 予期せぬネットワークエラーが発生しました: {e}")
        raise

# 使用例
if __name__ == "__main__":
    api_url = "https://api.example.com/v1/data"
    data = {"items": ["test_payload_data" * 50]} # あえて大きめのデータ
    
    try:
        result = robust_api_request(api_url, data)
        print("APIレスポンス成功:", result)
    except Exception:
        print("処理を中断しました。ネットワーク管理者にPMTUD/MSSの設定確認を依頼してください。")

—

まとめ:見えないパケットの旅路に想いを馳せて

今回は、MTUとMSSの深遠な関係性、そしてPMTUDが崩壊したときに発生するブラックホール問題について、ネットワークの基礎から実務的なデバッグ・対策手法まで解説しました。

  • MTU はL2/L3の物理的なパケット上限サイズ。
  • MSS はL4(TCP)が一度にやり取りするデータペイロードのサイズ(MTU - ヘッダー40バイト)。
  • ICMPがブロックされたネットワーク環境ではPMTUDが機能せず、巨大なパケットが闇に消える(ブラックホール問題)。
  • 現場の処方箋としては、ルーターやVPNでの MSS Clamping の導入や、インフラ・コンテナ層での適切なMTUチューニングが最も確実。

Web APIの設計やインフラの構築において、「コードが正しいのに動かない」という壁にぶぶつかったとき、問題の解決の糸口はモニターの上のアプリケーションコードではなく、目に見えない光や電気信号となって海底ケーブルやルーターのキューを駆け巡る「パケットのサイズとヘッダー」の中に隠されていることが多々あります。

この知識が、皆さんの日々のインフラ運用や、深夜の障害対応における強力な武器となることを願っています。それでは、良きネットワークライフを!

コメント

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