VPN経由のファイル転送だけ失敗する ― 経路MTU不一致とPMTUD Blackholeの調べ方と対策

リモートサーバへの接続経路を、新しく導入した拠点間VPN経由に切り替えました。
その直後から、ping は通るのに数十MBを超えるファイル転送だけがエラーも出さずに途中で止まる、という障害が起き始めました。
切り替え前の経路(VPNなし)に戻すと、同じファイルは問題なく転送できます。

この症状のとき、最初に疑うべきは 経路MTUの不一致と PMTUD Blackhole です。
この記事では、この障害を実機で再現し、調査コマンドの見え方と対策までを確認した流れをまとめます。


1. 症状

リモートサーバへの経路を拠点間VPNへ切り替えた直後から、次の症状が出ています。

  • ping は正常に応答が返る
  • 小さいデータのやりとり(Web閲覧・小さなAPI応答)は成功する
  • 数十MB以上のファイル転送が、エラーも出ないまま途中で止まる
  • SSH接続がログインの途中で固まることがある
  • 切り替え前の経路(VPNなし)では大容量転送も成功する
  • サーバのCPU・メモリ・ディスクは正常範囲内

この症状を、考えられる仮説と突き合わせます。

仮説症状との矛盾
帯域不足小さいデータのやりとりが普通に通るなら帯域は空いている。帯域不足は大小問わず遅くなる
サーバの高負荷(CPU/I/O)サーバのメトリクスは正常。負荷起因なら監視値に現れる
経路MTUの不一致「ping OK・小さいデータOK・大容量転送NG・特定経路限定」と完全に一致

症状と矛盾なく残る仮説は、経路MTUの不一致です。
なぜこの症状パターンになるのか、仕組みを次章で確認します。


2. 原因の仕組み ― 経路MTUとPMTUD Blackhole

VPNを通るとMTUが縮む

MTU(Maximum Transmission Unit)は、1回で送信できるパケットの最大サイズです。
イーサネットの標準は 1500 バイトで、送信側も受信側も、通常はこの値を前提に通信します。

ここで、データ量とパケットサイズの関係を押さえておきます。
大容量のデータは丸ごと1つのパケットで送られるわけではなく、MTU上限のサイズに分割して送られます。
例えば 150MB のファイルなら、1パケットあたり約1460バイト(1500からIP/TCPヘッダ40バイトを引いたデータ部)に刻まれ、約10万個のパケットとして送られる計算です。

つまり、大容量転送中はMTU上限いっぱいのサイズのパケットが大量に流れ、小さいデータのやりとりではパケットも小さいままです。
「データ量で成否が分かれる」ように見えた症状の実体は、パケットサイズで成否が分かれていることになります。

なお、差し引いた40バイトの内訳は、IPヘッダ20バイト(送信元・宛先のIPアドレス、TTL、DFビットなど、配送に必要な情報)+ TCPヘッダ20バイト(ポート番号、データの順序を管理するシーケンス番号など、通信の制御情報)です。
分割された各パケットは1つずつ独立して配送されるため、このヘッダは全パケットに毎回付きます。

ところが、VPN経由ではパケットの形が変わります。
VPN装置は、届いた元のパケットを丸ごと暗号化し、外側に新しいヘッダを付けて送り出します(カプセル化)。
中身は暗号化されて宛先も読めなくなるため、VPN区間の配送には「VPN装置からVPN装置へ」という宛先を書いた外側のヘッダが別に必要になります。

① クライアントが送り出す元のパケット(最大 1500 で作られる)
   [ IPヘッダ | TCPヘッダ | データ ]

② VPN装置が ① を丸ごと暗号化し、外側に配送用のヘッダを付ける
   [ VPNヘッダ | ①を丸ごと暗号化したもの ]
   → ① が 1500 のままだと、② は 1500 を超えて膨らむ

③ しかし、VPN区間の回線を通れるのも 1500 まで
   → ② を 1500 に収めるには、中身の ① を 1400 前後までに縮めておく必要がある

通れる上限は、どちらの経路も 1500 で変わりません。
変わるのは中身に使える枠です。

では、その枠はいくつ減るのか。
VPNヘッダの実体は、外側のIPヘッダ(20バイト)に加えて、暗号化や改ざん検知に使う情報で、方式や設定にもよりますが合計50〜80バイト程度です。
パケットごとに変動する分や設定差も見込んで、中身に使える枠を 1400 のような切りのいい値に設計するのが一般的です。
これが「VPNを通ると実効MTUが縮む」の正体です。

