ネットワークの世界へようこそ。今日は、モダンなWebアプリケーション開発やクラウドインフラの運用に携わる君たちが、意外と見落としがちな「レイヤー3の深淵」について話をしよう。
最近は「HTTP/3だ」「gRPCだ」と上位レイヤーの華やかな話題が尽きないが、結局のところ、僕たちがやり取りしているデータは最終的にIPパケットという「封筒」に詰め込まれて物理回線を流れていく。その封筒が、ネットワークの途中で「大きすぎる」と判断されたとき何が起きるのか。
今回は、IPヘッダーの中でも特に泥臭く、しかしトラブルシューティングにおいて決定的な役割を果たす「フラグメントオフセット」と「More Fragments (MF) フラグ」の挙動について、パケットの鼓動を感じられるレベルまで掘り下げて解説する。
—
1. なぜ「断片化(フラグメンテーション)」が必要なのか
ネットワークには「MTU (Maximum Transmission Unit)」という、一度に送信できる最大データサイズの制限がある。イーサネットの世界では通常 1500 バイトだ。
しかし、VPNトンネル(IPsecやGRE)を通したり、複雑なカプセル化が行われるクラウド環境では、このMTUが 1400 や 1280 に減少することがある。このとき、MTUを超えるサイズのパケットがやってくると、ルーターはパケットを細かく切り刻む。これが「IPフラグメンテーション」だ。
Web APIのレスポンスが巨大なJSONだったり、大容量のファイルアップロードを行ったりする際、OSのカーネルやルーターの内部では、これから説明する「パズルのピース作り」が音もなく行われているんだ。
—
2. IPヘッダーの「三種の神器」:識別子、フラグ、オフセット
パケットを分割して、受信側で正しく元通り(リアセンブリ)にするためには、IPヘッダー内の3つのフィールドが連携し合う必要がある。
1. Identification (識別子 / 16bit): 分割される前の「元のパケット」が何だったかを特定するID。分割された全ての断片は、同じIDを持つ。
2. Flags (フラグ / 3bit):
DF (Don't Fragment): 「分割するな」という指示。これが立っていると、MTU超過時にルーターはパケットを破棄し、ICMPエラーを返す(Path MTU Discoveryの仕組み)。MF (More Fragments): 「まだ続きがあるか」を示す。1なら続きあり、0ならこれが最後の断片だ。
3. Fragment Offset (フラグメントオフセット / 13bit): その断片が、元のデータの「先頭から何バイト目か」を示す。
ここが肝心:オフセットの単位は「8バイト」
ここが試験に出るポイントであり、実務でも勘違いしやすい点だ。オフセットフィールドは13ビットしかないため、最大 65535 バイトを表現するために、「8バイト単位」で数えることになっている。つまり、実際のバイト位置を 8 で割った値がここに格納される。
—
3. 具体的なパケットの流れ:4000バイトのデータを送る場合
例えば、MTU 1500 バイトの経路で、IPヘッダーを含めて計 4000 バイトのパケットを送る場面を想定してみよう(IPヘッダーを 20 バイトとする)。
1. 第1フラグメント:
- サイズ:
1500バイト(データ本体は1480バイト) MFフラグ:1(まだ続きがある)Fragment Offset:0(先頭なので $0 \div 8 = 0$)
2. 第2フラグメント:
- サイズ:
1500バイト(データ本体は1480バイト) MFフラグ:1(まだ続きがある)Fragment Offset:185(前のデータが1480バイト分あるので、$1480 \div 8 = 185$)
3. 第3フラグメント:
- サイズ:
1060バイト(残りデータ1040バイト + ヘッダー20バイト) MFフラグ:0(これで最後!)Fragment Offset:370(累計データが2960バイトなので、$2960 \div 8 = 370$)
受信側はこの Identification が一致するものを集め、Offset 順に並べ、MF が 0 のパケットが届いた時点で「よし、パズル完成だ」と判断して上位レイヤー(TCP等)に渡すわけだ。
—
4. 実務でのデバッグ:tcpdump と ping で挙動を確認する
インフラの現場で「特定の拠点からだけAPIがタイムアウトする」といったトラブルに遭遇したら、まずはこのフラグメンテーションを疑うべきだ。
以下のコマンドで、意図的にフラグメントを発生させてパケットの中身を覗いてみよう。
pingによる再現(Linux/macOS)
-s オプションでデータサイズを指定し、-M want(Linuxの場合)などでフラグメントを許可する。
# 2000バイトのデータを送信(ヘッダーを含めるとMTU 1500を超える)
ping -s 2000 -c 1 8.8.8.8
tcpdumpでの観察
別のターミナルで tcpdump を走らせておくと、オフセットの動きが手に取るようにわかる。
# フラグメントの状況を表示
sudo tcpdump -i eth0 -v 'icmp'
# 出力例(イメージ):
# IP (tos 0x0, ttl 64, id 12345, offset 0, flags [+], proto ICMP (1), length 1500)
# IP (tos 0x0, ttl 64, id 12345, offset 1480, flags [none], proto ICMP (1), length 540)
ここで flags [+] と出ているのが MF フラグが立っている状態だ。
—
5. 擬似的なパケット生成によるオフセット計算の理解(Python/Scapy)
エンジニアなら、コードで理解するのが一番早い。Pythonの強力なパケット操作ライブラリ Scapy を使って、フラグメントパケットを手作りしてみよう。
from scapy.all import IP, ICMP, send
# 元の巨大なパケットを定義(Identificationを同じにするのがコツ)
packet_id = 54321
target_ip = "192.168.1.10"
# 第1フラグメント: 1480バイトのデータ (Offset=0, MF=1)
# frag=0 は内部的に Offset=0 を、flags=1 は MF=1 を意味する
p1 = IP(dst=target_ip, id=packet_id, frag=0, flags="MF") / ICMP() / ("A" * 1472)
# ※ICMPヘッダー8バイト + "A"*1472 = 1480バイト
# 第2フラグメント: 残りのデータ (Offset=185 (1480/8), MF=0)
p2 = IP(dst=target_ip, id=packet_id, frag=185, flags=0) / ("B" * 500)
# 送信
send(p1)
send(p2)
print(f"Sent fragmented packets with ID: {packet_id}")
このコードでは、手動で frag(オフセット)を計算して代入している。これを実行して Wireshark でキャプチャすると、受信側が「一つのICMPパケット」として再構築しようとする健気な姿が見られるはずだ。
—
6. シニアエンジニアの視点:なぜこれがセキュリティに関わるのか
最後に、なぜWebセキュリティの文脈でこの話が重要なのかを伝えておきたい。
1. IDS/IPSの回避: 攻撃者はパケットをバラバラにして、中身をわざと重複させたり(Overlapping Fragment)、微小な断片にしたりして、セキュリティ製品の検知を逃れようとする。
2. Firewallの負荷: ステートフルなファイアウォールは、全ての断片が揃うまでパケットをメモリに保持しなければならない。これはDoS攻撃の標的になりやすい(Fraggle攻撃など)。
3. APIのパフォーマンス: Web APIが返すJSONが巨大で、経路上のMTUが小さい場合、再構築のオーバーヘッドでレイテンシが悪化する。
結論として、僕たちが目指すべきは「可能な限りフラグメントを発生させないこと」だ。
そのためには、TCPの MSS (Maximum Segment Size) オプションを適切に調整し、最初からMTUに収まるサイズでデータを送出するのが定石だ。しかし、もしトラブルが起きたとき、このオフセットとMFフラグの仕組みを知っていれば、君はパケットキャプチャの海の中から一瞬で真犯人を見つけ出すことができるだろう。
「パケットは嘘をつかない。」
この言葉を胸に、明日からのデバッグに役立ててほしい。
コメント