小さなリクエストは通るのに大きなダウンロードが止まり、誰かがMSSクランピングを提案する。その提案は正しい順序で試す価値があるほど頻繁に正しく、盲目的に適用すると半日を無駄にするほど頻繁に間違っている。以下の三つのチェックは、本物のTCPサイズ問題を、UDPの問題、壊れたpath-MTU検出、そして書き換えるはずのハンドシェイクを決して見ないクランプから切り分ける。

障害の形から始める

サイズの障害には署名がある。接続が確立し、認証と小さな応答が成功し、その後にフルサイズのセグメントが必要になった途端に転送が無期限に止まる。テキストは読み込むが画像を最後まで表示しないページは、より小さな規模での同じ障害だ。まったく接続できないなら問題は経路上のより手前にあり、クランピングは役に立たない。クランプは、決して届かなかったハンドシェイクを修復できない。

この署名が存在するのは、セグメントサイズが合意されるのがハンドシェイクだからだ。双方がTCPオプションとして最大セグメントサイズを通知し、送信側は相手の通知を尊重する。経路がそれほど大きなセグメントを運べないなら、障害は後になって、送信側がようやく一つ試すときに現れる。

その症状を読む間も、トランスポートを見失わないこと。クランピングはTCPオプションの書き換えなので、同じトンネル下で音声やビデオ通話が止まってもMSSの問題ではなく、クランピングで改善はしない。TCPオプションに触れる前にトランスポートを書き留めておく。

経路のコストを見積もり、トンネルと比較する

重要な数字は二つあり、しばしば混同される。path-MTUは経路が断片化なしで運ぶ最大のパケットで、IPv4の通知MSSはその値からIPとTCPのヘッダー40バイトを引いたもの、IPv6なら60を引いたものだ。トンネルのカプセル化は、内側のパケットが始まる前から予算の一部を消費する。

ping -M do -s 1400 198.51.100.20
tracepath 198.51.100.20

do-not-fragmentビット付きのpingは、そのペイロードサイズのパケットが生き残るかを教えてくれる。失敗するまでサイズを変えれば、経路の限界を経験的に見つけたことになる。tracepathの出力は経路沿いで検出されたMTUを報告し、うまく動けば速く、途中のホップがプローブをフィルタすると信頼性が下がる。

次にトンネルのオーバーヘッドを引く。外側のIPヘッダー、UDPヘッダー、トンネル固有のヘッダーはいずれも同じ1500バイトから出るので、典型的なEthernet基盤での使用可能な内側MTUは物理リンクが示すより低い。計算した内側MTUが相手の通知より低ければ、推測ではなく本物のクランプ候補を見つけたことになる。計算値がすでに相手の通知を上回っているなら、ここで止めて別の場所を調べる。

ハンドシェイクが通る場所にクランプを置く

クランプは、ゲートウェイを通過するSYNとSYN-ACKのパケット内のMSSオプションを書き換えることで機能する。どちらの方向も決して通らないインターフェースにルールを置くと、ネゴシエーションは変わらず完了し症状は残る。だからこそ、値よりも配置が結果を決める。

SymptomWhat it points toNext check
Handshake completes, large transfer stallsSegment size larger than the path can carryPath MTU against the tunnel's effective MTU
Nothing connects, small or largeRouting, firewall or keying failureNot MSS; look at the tunnel state
Only UDP applications stallDatagram size or a dropped ICMP errorPlatform-specific UDP handling, not clamping
Fixed after reconnect, returns laterClamp applied to one direction or one interfaceRule placement and the handshake path

方向がもう半分だ。片方向だけクランプするとフローの半分は直り、残りは断続的に見える形で失敗し続ける。転送されるトラフィックの両方向に適用すること。トンネルインターフェースの古典的なLinuxの形は、SYNパケットに一致するmangleルールだ。

iptables -t mangle -A FORWARD -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

--clamp-mss-to-pmtuが実際に何をするかに注意する。ルートのMTUから値を導くが、それは常にトンネルの実際のペイロード予算ではない。内側のルートと外側の経路が異なるルートベースのトンネルでは、前のチェックで計算した明示的な値の方が自動値より予測しやすい。

新しい接続で検証し、フィードバックを生かし続ける

確立済みの接続は交渉したMSSを保持するので、同じセッションでのテストは何も証明しない。変更後に新しい接続を開き、たまたま完了した転送から推測するのではなく、交渉された値を直接読む。

ss -tin dst 198.51.100.20
iptables -t mangle -S | grep TCPMSS

最初のコマンドはソケットのMSSを輻輳状態とともに報告し、クランプが効いたことの最も速い確認になる。二つ目は、追加したつもりのルールが、それが置かれたテーブルに実際に存在することを確認する。

次に、直していない仕組みを守る。path-MTU検出はICMP fragmentation-neededメッセージが送信側に届くことに依存しているので、それを捨てるファイアウォールは正しいクランプがあっても大きな転送を壊し続ける。トンネル経路のためにこれらのメッセージを許可するか、クランピングが自分が選んだポリシーを補っていることを受け入れる。

最後に、クランプを調整つまみとして扱う誘惑に抗うこと。セグメントサイズを制限するので、必要としなかった経路ではスループットを犠牲にする。三つのチェックの後に適用すれば的を絞った修理であり、最初に適用すれば元の障害を隠し、以後のすべてのテストに二つ目の変数を加える変更になる。その取引は既定ではなく、意図して行う価値がある。

測定したpath-MTU、引いたトンネルのオーバーヘッド、追加したルール、その後に見たMSSを書き留める。四つの数字があれば、次のインシデントは調査ではなく検索になる。

関連記事