問題は、両端のサーバはこの縮みを知らないことです。
クライアントもサーバも自分のNICは MTU 1500 のままなので、1500バイトぎりぎりのサイズでパケットを送ろうとします。
そのパケットはVPNヘッダを重ねた時点で 1500 を超えるため、VPN区間を通れません。

本来はPMTUDが自動調整する

この食い違いを自動調整する仕組みが PMTUD(Path MTU Discovery:経路MTU探索) です。

前提を1つ押さえておきます。
IPの仕様上、ルータは大きすぎるパケットを自分で分割(フラグメント)して通すこともできます。
ただし、分割と再組み立てには性能面の負担が大きいため、現在のOSは最初から分割を禁止し、「通れないなら通知をもらって、送る側がサイズを調整する」方式を採っています。
この「分割禁止」の指示が DFビット(Don’t Fragment) です。
IPヘッダにある1ビットのフラグで、これが立ったパケットをルータは分割できず、通れない場合は破棄して送信元へ通知する決まりになっています。

この前提のうえで、PMTUDは次の流れで動きます。

  1. 送信側は、パケットに DFビットを立てて送る
  2. 経路の途中に通れないサイズの区間があると、そこにあるルータがパケットを破棄し、「mtu=1400 までしか通れない」という通知=ICMP Type3 Code4(Fragmentation Needed) を送信元へ返す
    ※ICMPのメッセージには種類ごとに番号が振られています(Type=大分類・Code=細分類)。ping の疎通確認は Type8(要求)/Type0(応答)、「宛先に届けられない」系の通知が Type3 で、その理由の4番(Code4)が Fragmentation Needed です
  3. 送信側はこの通知を受けて、そのサイズ以下でパケットを送り直す
  4. 受信側には、小さく詰め直されたパケットが順に届き、TCPが元のデータに組み立て直す(データが途中で切り捨てられるわけではない)

なお、PMTUDは事前に経路を測ってから送る方式ではなく、まず送ってみて、ぶつかったときに学ぶ方式です。
通れる経路ではこの流れ自体が発生せず、一度学んだ経路MTUは約10分間(Linuxの既定値:net.ipv4.route.mtu_expires=600秒)キャッシュされ、以降は最初から縮めたサイズで送られます。

正常な環境では、この一連の流れが一瞬で完了するため、利用者がMTUの縮みを意識することはありません。

ICMPが遮断されるとBlackholeになる

ところが、経路上のファイアウォールが ICMPを一律で遮断していると、手順2の通知が送信元に届きません。

  • 送信側は「パケットが届かなかった」ことしか分からず、同じサイズで再送を繰り返す
  • 再送は何度やっても同じ場所で破棄される
  • 結果、通信はエラーを出さないまま止まる

この状態を PMTUD Blackhole と呼びます。
パケットが消えるのに通知が返らない様子から名付けられたもので、RFC 2923(TCP Problems with Path MTU Discovery)で「black hole」問題として文書化されている用語です。
セキュリティ目的でICMPを広く塞いだ結果、経路MTUの通知まで道連れに遮断されてしまう、というのが典型的な発生パターンです。

観点内容
なぜ ping は通るかping は既定 84 バイトで、縮んだMTU(1400)より十分小さい
なぜ大容量転送だけ失敗するか実効MTU(1400)を超えるパケットだけが破棄される
なぜエラーが出ないか破棄の通知(ICMP)自体が遮断されており、送信側は再送を繰り返すだけになる
なぜVPN経由に限定されるかMTUが縮んでいるのはVPN区間だけで、他の経路は1500のまま通れる

整理すると、経路MTUの不一致は前提条件にすぎず、それ単体ではPMTUDの自動調整が働くため障害になりません
そこにICMPの遮断(PMTUD Blackhole)が重なって初めて、今回のような障害として表面化します。

発生の契機 ― 自然発生はしない

この仕組みから分かるとおり、MTUもICMPの遮断も構成で決まるため、安定運用中の経路で自然発生することはありません。
現れ方は次のどちらかです。

  • 構成変更を契機に発生する:VPNの新規導入・VPN装置の更改・ファイアウォールポリシーの変更(ICMP遮断の追加)・経路の切り替え
  • 潜在していたものが発覚する:構成上は以前から壊れていたが、大容量転送を伴う業務(バックアップジョブの新設など)を初めて流したときに顕在化する

