パケット番号を「削る」技術 ― QUICにおける可変長エンコーディングの深淵
Webの進化は止まらない。TCPの「3ウェイ・ハンドシェイク」という呪縛から解き放たれ、UDPベースのQUICが標準となった今、我々インフラエンジニアはパケットの「効率」について改めて向き合う必要がある。
特に興味深いのが、QUICがパケットヘッダーの軽量化のために採用している「パケット番号(Packet Number)の可変長エンコーディング」だ。単なる仕様の話ではない。これは、限られた帯域とCPUサイクルを極限まで絞り出すための、ネットワーク設計の美学そのものだ。
今回は、なぜQUICがパケット番号を削るのか、そしてその復号ロジックが現場のトラブルシューティングにどう関わってくるのかを解き明かしていこう。
—
なぜパケット番号を「削る」のか?
TCPの時代、シーケンス番号は常に固定長の32ビットだった。しかし、QUICにおいてパケット番号は、通信の安全と効率を両立させるための「可変長」という賢い戦略をとっている。
RFC 9000(QUIC)の設計思想は明確だ。「送る必要のないビットは送らない」。
パケット番号は受信側のACK処理やパケットの並び替えに不可欠だが、通信の途中で毎回32ビット(4バイト)をフルで送るのは冗長だ。直前のパケットと近い値であれば、下位ビットだけ送れば復元できる。この「差分」を利用したエンコーディングこそが、QUICの低レイテンシを支える縁の下の力持ちなのだ。
可変長エンコーディングの仕組み
QUICのパケット番号は、そのパケットの「最大送信済みパケット番号」を基準として、必要な分だけ(1〜4バイト)を動的に切り詰める。
- 1バイト: 0~127まで
- 2バイト: 0~16,383まで
- 4バイト: 0~1,073,741,823まで
受信側は、既に受信したパケットの番号をヒントに、この「切り詰められた値」から本来の値を復元(デコード)する。
—
復号ロジック:現場のデバッグで役立つ視点
パケット番号の復号には、以下のロジックが使われる。
1. 期待値の算出: 受信済み最大番号から、期待される次の番号を予測する。
2. マスキング: 受信した(切り詰められた)バイト数に応じて、上位ビットをマスクする。
3. 最近傍選択: 期待値に最も近い値を、切り詰められた値から算出する。
これを理解しておくと、WiresharkでQUICのダンプを追う際に、「なぜこの番号が表示されているのか」が直感的に分かるようになる。パケットロスが発生して番号が飛んだとき、復号ロジックがどう働くかを追うのは、エンジニアとしての醍醐味だ。
—
実践:QUICの挙動を観測する
では、実際にQUIC通信を観測してみよう。現代のエンジニアにとって、まずは `curl` でQUIC(HTTP/3)を確認するのが第一歩だ。
-v で詳細を表示し、–http3 でQUIC通信を強制する
QUICはUDP 443を使うため、環境によってはファイアウォールに注意
curl -v –http3 https://quic.rocks/
もしローカルでHTTP/3サーバー(例: quic-go)を立てているなら
パケットレベルのログを出力してデバッグする
./my-quic-server -debug=true
Pythonでパケット構成をシミュレートする(概念コード)
パケット番号をどのようにエンコードするか、そのロジックをPythonで簡潔に表現してみよう。
def encode_packet_number(pn, last_pn):
“””
単純化したエンコーディングロジック
実際にはさらに複雑な長さ計算が必要だが、本質はここにある
“””
diff = pn – last_pn
if diff < 128:
return pn.to_bytes(1, 'big') # 1バイトで表現
elif diff < 16384:
return pn.to_bytes(2, 'big') # 2バイトで表現
else:
return pn.to_bytes(4, 'big') # 4バイトで表現
使用例
current_pn = 100
last_pn = 95
encoded = encode_packet_number(current_pn, last_pn)
print(f"送信データ: {encoded.hex()}") # 実際にはヘッダーに埋め込まれる
---
現場でトラブルに遭遇したときのために
インフラ運用において、QUICのパケット番号関連で問題が起きるケースは、往々にして「ミドルボックス(L7ロードバランサーやファイアウォール)の不適切なパケット分割」だ。
- 症状: 特定のネットワーク環境下でのみHTTP/3通信が断続的に切れる。
- 原因: 一部の古いルーターが、可変長パケット番号を含むヘッダーを「不正なパケット」と誤認して破棄している。
- 対策:
- まずは `tcpdump` でUDPパケットをキャプチャし、パケット番号が正しくインクリメントされているか確認する。
- MTUサイズを見直す。QUICはPath MTU Discoveryを内包しているが、中間経路の制限が厳しすぎる場合、パケット断片化がパケット番号の復号ロジックと衝突することがある。
—
最後に:ネットワークを「読み解く」力
QUICのパケット番号エンコーディングは、一見すると「たかが数バイトの節約」に過ぎないかもしれない。しかし、この数バイトを削るために、プロトコル全体が複雑な状態管理を許容している。その背景には、ミリ秒単位のレスポンスを求めるユーザーに対する、我々エンジニアの執念が隠れている。
プロトコルの仕様書(RFC)を読むときは、単なるルールの羅列としてではなく、「なぜこの設計になったのか?」というエンジニアの意図を想像してほしい。それができれば、どんな難解な障害も、パケットを追うだけでその真因にたどり着けるようになるはずだ。
さあ、次はあなたのサーバーで `tcpdump` を走らせ、その目で生のパケットが呼吸する様子を観察してみてほしい。そこには、教科書には載っていない「リアルなネットワーク」が広がっている。
コメント