【図解】Path MTU DiscoveryブラックホールとPLPMTUD(RFC4821)による自動調整

前提

通常の Path MTU Discovery については以下の記事をご参照下さい。

【図解】Path MTU Discoveryの仕組み~ルータやWindows/Linuxでの設定確認/変更方法~

Path MTU Discoveryとブラックホール問題

通常の Path MTU Discovery では ICMP Type:3 (Destination Unreachable) / Code:4 (Fragmentation needed and df was set) に頼ることになりますが、ネットワークの設定によってこの ICMP が拒否されてしまうケースがあります。

ファイアウォールの場合はステートフルインスペクションにより ICMP Type:3/Code:4 も (戻りの通信と同様に) 動的に許可されますが、経路途中のルータなどで ICMP を全て止める ACL 設定をしてしまうと、ICMP Type:3/Code:4 が届かなくなり、Path MTU Discovery が効かなくなってしまいます。

具体的にどうなるかというと、TCP 3way handshake のような小さなサイズのパケットは通過して TCP コネクション確立はできるけど、そのあとのデータ送受信の通信ができない、という状態になってしまいます。

これは一般に Path MTU Discovery のブラックホール問題と呼ばれています。

この回避策として、Packetization Layer Path MTU Discovery という仕組みが 2007年に RFC4821 として規格化されました。

Packetization Layer Path MTU Discoveryとは

簡単に言うと、ICMP に頼らず、MTU の適切なサイズを自ら調整するアルゴリズムです。

はじめは控えめのサイズの MTU で通信を開始し、その後は実際の通信の一部のパケットを『probe』という探索用のパケットと位置付けて少し大きめの MTU で送信し、成功したら MTU を上げる、というのを繰り返します。

探索するためにはサイズを制御できる必要があるため、探索を行うレイヤーは Packetization Layer (パケット化を行う層) である必要があります。パケットサイズをコントロールできるプロトコルのことを指します。

RFC では具体的な実装例として TCP, SCTP, IP が定められています。

ただし、IP については非推奨です。なぜならサイズの制御を IP フラグメンテ-ション (経路途中の NW 機器ではなく、送信ホスト自らがフラグメンテーションを実施) により行うからです。さらに IP レイヤーだけではその MTU で通信が成功したかどうかを把握する術が無く、結局 ICMP (Echo/Echo reply) 等の補助プロトコル/補助ツールを使う必要があります。

実通信を探索用に使えない上に、そもそも利用を回避したいはずの ICMP を別の形とはいえ利用するので実用性があまり無いです。

なお、図にある通り、フラグメンテ-ションはホストのみで行います。これは IPv6 の実装を模擬しています。(IPv6 では NW 機器によるフラグメンテーションは禁止している。)

この RFC4821 では『UDP への汎用的な適用は IP レイヤーを使わざるを得ない』としています。サイズ調整はアプリケーションでは想定していないことが多いからです。

ただし、これは 2007年のときの話で、現在は QUIC プロトコルへの実装に向け、UDP で実用性のある PMTUD 規格 Datagram PLPMTUD の策定中 (draft 12) です。

TCP でのアルゴリズムの実装例

Section 10.1 に TCP での具体的な実装として 2 つの方法が示されています。

以下にイメージ図を示します。

probe (MTU 探索パケット) は MTU 超過となる可能性があり、もしドロップしたら受信側では受信データに間隔 (gap) が発生します。この gap を "probe gap" と呼びます。

2 つの方法の主な違いは『probe gap をどのように再送するか』です。

Method 1. Non-overlapping method は探索に成功する確率が高いなら効率的な方法です。一方、Method 2. Overlapping method は探索に失敗する確率が高いなら効率的な方法です。

TCP では受信したデータが全体のどの位置のデータなのかをシーケンス番号で知ることができます。なので重複しているデータを受信しても問題なく処理できます。Method 2 ではこれを利用し、再送されるパケットが 1 パケットになるようにしています。

UDP だとこのようにはできません。シーケンス番号がないためです。

上位層でシーケンスを生成しないアプリケーションが UDP で通信する場合は 1 パケットで完結するか、IP フラグメンテ-ションで分割する必要があります。

例えば DNS の場合、DNS パケット内にシーケンスのフィールドが無く、アプリケーションが 1 つのレスポンスを 2 つのパケットに分けると、もし途中でパケットの順番が入れ替わったりしたら受信側で正しく組み立てられません。(フラグメンテ-ションであれば、IP ヘッダ内のフラグメント関連のフィールドで順番が分かるのですが。)