したがって「いつから起きているか・直前に何を変えたか」の確認が、切り分けの近道になります。
今回のケースは、VPNへの経路切り替えという契機がはっきりしている典型例です。


3. 調査手順 ― 実機で再現して確認する

ここからは、実機で再現した調査の流れです。

検証環境は Oracle Linux 8.10 の仮想マシン3台です。
VPN装置の代役として、中継ルータ(vpn-router)のサーバ側インターフェースの MTU を 1400 に絞り、「経路の途中だけMTUが縮んでいる」状態を作りました。
さらに、このルータで ICMP Type3 Code4 を破棄し、経路上のファイアウォールがICMPを遮断している状況(PMTUD Blackhole)を再現しています。

client-node                vpn-router                  server-node
10.0.10.102  ──────►  10.0.10.101 │ 10.0.40.101  ──────►  10.0.40.103
MTU 1500               MTU 1500   │ MTU 1400              MTU 1500
                     (VPN区間の実効MTU縮小を模擬)

クライアント・サーバのNICはどちらも MTU 1500 のまま、というのが再現の要点です。
両端は1500で送れるつもりでいるのに、経路の途中だけ1400しか通れない——実際のVPN障害と同じ構図です。

Step 1. 症状を確認する ― pingは通るのに転送が固まる

準備 ― テストファイルの作成

転送テスト用に、サイズの異なる2つのファイルを乱数データで作成しておきます。

$ dd if=/dev/urandom of=/root/mtu-test-1k.bin bs=1K count=1
1+0 レコード入力
1+0 レコード出力
1024 bytes (1.0 kB, 1.0 KiB) copied, 0.000127449 s, 8.0 MB/s
$ dd if=/dev/urandom of=/root/mtu-test-100m.bin bs=1M count=100
100+0 レコード入力
100+0 レコード出力
104857600 bytes (105 MB, 100 MiB) copied, 0.358752 s, 292 MB/s

mtu-test-1k.bin が 1KB(1024バイト)、mtu-test-100m.bin が 100MB です。

ping は通る

まず、報告どおりの症状が出ることを確認します。

$ ping -c 3 10.0.40.103
PING 10.0.40.103 (10.0.40.103) 56(84) bytes of data.
64 bytes from 10.0.40.103: icmp_seq=1 ttl=63 time=0.602 ms
64 bytes from 10.0.40.103: icmp_seq=2 ttl=63 time=0.468 ms
64 bytes from 10.0.40.103: icmp_seq=3 ttl=63 time=0.511 ms

--- 10.0.40.103 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2031ms

ping は問題なく通ります。
なお ttl=63 は、ルータを1ホップ経由してきた証拠です。
TTLはルータを1つ通過するたびに1減る値で、Linuxの初期値は64です。
64 − 63 = 1 で「ルータ1台越しの応答」だと読み取れます。

scp は 1KB でも固まる

次に、1KBの小さなファイルを scp で転送してみます。
経過時間が分かるよう、ターミナルソフトの自動保存ログ(タイムスタンプ付き)で示します。

[2026-07-05 16:04:48.501] [root@client-node ~]# scp /root/mtu-test-1k.bin root@10.0.40.103:/tmp/
[2026-07-05 16:04:56.879] Connection closed by 10.0.40.103 port 22
[2026-07-05 16:06:56.896] lost connection

実行するとパスワードプロンプトが表示される前に応答がなくなり、しばらく待つと Connection closed で切断されました。

このタイムスタンプは「その行の書き込みが始まった時刻」に付く点に注意して読みます。
2行目は、Enter で実行を開始した 16:04:56 に行が開いたまま無応答が続き、切断メッセージが届いた時点で埋まったものです。
実際の切断時刻は3行目が始まった 16:06:56。実行開始からちょうど120秒です。

ここで見落としやすい点があります。
「小さいファイルなら成功するはず」という教科書的なパターンに反して、1KBの転送でも失敗しているのです。

