【実務・中級編】 MTU(Maximum Transmission Unit)とMSS(Maximum Segment Size)の不整合 – ネットワーク基礎とWebセキュリティ実践ガイド

なぜ「API呼び出しが途中で死ぬ」のか? ― MTU/MSS不整合と闘うネットワークの流儀

現場で叩き上げのエンジニアなら一度は経験があるはずだ。小さなテキストデータなら問題なく取得できるAPIが、大きなJSONやバイナリを返した瞬間に、まるで電源が落ちたかのように沈黙する。curlで叩けば Empty reply from server、ブラウザのコンソールには net::ERR_CONNECTION_RESET。

「サーバーが落ちているわけじゃない。ネットワークも疎通している。なのに通信できない」。この現象の背後に潜む「MTU(Maximum Transmission Unit)」と「MSS(Maximum Segment Size)」の不整合は、現代のクラウド環境でも依然としてエンジニアの胃を痛める、最も泥臭く、かつ最も本質的なパケットの死角だ。

今日は、パケットがネットワークの荒波に揉まれ、断片化という悲劇に直面したとき、何が起きているのかを紐解いていこう。

—

1. MTUとMSS:パケットの「積載量」と「中身」の規律

まず、基本を整理しよう。

  • MTU (Maximum Transmission Unit): ネットワーク機器(NICやルーター)が一度に送信できる物理的なパケットの最大サイズ。イーサネットの標準は 1500 byte だ。これを超えるパケットは、基本的には通れない。
  • MSS (Maximum Segment Size): TCPヘッダーを除いた、TCPペイロード(データ部分)の最大サイズ。

TCPのコネクションを確立する「3ウェイ・ハンドシェイク」の際、お互いに「俺はこれくらいのサイズのデータなら一気に処理できるぜ」と申告し合うのが MSS だ。

計算式は非常にシンプルだ。
MSS = MTU - IPヘッダー(20byte) - TCPヘッダー(20byte)

標準的なMTU 1500 byte の場合、MSS は 1460 byte となる。しかし、VPNトンネルやクラウド上の仮想ネットワーク(VXLANなど)を経由すると、カプセル化によるオーバーヘッドが発生し、実効的なMTUが 1500 を割ってしまうことが多々ある。ここが不整合の入り口だ。

—

2. PMTUDの理想と、現実に潜む「ブラックホール」

この問題を解決するために設計されたのが、パスMTUディスカバリー(PMTUD)だ。

送信元はIPヘッダーに「断片化禁止(DF: Don’t Fragment)」ビットを立ててパケットを送る。もし経路上のどこかでMTU制限に引っかかると、ルーターは「ICMP Type 3 Code 4(Destination Unreachable: Fragmentation Needed)」という悲鳴を上げる。これを受け取った送信元は、「あ、デカすぎたのか」と理解し、送信サイズを縮小する。

……というのが教科書の理屈だが、現実はそう甘くない。セキュリティポリシーで ICMP を全拒否しているファイアウォールやロードバランサーが多すぎるんだ。結果、パケットは「断片化禁止」の烙印を押されたままドロップされ、送信元には何のフィードバックも戻らない。これが有名な「PMTUDブラックホール問題」だ。

—

3. 現場の特効薬:MSSクランプ(MSS Clamping)

PMTUDが機能しない環境で、我々が取るべき最も実用的な防衛策が「MSSクランプ」だ。これは、ルーターやロードバランサーがSYNパケットを通過させる際、MSS 値を強制的に書き換えるテクニックである。

例えば、iptables を使って、パケットの MSS を 1400 byte に強制的に制限する設定はこうなる。

# 経路上のMTUが1450などの環境で、余裕を持って1400に制限する例
# mangleテーブルでFORWARDチェーンに介入し、MSSを書き換える
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1400

これを踏み台サーバーや境界ルーターで設定するだけで、それまで謎の断絶を繰り返していたAPI通信が嘘のように安定することがある。

—

4. デバッグの最前線:パケットの「声」を聞く

問題が発生したとき、闇雲にコードを弄るのはやめよう。まずはパケットサイズを確認するのが鉄則だ。

curl でヘッダーを確認する

まず、curl で詳細なトレースを見る。

# -v で詳細を表示、--trace-ascii でパケットの内容を追う
curl -v https://api.example.com/huge-data --trace-ascii debug.dump

もし通信が途中で止まるなら、Wireshark や tcpdump の出番だ。

tcpdump で MSS を確認する

サーバー側で、接続確立時のSYNパケットをキャプチャしてみる。

# 80番ポートでSYNパケットのみをキャプチャし、MSSオプションを確認
sudo tcpdump -ni eth0 'tcp[tcpflags] & tcp-syn != 0' -vv

出力結果の中に mss 1460 という文字列が見えるはずだ。もし、クライアントが 1460 を要求しているのに、経路のどこかでパケットが消失しているなら、そこがボトルネックだ。

Python で再現試験を行う

クライアント側で、あえて大きなペイロードを送信してみるスクリプトを書いてみるのもいい。

import requests

# 巨大なJSONデータを送信してみるテスト
payload = {"data": "A" * 2000}  # MTUを意識した大きなデータ
try:
    # タイムアウトを短く設定し、コネクションがハングアップするか確認
    response = requests.post("https://api.example.com/upload", json=payload, timeout=5)
    print(f"Status: {response.status_code}")
except requests.exceptions.ConnectionError as e:
    print(f"ネットワークエラーが発生しました: {e}")

—

最後に:ネットワークを「疑う」勇気を

Web APIの設計において、MTUやMSSを意識することは一見、レイヤーの異なる「低レイヤーの仕事」に見えるかもしれない。だが、クラウドネイティブな時代になればなるほど、物理的な制限は仮想ネットワークという皮をかぶって、より巧妙に隠蔽される。

アプリケーションがどれほど美しく設計されていても、それを運ぶパケットが物理的な制約という「壁」に激突していては、すべては水泡に帰す。

「コードは正しいはずだ。原因はどこだ?」と迷ったとき、一度レイヤーを下げて、パケットのサイズと経路に思いを馳せてみてほしい。ネットワークは、常に正直だ。不整合があれば、必ずどこかでパケットが悲鳴を上げている。その声を聞き取れるようになることこそが、凄腕のエンジニアへの第一歩なのだから。

コメント

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