なぜ君のAPIは「謎のタイムアウト」を起こすのか? ― IPv4ヘッダーのTotal LengthとMTUの深淵
ネットワークエンジニアとして現場を渡り歩いていると、若手から「Web APIのレスポンスが、データ量が少ないときは正常なのに、特定のサイズを超えるとパタッと応答しなくなるんです」という相談をよく受ける。
ログを追うと Connection Reset や Timeout が記録されているが、アプリケーションのロジックには問題がない。さて、原因はどこにあるか? 答えは、OSI参照モデルの第3層(ネットワーク層)、もっと言えば 「IPv4ヘッダーの Total Length と MTU の不整合」 にあることが非常に多い。
今日は、教科書には載っていない「現場で生き残るためのパケットサイズの話」をしよう。
—
1. 16ビットの呪縛:Total Length フィールドの正体
IPv4ヘッダーには Total Length という、その名の通り「IPパケット全体のサイズ(ヘッダー+データ)」を表す16ビットのフィールドがある。
16ビットということは、最大値は $2^{16} – 1 = 65,535$ バイトだ。理論上は64KB弱のパケットを送れるように見える。しかし、現実の世界はそんなに甘くない。パケットがルーターを通過する際、物理メディアの制限である MTU (Maximum Transmission Unit) という壁にぶち当たるからだ。
標準的なイーサネットのMTUは1,500バイト。もし君のアプリケーションが生成したパケットの Total Length が1,500を超えていたら、ルーターは2つの選択を迫られる。
1. フラグメンテーション(断片化): パケットをMTUに合わせて分割する。
2. パケット破棄: Don't Fragment (DF) ビットが立っていれば、ルーターは容赦なくパケットを捨てる。
近年のWebインフラにおいて、フラグメンテーションは「悪」だ。パケットの順序制御や再送コストを考えると、断片化させるくらいなら、最初から収まるサイズで送るのがエンジニアの鉄則である。
—
2. 現場でMTUが引き起こす「サイレント・キラー」
なぜAPIでこの問題が起きるのか? それは、「MSS (Maximum Segment Size)」 の調整不足だ。
TCPのハンドシェイク(SYNパケット)の段階で、クライアントとサーバーは「私はこれくらいのサイズまで一度に受け取れるよ」という主張(MSS)を交換する。通常、MTU 1,500バイトからIPヘッダー(20バイト)とTCPヘッダー(20バイト)を引いた 1460 バイトがMSSとして設定される。
しかし、VPNやトンネリング(GRE, VXLAN, IPsec)を使用していると、ヘッダーにオーバーヘッドが追加され、実効MTUが1,400バイト以下まで下がることがある。この時、ハンドシェイクがうまくいっても、その後の巨大なHTTPレスポンスパケットが「断片化禁止」のフラグを立てて送出されると、途中のルーターで捨てられ、パケットは闇へ消えるのだ。
—
3. 実践:パケットサイズを確認し、制御する
では、我々はどうやってこの挙動を制御すればいいのか。まずは curl を使って、パケットの挙動を追跡する術を覚えよう。
curlでパケットのサイズとDFビットを確認する
以下のコマンドで、TCP通信の過程を可視化できる。
# -v: 詳細出力, --trace-ascii: パケットフローを詳細にトレース
# 通信の過程でMSSやパケットサイズの影響が出ていないか確認する
curl -v https://api.example.com/data --trace-ascii debug.txt
PythonでMTU制限を考慮したソケット通信のヒント
もし自前でソケット通信を行うような高負荷なAPIサーバーを構築しているなら、IP_MTU_DISCOVER を設定しておくのが安全だ。
import socket
# ソケットを作成
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# IP_PMTUDISC_DO を設定することで、DFビットを強制的に立てる
# これにより、途中で分割が発生するパケットを即座にエラーとして検知できる
s.setsockopt(socket.IPPROTO_IP, socket.IP_MTU_DISCOVER, socket.IP_PMTUDISC_DO)
# 接続先へデータを送信する際のサイズ管理をここで行う
# 1460バイトを超えないようにバッファを制御するのがプロの仕事
s.send(data[:1460])
—
4. エンジニアへのアドバイス:どうトラブルを解決するか
もし君が「特定の環境からだけAPIに繋がらない」というトラブルに直面したら、以下の手順で切り分けてみてほしい。
1. ping によるMTUパス探索:
ping -f -l 1472 <ターゲットIP> を試す。(-f はフラグメンテーション禁止、-l はサイズ指定)。もし1472(ヘッダー込み1500)で通らず、値を下げて通るなら、経路上のどこかのMTUが小さいことが確定する。
2. MSS Clampingの検討:
インフラ側で iptables やロードバランサーの設定を見直す。Linuxサーバーであれば、以下のコマンドでMSSを強制的に絞る設定が可能だ。
# ロードバランサーやゲートウェイでMSSを制限する例
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360
# これにより、以降の通信は1360バイトを上限としてネゴシエーションされる
—
最後に:ネットワークは「見えない」からこそ面白い
Web API設計において、アプリケーション層のJSON構造や認証トークンばかりに目を奪われがちだが、それらを運ぶパケットは、幾多のネットワーク機器の「ルール」に従って移動している。
Total Length や MTU は、単なる仕様の数字ではない。それは通信が「届く」か「届かない」かを分ける、ネットワークの境界線だ。この境界線を意識できるようになれば、君はもう一段上のインフラエンジニアになれるはずだ。
次は、このパケットがルーターのキューで溢れる Bufferbloat の話でもしようか。また現場で会おう。
コメント