理由は、ファイルを送る手前にある SSH の手続きです。
scp は、いきなりファイルを送るわけではありません。
まずSSHの接続確立——暗号方式を決める鍵交換(ハンドシェイク)と認証——を済ませ、できあがった暗号化通信路の中でファイルを送ります。
パスワードプロンプトが出る前に固まった今回の失敗は、この接続確立の段階で死んでいるということです。
1KBのファイルには、送信の順番すら回ってきていません。

切断が120秒ちょうどだったことにも意味があります。
これはハンドシェイクが終わらないまま、サーバ側 sshd の待ち時間 LoginGraceTime(既定120秒)が尽きて、サーバが接続を打ち切ったものです。

では、なぜ接続確立が終わらないのか。
結論だけ先に言うと、鍵交換でやりとりするパケットが実効MTUの1400より大きいためです。
その証拠は、Step 4 のパケットキャプチャで確認します。

「小さいデータならOK」が成り立つかどうかはプロトコル次第です。
SSH や TLS のように、ハンドシェイクの段階で実効MTUを超えるパケットを使う通信は、送るデータ量に関係なく接続の確立ごと固まります。
「ping は通るのに SSH がログイン途中で固まる」という報告も、同じ原因で説明できます。

Step 2. DFビット付きpingで経路MTUを特定する(最重要)

MTUの問題が疑われるとき、最初に打つべきコマンドは ping -M do -s 1472 <宛先> です。
-M do は DF ビット(分割禁止)を立てるオプション、-s 1472 はデータサイズの指定です。
1472 バイトに IP ヘッダ 20 バイトと ICMP ヘッダ 8 バイトが加わり、ちょうど 1500 バイト=標準MTUぎりぎりのパケットになります。

このコマンドは「見え方の対比」が診断の核心です。

正常な環境での見え方

まず、PMTUDが正常に機能している環境(ICMPが通る状態) での結果から示します。

$ ping -M do -s 1472 -c 3 10.0.40.103
PING 10.0.40.103 (10.0.40.103) 1472(1500) bytes of data.
From 10.0.10.101 icmp_seq=1 Frag needed and DF set (mtu = 1400)
ping: local error: Message too long, mtu=1400
ping: local error: Message too long, mtu=1400

--- 10.0.40.103 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2069ms

1発目で、経路上のルータ(10.0.10.101)から「mtu = 1400 までしか通れない」という通知が即座に返ってきます。
この Frag needed and DF set (mtu = 1400) という行が、セクション2で説明した ICMP Type3 Code4 の実物です(ping が受信したICMPを解釈して表示しています)。
2発目以降が local error に変わっているのは、カーネルが1発目の通知で経路MTU=1400を記憶し、送信前に自分で止めるようになったためです。
経路MTUが縮んでいても、PMTUDが生きていれば、このように即座にエラーとして見えます

障害環境での結果

次に、同じコマンドを今回の障害環境(ICMPが遮断された状態)で実行します。

$ ping -M do -s 1472 -c 3 10.0.40.103
PING 10.0.40.103 (10.0.40.103) 1472(1500) bytes of data.

--- 10.0.40.103 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2079ms

結果は、エラーメッセージが一切表示されないまま 100% packet loss
正常時に一瞬で返っていた通知が、影も形もありません。

つまり、この2つの結果の対比:

  • 通知が即座に返る → 経路MTUは縮んでいるが、PMTUDは機能している(通常は障害にならない)
  • 何も返らずタイムアウト → ICMPが経路で遮断されている=PMTUD Blackhole

「エラーが出ない」ことそのものが、ICMP遮断の証拠になります。

サイズを下げて境界を特定する

続けて、サイズを下げて境界を特定します。

$ ping -M do -s 1372 -c 3 10.0.40.103
PING 10.0.40.103 (10.0.40.103) 1372(1400) bytes of data.
1380 bytes from 10.0.40.103: icmp_seq=1 ttl=63 time=0.699 ms
1380 bytes from 10.0.40.103: icmp_seq=2 ttl=63 time=0.549 ms
1380 bytes from 10.0.40.103: icmp_seq=3 ttl=63 time=0.522 ms

--- 10.0.40.103 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2055ms

1372(+ヘッダ28=1400バイト)は通り、1472(=1500バイト)は通りません。
これで 経路MTU=1400 が特定できました。

Step 3. tracepath で経路を確認する

tracepath は、UDPパケットを使って経路の各ホップとMTUを調べるコマンドです(traceroute と違い追加インストール不要で、一般ユーザーでも実行できます)。

