【実務・中級編】 IPv4ヘッダーのTotal LengthフィールドとMTU制限 – ネットワーク基礎とWebセキュリティ実践ガイド

なぜ君の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 の話でもしようか。また現場で会おう。

コメント

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