TT Lab
はじめる
学ぶ 学習パス コース

ネットワーク基礎

MTUと経路 — 大きさだけで失敗する通信

TT Labで続きを見る

一言でいうと

TCPは、自分のインターフェースのMTUだけを見てセグメントサイズを決めるので、経路の途中の狭い区間を知ることができません。そのことを知らせてくれるICMPをブロックすると、大きなパケットだけが静かに消えるブラックホールが生まれます。

なぜ必要なのか

症状がとりわけ特異な障害があります。

この組み合わせなら、他のものを見る必要はありません。パスMTUの問題です。そして、たいてい、VPN、トンネル、オーバーレイネットワークを新しく付けたか、経路が変わった直後に現れます。

どう動くのか

MTUは、1つのインターフェースが一度に送り出せるIPパケットの最大サイズです。イーサネットのデフォルトは1500バイトです。TCPは、ここからIPv4ヘッダー20バイトとTCPヘッダー20バイトを引いた1460バイトをMSSとして決め、ハンドシェイクのSYNパケットにオプションとして載せて、相手に知らせます。

決定的な事実は、これです。両側とも、自分のインターフェースのMTUだけを見てMSSを決めます。経路の途中に、より狭い区間があるかどうかは、誰も知りません。2つのホストが1460バイトをやり取りすることで合意しても、途中に1420バイトのトンネルがあれば、その合意は守れません。

小さなリクエストが成功する理由が、ここにあります。ヘルスチェックの応答は、セグメント1つで終わるので、狭い区間を無事に通過します。しきい値を超えるパケットだけが消えるので、症状がサイズによって分かれます。TLSハンドシェイクで止まるのも、同じ理由です。ClientHelloは小さいものの、サーバーが送る証明書チェーンは数千バイトなので、すぐに最大サイズのセグメントになります。

もともとTCPには、この問題を解く仕組みがあります。パスMTUディスカバリー(PMTUD)です。LinuxはTCPパケットにDF(分割禁止)ビットを立て、途中のルーターが、自分の次のホップのMTUより大きいDFパケットを受け取ると、それを破棄しながら、ICMPタイプ3コード4(Fragmentation Needed)で、次のホップのMTUを知らせます。送信者は、その値をキャッシュして、セグメントを小さく切って、もう一度送ります。

問題は、このICMPをブロックしたときです。送信者は、何も知らないまま、大きなパケットを送り続け、そのパケットは破棄され続けます。再送も同じサイズなので、また破棄されます。エラーは出ません。接続はESTABLISHEDのまま残り、アプリケーションは、自分のタイムアウトにかかって初めて終わります。これがPMTUDブラックホールです。

ここで、広く広まった誤解を正す必要があります。「セキュリティのためにICMPはすべてブロックする」というポリシーは、それ自体が障害の要因です。ブロックしてよいのはecho request/replyくらいで、タイプ3コード4は必ず通す必要があります。IPv6では、選択の余地すらありません。IPv6のルーターはパケットを分割しないので、ICMPv6 Packet Too Big(タイプ2)が必須です。

カプセル化が持っていくバイト数を覚えておくと、診断が速くなります。VXLANは50バイト(実効MTU 1450)、WireGuardは60–80バイト(1440–1420)、GREは24バイト(1476)、IPsec ESPは最大で約73バイトです。

現場での姿

Kubernetesでは、この問題がオーバーレイネットワークのせいで、定期的に再発します。ノードのインターフェースが1500なのに、VXLANインターフェースとPodのeth0も1500に設定されていると、即座にブラックホールです。VXLANは50バイトを持っていくので、1450である必要があります。症状は特徴的です。Pod同士で小さなリクエストは通るのに、大きな本文のPOSTだけが止まり、kubectl execはできるのに、ログが多いPodのkubectl logsが途中で止まります。

現実的な解決策は、トンネルの入口でMSSクランピングをかけることです。通過するSYNパケットのMSSオプションの値を、ルーターが直接書き換えて下げるので、ICMPにまったく依存しません。ただし、TCPにだけ適用され、QUICのようなUDPベースのプロトコルは、自分でサイズを調整する必要があります。

続くクイズで確認すること

「小さなリクエストは通るのに、大きなレスポンスだけが止まる」症状から、すぐにMTUを思い浮かべられるか、なぜエラーなしに止まるのかを説明できるかを確認します。