これも Step 2 と同じく、正常時との対比で見ます。
まず、PMTUDが正常に機能している環境では、経路MTUの pmtu 1400 が表示され、宛先まで到達します。

$ tracepath 10.0.40.103
 1?: [LOCALHOST]                      pmtu 1400
 1:  10.0.10.101                                           0.416ms
 1:  10.0.10.101                                           0.270ms
 2:  10.0.40.103                                           0.490ms !H
     Resume: pmtu 1400

次に、今回の障害環境での結果です。

$ tracepath 10.0.40.103
 1?: [LOCALHOST]                      pmtu 1500
 1:  10.0.10.101                                           0.293ms
 1:  10.0.10.101                                           0.307ms
 2:  no reply
 3:  no reply
(中略:30ホップまで no reply が続く)
     Too many hops: pmtu 1500
     Resume: pmtu 1500

Blackhole 状態では、pmtu が 1500 のまま更新されず、ルータ(10.0.10.101)より先が全て no reply になります。

もう1つ、Blackhole 時の出力で注目すべきは、ルータ(1ホップ目)までは応答が返っていることです。
遮断されているのは Fragmentation Needed(Type3 Code4)だけで、別の種類であるTTL超過の通知(Type11)は生きているためです。
tracepath が各ホップの検出に使っているのはこの Type11 なので、遮断の影響を受けずにルータまでは見えています。
「どこまで見えて、どこから先が無応答か」で、問題の区間を絞り込めます。

Step 4. パケットキャプチャで裏取りする ― 再送の繰り返しとICMPの沈黙

中継ルータに入れる環境であれば、tcpdump で決定的な証拠が取れます。
Step 1 と同じ失敗をもう一度起こし、その間にネットワーク上で何が起きているかを観測します。
接続の試行には、scp の代わりに ssh -vvv(詳細ログ付きのSSH接続)を使います。
固まる場所は scp と同じSSHハンドシェイクなので同じ失敗が再現でき、クライアント側の見え方も同時に記録できるためです。

ルータ側の観測 ― tcpdump

# tcpdump -ni ens19 '(tcp port 22 and greater 1300) or icmp[icmptype]==3'
16:09:55.389661 IP 10.0.10.102.58170 > 10.0.40.103.ssh: Flags [P.], seq 2134718348:2134719740, ack 165454045, win 502, options [nop,nop,TS val 3828198063 ecr 3256854657], length 1392
16:09:55.593772 IP 10.0.10.102.58170 > 10.0.40.103.ssh: Flags [P.], seq 0:1392, ... length 1392
16:09:55.801367 IP 10.0.10.102.58170 > 10.0.40.103.ssh: Flags [P.], seq 0:1392, ... length 1392
16:09:56.209386 IP 10.0.10.102.58170 > 10.0.40.103.ssh: Flags [P.], seq 0:1392, ... length 1392
16:09:58.681370 IP 10.0.10.102.58170 > 10.0.40.103.ssh: Flags [P.], seq 0:1392, ... length 1392
16:10:01.945388 IP 10.0.10.102.58170 > 10.0.40.103.ssh: Flags [P.], seq 0:1392, ... length 1392
16:10:21.849374 IP 10.0.10.102.58170 > 10.0.40.103.ssh: Flags [P.], seq 0:1392, ... length 1392
16:11:43.257388 IP 10.0.10.102.58170 > 10.0.40.103.ssh: Flags [P.], seq 0:1392, ... length 1392
(2行目以降は ack/win/TCPオプションの表記を省略・11回の再送から関連行のみ抜粋)

この出力から3つのことが読み取れます。

  1. length 1392 のパケットが原因です。データ部1392バイトにIP/TCPヘッダ52バイトを足すと、パケット全体で 1444バイト——実効MTUの1400を超えています。中身は、SSHが鍵交換の最初に送る「自分が使えるアルゴリズムの一覧」(文字列のリスト)で、ファイルのデータは1バイトも含まれていません。接続確立に必要なこのパケットが通らない限り、ファイルサイズに関係なく失敗する——Step 1 の「1KBでも失敗」の答えがこれです
  2. 同じパケット(seq 0:1392)が繰り返し再送されています。タイムスタンプの間隔は 0.2秒 → 0.4秒 → 0.8秒 …… → 55秒 と伸びており、TCPの再送タイムアウトが指数的に延びていく様子がそのまま見えます
  3. そして、ICMPが1行も現れません。キャプチャのフィルタにはICMP(Type3)を含めていますが、破棄の通知はどこからも返ってきていません

