症状は一つの形に従います。ホストが動いていたのに、クライアントとして VPN に接続した途端、LAN クライアントからは届くのに応答が消えます。ホスト上でのキャプチャを見ると、応答は LAN インターフェイスではなくトンネルデバイスから出ています。トンネル自体は壊れておらず、直すべきは経路であって再接続ではありません。以下の四つの確認を順に行い、最初に失敗したところで止めてください。

応答は新しいパケットであり、経路の逆走ではない

応答が要求の通った道をそのまま戻るという考えは捨てたほうが役に立ちます。ルーティングは送信側ホストで、パケットごと・方向ごとに決まります。トンネル内の 10.64.0.23 から来た要求は戻り経路を作りません。ホストが応答するとき、その応答は新しいパケットとしてテーブルと照合され、勝った経路から出ていきます。

LAN も提供するマシンでフルトンネルのクライアントを使うと、これほど混乱する障害になるのはそのためです。クライアントはデフォルトルート、あるいは一部のクライアントが好む二本の半デフォルトルートを入れ、LAN クライアントへの応答がトンネルに一致してしまいます。応答はプロバイダへ向かい、プロバイダには転送する理由がないので、ホストは動いていてローカルには応答しているのに LAN クライアントにはタイムアウトが見えます。キャプチャを見れば明らかで、ルーティングテーブルが説明になります。

残りを読む間、二つのサブネットを頭に置いてください。一つはリモートクライアントが来る VPN アドレスプールで、この例では 10.64.0.0/24 です。もう一つは動き続けるべき LAN で、172.16.24.0/24、ルーターは 172.16.24.1 にあります。不具合は、両者の間の通信がデフォルトルートで決まってしまうことです。

キャプチャと照会で証明する

何かを変える前に、応答がどのデバイスを使うかを確認します。照会はキャプチャより速く、パケットを組み立てるときにカーネルが答えるのとまったく同じ問いに答えます。

ip route get 10.64.0.23 from 172.16.24.10
tcpdump -ni any -c 6 icmp

応答は二つの部分に分けて読みます。もし dev tun0 と出れば、ホストは応答をカプセル化してプロバイダに渡し、LAN クライアントはそれを見ることはありません。もし dev eth0 と出るのにクライアントがまだタイムアウトするなら、経路はすでに正しく、パケットは後で捨てられています。これは別の問題で、直し方も別です。

同じ手順でのキャプチャは、ping では解けない疑問を決着させます。LAN クライアントにエコー応答が届くことは、どこかの経路が機能している証拠であって、どのインターフェイスが運んだかは示しません。だからキャプチャはホスト自身で行い、フレームがどこから出ていくかを見ます。同じサブネットのプリンターは応答し続けます。直接経路のほうがトンネルのデフォルトより具体的だからです。転送ポートも他のフローとまったく同じように失敗します。インターフェイスを選ぶのはテーブルであって、接続ではないからです。

トンネルを上回る最も狭い経路を入れる

決めるのはメトリックではなくプレフィックス長なので、LAN ルーター経由のクライアントプール向け経路は、VPN クライアントが入れるどんなデフォルトルートにも勝ちます。メトリックの手術は不要で、デフォルトルートはクライアントが置いた場所にそのまま残せます。

ip route replace 10.64.0.0/24 via 172.16.24.1 dev eth0
ip route get 10.64.0.23 from 172.16.24.10

二つ目の add ではなく replace を使ってください。再接続したクライアントは自分の項目を入れているので、二度目の add は経路が既に存在するという権限問題のように読めるエラーで失敗します。Windows ホストでは同等なのは route add 10.64.0.0 mask 255.255.255.0 172.16.24.1 -p で、-p は再起動をまたいで項目を保ちます。トンネルが再び上がるとクライアントがテーブルを作り直すことがあるからです。現在の Windows ホストでサポートされる同等は New-NetRoute -DestinationPrefix 10.64.0.0/24 -NextHop 172.16.24.1 -InterfaceAlias Ethernet -PolicyStore PersistentStore で、古い route コマンドは旧形式として残されています。

一つの細部が、これが正しいのか単に説得力があるだけなのかを決めます。次のホップは、クライアントへの戻り道を実際に持つルーターでなければなりません。トンネル経由でしか届かないアドレスへ経路を向けると、きれいな失敗ではなく断続的な損失として現れるループを作ります。行を確定する前に、次のホップが LAN サブネット上にあることを確認してください。

再接続の後も応答を保つ

手で追加した経路は、良くても次の再接続までしか残りません。クライアントプールが動的なら、サーバー設定で固定し、経路に入れたプレフィックスがクライアントの受け取るプレフィックスのままになるようにします。そうでなければ項目は空の範囲を指し、症状は何週間も後に明白な原因もなく戻ってきます。経路をクライアント自身の up スクリプトに書くのがもう一つの確実な方法です。クライアントがテーブルを書き直した後に走るからです。

ステートフルなファイアウォールは正しい経路でも破ることがあります。キャプチャが応答の出ていく先を eth0 と示しているのにクライアントがまだタイムアウトするなら、ホストが送信前にパケットを捨てていないか確認してください。厳格な逆経路フィルタリングと、net.ipv4.conf.all.rp_filter を 1 に設定すると、到着したインターフェイスから戻る経路を持たない送信元のパケットは捨てられます。これはまさに今作って直した非対称のケースで、conntrack の状態も転送ポートについて同じことを語ります。

最後の確認は人が飛ばすものです。トンネルが落ちて再び上がった後に、もう一度照会してください。四つの値が書き留めたものと一致するはずです。宛先、次のホップ、デバイス、選ばれた送信元です。どれかが動いていれば、クライアントがテーブルを書き直しており、直す場所はそのフックであってシェルの履歴ではありません。

関連記事