そのため、DNS では十分小さめのサイズ 512 Bytes を上限とし、それより大きいサイズの通信をしたいときは TCP を使うように定められています。(これは後に EDNS0 という拡張機能により、4096 Bytes までを UDP で送信できるようになりました。)

probe/non-probeのパケットサイズの決め方

探索用パケット [probe] のパケットサイズについては Section 7 で議論されています。

search_low が probe の下限サイズ、search_high が probe の上限サイズ、eff_pmtu が現在最適と考えられる non-probe 用のサイズを意味します。

RFC ではこれらの具体的な数字を具体的に示しています。2007年時点の環境を踏まえての議論になっていますが、現在でも十分通用するものかと思います。

search_low の初期値は 1024 Bytes ならたぶん十分安全だろう。

It is RECOMMENDED that search_low be initially set to an MTU size that is likely to work over a very wide range of environments. Given today's technologies, a value of 1024 bytes is probably safe enough.

eff_pmtu の初期値は 1400 が妥当かもしれない。

Note that the initial eff_pmtu can be any value in the range search_low to search_high. An initial eff_pmtu of 1400 bytes might be a good compromise because it would be safe for nearly all tunnels over all common networking gear, and yet close to the optimal MTU for the majority of paths in the Internet today.

search_high は送信インタフェースの MTU かプロトコル固有の値に。

The initial value for search_high SHOULD be the largest possible packet that might be supported by the flow. This may be limited by the local interface MTU, by an explicit protocol mechanism such as the TCP MSS option, or by an intrinsic limit such as the size of a protocol length field.

完全に通信できないブラックホール状態では eff_pmtu は search_low まで下げるか、両方とも 68 bytes まで下げる。

The response to a detected black hole depends on the current values for search_low and eff_pmtu. If eff_pmtu is larger than search_low, set eff_pmtu to search_low. Otherwise, set both eff_pmtu and search_low to the initial value for search_low. Upon additional successive timeouts, search_low and eff_pmtu SHOULD be halved, with a lower bound of 68 bytes for IPv4 and 1280 bytes for IPv6.

ステータス

PLPMTUD では Probe の結果により eff_pmtu を変えていきますが、Probe のステータスとして以下の 4 つがあります。これは TCP だけでなく、汎用的なステータスです。TCP では ack で受け取れた or 受け取れないを検知しますが、他のプロトコルでは他の仕組みで検知できる必要があります。

  1. Success = リモートホストが probe を受け取れたことを検知した
  2. Failure = リモートホストが probe は受け取れず、その前後の non-probe パケット (leading window & trailing window) は受け取れたことを検知した
  3. Timeout Failure = リモートホストから、probe より前の non-probe (leading window) は受け取れたが、それ以降に応答が無い (リモートホストの混雑?)
  4. Inconclusive = リモートホストが probe といくつかの non-probe を受け取れなかったことを検知した (この調査結果は疑わしい)

その他の考慮事項

PLPMTUD では回線負荷分散などにより Path が複数あり、かつ、複数の Path でそれぞれ MTU が異なるシナリオも想定しています。なので eff_mtu は必ずしも上方向に上がっていくだけではなく、学習していく中で下がることもあります。

エラー率を考慮したり、前の eff_mtu の値を保存しておき、必要に応じて戻す、といったことも実装すべき、としています。

A RECOMMENDED strategy would be to save the value of eff_pmtu before raising it. Then, if loss rate rises above a threshold for a period of time (e.g., loss rate is higher than 10% over multiple retransmission timeout (RTO) intervals), then the new MTU is considered incorrect. The saved value of eff_pmtu SHOULD be restored, and search_high reduced in the same manner as in a probe failure. PLPMTUD implementations SHOULD implement MTU verification.

また、probe パケットはロスする確率が高いため、もしロスしても輻輳制御してはいけない、といった記述がある一方、

When only the probe is lost, it is treated as an indication that the Path MTU is smaller than the probe size. In this case alone, the loss SHOULD NOT be interpreted as congestion signal.

先程のステータスの Timeout Failure の場合は輻輳制御が必要だ、としています。

If the loss was detected with a timeout and repaired with go-back-n retransmission, then congestion window reduction will be necessary.