補足:ヘッダ52バイトの内訳は IP 20+TCP 32 です。TCPヘッダが基本の20バイトより大きいのは、タイムスタンプオプション12バイトが付いているためです(1行目の options [nop,nop,TS ...] がその表れ)。データ部のサイズはSSHのバージョンや設定で変わりますが、アルゴリズム名を列挙する都合上、1400前後になりやすいパケットです。

クライアント側の観測 ― ssh -vvv

この再送が続いていた間、クライアント側の ssh -vvv の出力は次の箇所で止まっていました。
-v はSSHのデバッグ表示オプションで、v を重ねるほど詳細になります(-vvv が最大)。

$ ssh -vvv root@10.0.40.103
(中略)
debug3: send packet: type 30
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
Connection closed by 10.0.40.103 port 22

鍵交換の応答(SSH2_MSG_KEX_ECDH_REPLY)を待ったまま進まず、最終的にサーバ側から切断されています。
自分が送った鍵交換パケットが経路で破棄され続けているため、いくら待っても応答は来ません。

これで、大きいパケットだけが破棄され、破棄の通知も遮断されていることが実機の証拠で確定しました。


4. 対策 ― 根本対応と現場の定番、それぞれの限界

対策① 根本対応:ICMP Type3 Code4 を通す

原因はICMPの遮断なので、根本対応は経路上のファイアウォールで ICMP Type3 Code4(Fragmentation Needed)を許可することです。

検証環境では、ルータに注入していた破棄ルールを削除しました(実環境でいえば、FWポリシーの修正にあたる操作です)。

# firewall-cmd --direct --remove-rule ipv4 filter OUTPUT 0 -p icmp --icmp-type fragmentation-needed -j DROP
success

実環境でLinuxベースのFW/ルータを使っている場合、Type3 Code4 を明示的に許可するなら次のような設定になります(遮断ルールより先に評価される位置へ入れます)。

# iptables -I FORWARD -p icmp --icmp-type fragmentation-needed -j ACCEPT

FWアプライアンス製品であれば、ICMPポリシーで「Destination Unreachable(Type3)のうち Code4(Fragmentation Needed)」を許可対象に加える設定が該当します。

破棄ルールの削除後、同じコマンドを再実行しました。

$ ping -M do -s 1472 -c 3 10.0.40.103
PING 10.0.40.103 (10.0.40.103) 1472(1500) bytes of data.
From 10.0.10.101 icmp_seq=1 Frag needed and DF set (mtu = 1400)
ping: local error: Message too long, mtu=1400
ping: local error: Message too long, mtu=1400

--- 10.0.40.103 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2069ms

$ scp /root/mtu-test-100m.bin root@10.0.40.103:/tmp/
root@10.0.40.103's password:
mtu-test-100m.bin                             100%  100MB 152.0MB/s   00:00

Fragmentation Needed の通知が即座に返るようになり、固まっていた100MBの転送も成功しました。

ルータ側のキャプチャにも変化が現れます。
Step 4 では1行も現れなかったICMPが、観測されるようになりました。

# tcpdump -ni ens19 '(tcp port 22 and greater 1300) or icmp[icmptype]==3'
16:28:16.324853 IP 10.0.10.101 > 10.0.10.102: ICMP 10.0.40.103 unreachable - need to frag (mtu 1400), length 556

tcpdump では unreachable - need to frag という表記になりますが、これが ICMP Type3 Code4 の生の姿です(unreachable=Type3、need to frag=Code4)。
設定変更はルータ側だけで、クライアントにもサーバにも一切手を入れていません。
PMTUD が機能しさえすれば、全てのプロトコルが自動調整で回復します。

このとき、クライアントのカーネルが経路MTUを学習した痕跡も確認できます。

$ ip route get 10.0.40.103
10.0.40.103 via 10.0.10.101 dev ens19 src 10.0.10.102 uid 0
    cache expires 552sec mtu 1400

mtu 1400 が経路キャッシュに記録され、有効期限(expires)付きで保持されます。
期限の既定は net.ipv4.route.mtu_expires=600秒で、表示されている 552sec は「600秒からのカウントダウンの残り」です。
以降のこの宛先への通信は、最初から1400以下のパケットで送信されます。

