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

Path MTU Discoveryとは

IPv4 においては、パケットサイズが NW 機器のインタフェースの MTU 値を超えた場合、その NW 機器上でパケットを分割して MTU 値以下にする "フラグメンテーション" という機能があります(PC等のパケットの送信元が分割する TCP "セグメンテーション"とは違います)。

しかしフラグメンテーションは効率が悪いです。というのも、ルータでパケットを分割する処理負荷、PC 側でパケットを再構築する処理負荷がかかるからです。

詳細は以下の記事をご参照下さい。

【図解】MTUとMSS, パケット分割の考え方~IPフラグメンテーションとTCPセグメンテーション~
【図解】MTUとMSS, パケット分割の考え方~IPフラグメンテーションとTCPセグメンテーション~
MTU と MSS の違い MTU (Maximum Transmissi...

そのため、通信元と通信先の組合せ毎に、フラグメンテーションを必要としない最大のMSS値を把握できる(TCP においては TCP セグメンテーションによる分割を行える)仕組みが考え出されました。 それが Path MTU Discovery です。この Path MTU Discovery は ICMP プロトコルをベースに構成されている仕組みであり、これ自体がプロトコルなわけではありません

なお、IPv6 においてはこの考えに基づき、フラグメンテーションはしてはならないことになりました。

Path MTU Discoveryの動作

Path MTU Discovery の基本的な動作としては以下の通りです。

まず、Path MTU Discovery をサポートしている OS はソケット API を通じて、通信する際に IP パケットに df ビット (don't fragment) を立てます。

データを送る PC は、PC の MSS 値に従って TCP セグメントサイズを決めます。この MSS 値はインタフェースの MTU 値に応じて適切な設定が入っており、通常は Ethernet インタフェース (MTU = 1500 Byte) なので、MSS = 1460 となっています。

経路上の全てで PC の MTU 値を超えない場合、そのまま通信が行なわれます。

経路上のどこかでこの MTU 値を超えてしまう場合、df ビットが立っているため、フラグメントができないので、データが破棄されますが、 それと同時に、データの送信元の PC に ICMP のタイプ Ox03/コード Ox04 (Destination Unreachable/Fragmentation needed and Don't Fragment was set) が送られます。

この ICMP のタイプ Ox03/コード Ox04 には、破棄される原因となった MTU 値 (Next-Hop MTU) が含まれています。この情報を元に、PC は MSS 値を修正し、再度パケットを送ります。このような動作を通信が相手先に到達するまで繰り返します。

ブラックホール問題

Path MTU Discovery の仕組みにおいて、ICMP が送信元に届かなかった場合、通信はずっと届かなくなってしまう。 これをブラックホール問題と言います。

具体的には、サーバがクライアントにデータを送ろうとしてパケットが破棄された場合を考えます。このとき、サーバは不要な通信を止める目的で ICMP を全て止めてしまっていると、この問題が起きます。

そのため、DoS 攻撃の心配は出てきますが、ICMP の Destination Unreachable だけは通信が届くようにしておくのがよい場合もあります。

NATポイントでのパケット破棄問題

特殊なケースとして、ソース NAT の設定が入ったNW機器のインタフェースにおいて MTU サイズ越えが発生し、ICMP のタイプ Ox03/コード Ox04が送信される場合にも問題が発生します。

具体例として以下のケースが考えられます。

  1. クライアント 200.200.200.200 から 50.50.50.50 宛に http 通信を開始する。
  2. NAT ルータが 50.50.50.50 を 10.1.1.1 に NAT 変換する。
  3. 10.1.1.1 が 200.200.200.200 に向けて 1500 Byte のパケットを送出する。
  4. NAT ルータにおいて 10.1.1.1 を 50.50.50.50 に NAT 変換し、PPPoE インタフェースへルーティングする。
  5. PPPoE インタフェースにおいて MTU1454 を超えるパケットが到着したため、NAT ルータの内部向けインタフェースから Web サーバへ ICMP Type3 Code4 を送信。その際、NAT により宛先は 50.50.50.50 から 10.1.1.1 に変換されるが、ICMP メッセージ内に含まれる IP ヘッダの情報は NAT 変換されず、50.50.50.50 のままになる。
  6. ICMP を受信した Web サーバは、到着した ICMP にある IP ヘッダを確認するが、自分の送った内容と異なるので無視する。

この場合、Web サーバではメッセージを受け取っても、パケットを有効なものと見做さないため、MSS 値を変えません。そのため、ブラックホール問題と同じで、いつまで経っても通信ができない状態になってしまいます。

don't fragment bitを設定変更する方法

方法は大きく分けて2つあります。

1. Windowsのレジストリを変更する

[Windowsキー + R] ⇒『ファイル名を指定して実行する』のボックスで以下を入力し Enter

regedit

⇒ レジストリ編集コンソールの以下パスを右クリック

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters

⇒『編集』の 『新規』をクリックし、『DWORD 値』をクリック

⇒ 入力ボックスに以下を記述し、Enter

EnablePMTUBHDetect

⇒『編集』メニューの『修正』をクリック

⇒『値のデータ』ボックスに "1" を入力し、『OK』をクリック

⇒ 再起動

2. ルータのPBRで変更する

ルータの PBR (Policy Based Routing) の機能には、ACL のルールに従ってネクストホップを変えるだけでなく、IP ヘッダの変更もできます。

フラグメントが必要となる直前のルータで、インタフェースに入ったタイミングで IP ヘッダの "df bit" を 0 に書き換えを行う方法です。

Cisco の場合は以下の手順になります。

access-list 1 permit any
#
route-map DF-off permit 10
match ip address 1
set ip df 0
#
interface giga 0/1
ip address 10.1.1.1 255.255.255.0
ip policy route-map DF-off

これにより、giga 0/1 に入力してきたパケットの df bit をゼロにできます。

なお、ルータがルーティングするのではなく、ルータ自体がパケットを発生する場合は、パケットが入力してくるインタフェースが無いので、(config-if)# ip policy の代わりに以下のコマンドを使います。

(config)# ip local policy route-map DF-off

Windows/Linux で Path MTU Discovery 自体の設定を無効にする

Windows/Linux ではデフォルトで Path MTU Discovery が有効になっています。無効にしたい場合は以下のように設定します。

1. Windows の場合

[Windowsキー + R] ⇒『ファイル名を指定して実行する』のボックスで以下を入力しEnter

regedit

⇒ レジストリ編集コンソールの以下パスを右クリック

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters

⇒『編集』の 『新規』をクリックし、『DWORD 値』をクリック

⇒ 入力ボックスに以下を記述し、Enter

EnablePMTUDiscovery

⇒『値のデータ』ボックスが "0" になっていることを確認する

⇒ 再起動

2. Linux の場合

root でコマンドで以下のように設定

[root@localhost ~]# sysctl -a | grep pmtu
net.ipv4.ip_forward_use_pmtu = 0
net.ipv4.ip_no_pmtu_disc = 0
net.ipv4.route.min_pmtu = 552
[root@localhost ~]# echo "net.ipv4.ip_no_pmtu_disc = 1" >> /etc/sysctl.conf;grep net.ipv4.ip_no_pmtu_disc /etc/sysctl.conf
[root@localhost ~]# sysctl -p