対策② 暫定対応:クライアント側でMTUを縮める

ファイアウォールの修正にすぐ手を付けられない間の暫定として、クライアント側のNICのMTU(または特定宛先へのルートMTU)を実効値まで下げる方法があります。
そのホストの通信は全て回復しますが、ホスト1台ずつの設定になるため、対象が多いと管理しきれません。
あくまで応急処置です。

対策③ 現場の定番:ルータでMSS clamp

台数に依存する②に対して、経路の途中で一括で吸収する方法もあります。
「ICMPを遮断しているファイアウォールは他部署・他社の管轄で、すぐには変えられない」という現場での定番が、VPNルータでの MSS clamp です。

MSS(Maximum Segment Size)は、TCP接続の開始時に「1セグメントで受け取れるデータ量」としてお互いに申告する値です。
MSS clamp は、ルータがこの申告値を通過時に経路MTUへ合わせて書き換える機能です。
両端は「相手は小さいパケットしか受け取れない」と認識するため、最初から実効MTU以下のパケットしか送らなくなり、ICMPに頼る場面自体がなくなります。

検証環境では、ICMPを遮断したまま、Linuxルータに次のルールを入れました(VPN機器であれば同等の設定項目が用意されていることが多いです)。

# firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
success

TCP接続の開始パケット(SYN)だけを対象に、MSSを経路MTUに合わせて書き換える指定です。
結果、ICMPが遮断されたままでも scp が成功するようになりました。

$ scp /root/mtu-test-100m.bin root@10.0.40.103:/tmp/
root@10.0.40.103's password:
mtu-test-100m.bin                             100%  100MB 128.4MB/s   00:00

書き換えの様子は、ルータのサーバ側インターフェースをキャプチャすると確認できます。

# tcpdump -ni ens20 'tcp[tcpflags] & (tcp-syn) != 0' -v -c 4
16:41:13.155235 IP (tos 0x0, ttl 63, id 21250, offset 0, flags [DF], proto TCP (6), length 60)
    10.0.10.102.49710 > 10.0.40.103.ssh: Flags [S], cksum 0x46fb (incorrect -> 0x8233), seq 1506882142, win 64240, options [mss 1360,sackOK,TS val 3830075828 ecr 0,nop,wscale 7], length 0

クライアントは本来 mss 1460 で申告していますが、ルータ通過後は mss 1360(=1400 − TCP/IPヘッダ40)に書き換わっています。

なお、MSS clamp を使うときは2つの性質に注意が必要です。

  • 書き換えの対象は接続開始時のSYNパケットだけです。設定投入前から張られていた(張りかけの)接続は救えず、接続の張り直しから効果が出ます
  • cksum incorrect という表示はNICのチェックサムオフロードによるもので、異常ではありません

注意 ― clamp が救えるのはTCPだけ

このMSS clampには明確な限界があります。
MSS はTCPの仕組みなので、clamp が救えるのはTCPだけです。

clamp を入れて scp が成功するようになった状態で、DFビット付きの大きなpingを打つと:

$ ping -M do -s 1472 -c 3 10.0.40.103
PING 10.0.40.103 (10.0.40.103) 1472(1500) bytes of data.

--- 10.0.40.103 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2057ms

依然として、エラーが出ないまま失敗します。
PMTUD(ICMPの仕組み)は死んだままだからです。

DFビット付きの大きなUDP/ICMPパケットを使う通信(VoIP・VXLANなどのトンネル・一部のDNS/NFS)は、clamp では救えません。
MSS clamp は即効性のある定番ですが対症療法であり、根本はICMP Type3 Code4を通すことです。

対策の線引き

対策直るもの直らないもの位置づけ
① ICMP Type3 Code4 を許可全プロトコル(PMTUDが回復)根本対応。FWの管轄が別だと調整に時間がかかる
② クライアントのMTU縮小そのホストの全通信他のホスト暫定回避。台数が多いと破綻
③ MSS clamp(ルータ)TCPのみ。新規接続からUDP・DF付きICMP現場の定番。即効性あり

5. よくある Q&A

Q1. なぜ ping は通るのに、大容量転送だけ失敗するのですか?
ping のパケットは既定で84バイトしかなく、縮んだ経路MTU(1400)を余裕で下回るためです。
破棄されるのは実効MTUを超えるパケットだけです。
データが小さい通信はパケットも小さく実効MTU以下に収まって通過し、大容量転送はMTU上限サイズのパケットを使うため失敗する、という形で成否が分かれます。

Q2. なぜエラーメッセージが出ないのですか?
本来エラーを伝えるはずのICMP通知(Fragmentation Needed)そのものが、経路上で遮断されているためです。
送信側は「応答がない」ことしか観測できず、TCPは再送を繰り返すだけになります。

Q3. 「小さいファイルは成功する」と聞きましたが、今回は1KBでも失敗しています。なぜですか?
プロトコルによります。
SSH や TLS はハンドシェイクの段階で実効MTUを超えるパケットを使うため、送るデータ量に関係なく接続の確立ごと失敗します(実測の詳細は調査手順の Step 1)。
一方、ハンドシェイクが小さいプロトコルであれば、小さいデータのやりとりは成功します。

Q4. ping -M do -s 1472 の 1472 はどこから来た数字ですか?
ICMPパケットは「データ部 + ICMPヘッダ8バイト + IPヘッダ20バイト」で構成されます。
-s 1472 はデータ部のサイズ指定なので、1472 + 28 = 1500 バイト、つまり標準MTUちょうどのパケットになります。
経路MTUが1400なら、-s 1372(=1400バイト)が通る最大値です。

Q5. PMTUDはデフォルトで有効のはずです。VPNを使うと必ず起きる問題なのですか?
いいえ、必ずは起きません。むしろ多くの環境では起きないまま運用されています。
PMTUDはOSの既定で有効(TCPは常にDFビット付きで送信)ですが、ICMP通知が届くことに依存しています。
さらに、市販のVPN装置の多くはMSS clamp(対策③)を既定で行うため、ICMPが遮断されていても表面化しません。
この障害が起きるのは、ICMPの遮断とclamp無し(自前構築のトンネルなど)が重なったときです。
二重の保険が両方外れて初めて発生するため頻度は高くありませんが、起きたときに症状から原因へたどり着きにくい、という性質があります。

Q6. VPNで実効MTUはどれくらい縮みますか?
方式と設定によります。
代表的なIPsec(トンネルモード)では、外側IPヘッダ20バイト+ESPヘッダ8+IV(暗号の初期値)8〜16+パディング+改ざん検知データ12〜16で、合計50〜80バイト程度を消費します(NAT越え設定ではさらにUDPヘッダ8バイトが加わります)。
このため実効MTUを1400前後に設計するケースが多いですが、決め打ちせず、今回のように ping -M do のサイズを変えながら実測で特定するのが確実です。

Q7. セキュリティのためICMPは塞いでおきたいのですが。
ICMPを全開にする必要はありません。
少なくとも Type3 Code4(Fragmentation Needed)だけは通すのが、PMTUDと両立させる定石です。
echo(ping)の可否とは独立に設定できます。


6. まとめ

「ping は通るのに、大容量の転送だけがエラーも出さずに止まる」は、経路MTU不一致とPMTUD Blackholeを疑う典型パターンです。

調査の流れは次のとおりです。

ping OK + 大容量転送だけ失敗 + 特定経路(VPN)限定
   (SSHがログイン途中で固まる、も同じ原因の症状)
        │
        ▼  経路MTUを疑う
  ping -M do -s 1472 <宛先>
        │
        ├─ 即座に "Frag needed (mtu=X)" → PMTUDは機能している(別の原因を探す)
        │
        ▼  何も返らずタイムアウト = PMTUD Blackhole
  -s を下げて境界を特定(1372が通れば経路MTU=1400)
  tracepath <宛先>(詰まる区間の絞り込み)
  ip route get <宛先>(学習済み経路MTUの確認)
        │
        ▼  対策
  ① 根本:経路FWで ICMP Type3 Code4 を許可(全プロトコル回復)
  ② 暫定:クライアントのMTU縮小(台数が多いと破綻)
  ③ 定番:ルータで MSS clamp(TCP限定・新規接続から)

押さえておくべき初動は、ping -M do -s 1472 <宛先> の一撃です。
即座にエラーが返ればPMTUDは健在、何も返らなければBlackhole
「エラーが出ない」ことを証拠として読めるかどうかが、この障害の切り分けを分けます。