一つの顧客ネットワークに到達するために二つ目のVPNクライアントを入れるのが、この話の始まり方としてよくあり、両方のクライアントが成功を報告しているのに一つ目のトンネルは暗転します。各クライアントがデフォルトルートを入れ、二つ目の方がより具体的だった、カーネルはいまや一つ目のトンネル自身の外側接続を除くすべてでそちらを辿ります。何も落ちておらず、プロファイルも間違っていません。あるルートが別のルートを置き換えたのであり、解決策はどちらのトンネルがデフォルトを持つべきかを決めることです。

症状:二つ目のクライアントが静かに勝つ

OpenVPNの redirect-gateway def1 は 0.0.0.0/0 を上書きしません。0.0.0.0/1 と 128.0.0.0/1 を追加し、両者でIPv4の全アドレスを覆って、各プレフィックスは長さで単純なデフォルトに勝ります。元のデフォルトはテーブルに残ったまま通常のトラフィックには使われなくなり、VPNサーバーへのホストルートがトンネル自身の外側パケットを捕まえないようにします。両方のクライアントがこうしており、二つ目は単にそのペアを後から入れるだけです。

だから一つ目のトンネルは切断ではなく死んで見えます。インターフェースは上がり、制御チャネルは生きており、アプリケーションのトラフィックはもう届きません。逆の訴えも同じプッシュ欠如から来ており、接続しても何も送らない電話は、redirect-gateway を一度も告知していないサーバーと話していることが多く、端末は自前のデフォルトを保ってプールにしか届きません。

二つ目のクライアントが接続する前後でルーティングのスナップショットを取ります。テーブルに現れる二本の半端なルートが変化のすべてで、両方の訴えを説明します。症状が出るためにテーブルの他の何かが動く必要はありません。だから二つ目のクライアントを外すと一つ目が即座に戻るのです。

どのトンネルが実際に勝つかを読む

クライアントの起動順やトレイのアイコンから勝者を推測しないでください。両方のプライベートネットワークの外にある宛先にどのルートを使うかをカーネルに尋ね、返ってきたインターフェースを読みます。

ip route show
ip route get 198.51.100.24
route print -4
Find-NetRoute -RemoteIPAddress 198.51.100.24

最初の二つがLinuxの答えで、最後の二つがWindowsの相当品です。両方のトンネルが上がっている間に実行し、次に片方を切断してもう一度実行します。二つの出力の差が、修正を選ぶ前に必要な事実であり、どちらかのクライアントのログを読むより短時間で済みます。

結果をインターフェース名と一致したプレフィックスも含めて記録します。二つ目のVPNが一つ目を壊した、というチケットは症状の説明であって、誰かが行動できるルーティングの記述ではありません。この検索がそれを、次のエンジニアが一つのコマンドで再現できる事実に変えます。

Windows:一つのプロファイルにデフォルトを持たせない

Windowsは保存済みプロファイルを保持し、内蔵クライアントは一方を他方に依存させられるので、二つ目がダイヤルすると一つ目が自動で立ち上がります。最後に接続したプロファイルがデフォルトを保持しがちなので、実質的な持ち主は最初ではなく最後のダイヤルです。各プロファイルに独自のプッシュルートを与え、デフォルトを持つべきでない方でスプリットトンネリングを有効にします。

Set-VpnConnection -Name "Contoso" -SplitTunneling $true
rasdial Contoso

スプリットトンネリングはこれを変え、サーバーがプッシュしたプレフィックスだけがテーブルに入るため、デフォルトは一つ目のトンネルに残ります。両方のサーバーがデフォルトルートをプッシュすれば設計はやはり破綻し、その場合は最後の接続が勝ってクライアント側の設定では変えられません。サーバー側のプッシュを直すか、持ち主を一つに認めます。

アプリ起動型プロファイルは別の機能で知っておく価値があり、Windowsのプロファイルは指定した実行ファイルの起動時に開始でき、トラフィックフィルターが何を運ぶかを決めます。これはファイアウォール規則ではなくプロファイル設定で、デフォルトルートの衝突とは別の問題を解きます。

Linux:二つ目のトンネルをメインテーブルの外に置く

一台のマシンで openvpn --config を二つ動かすと両方のクライアントが同じテーブルに入り、三つの選択肢が隔離を強めながら分けます。最も軽いのは二つ目のクライアントにテーブルを一切編集させないことで、route-nopull と明示的なルート、または意図したものだけを入れる route-up スクリプトを使います。すると二つ目のトンネルは指定したプレフィックスだけを運び、それ以外は運びません。すると後から発見するのではなく、どのプレフィックスをどこへ送るかを自分で決められます。

ポリシールーティングは中間の選択肢です。規則が一つの送信元アドレスやファイアウォールマーク用に別のテーブルを選び、そのテーブルが二つ目のトンネルのルートを運びます。ここでの落とし穴は逆経路フィルタリングで、テーブルが選ぶのと別のインターフェースに届いた応答は捨てられるため、トンネルが壊れたと結論する前に rp_filter の設定を確認します。

ip rule add from 10.44.7.0/24 table 100
ip route add default dev tun1 table 100
sysctl net.ipv4.conf.all.rp_filter

ネットワーク名前空間は最も重く、最もきれいな選択肢です。二つ目のクライアントを ip netns の中で起動し、vethペアを与え、使うにはその名前空間に入ります。各名前空間が一つのテーブルを共有するのではなく独自のテーブルを持てば干渉は消え、両方のトンネルを何週間も上げておく必要があるならその追加設定だけの価値があります。

アプリケーション単位、そして連結は別問題として

二つ目のトンネルを一つのプログラムだけに使わせたいことがあります。Linuxではユーザー、cgroup、ポート単位のポリシールーティングでそれが実現でき、デフォルトに一切触れずに一つの宛先をインターフェースに固定できます。ソケットを一つのインターフェースに固定したアプリケーションはルーティングテーブルをまったく参照しないので、ランチャーは自前の設定でアドレスが選ばれるまで物理アダプターを使い続けられます。

連結は二つのトンネルを並べて動かすのとは別の操作です。A を B の上に連結するとは、内側サーバーのアドレスが外側トンネルの内側から到達可能でなければならないということです。外側のクライアントは内側クライアントの暗号化パケットを漏えいと誤解してはいけません。仮想マシン内の一クライアントは乱暴ですが確実な版で、一つのホストに両方を置くには上記のルーティング作業に加えて、内側トンネルに決して入らない外側サーバーへの経路が必要です。

デフォルトの持ち主、各トンネルが運ぶプレフィックス一覧、DNSの経路を書き留めます。この三つの事実が、次のインシデントを調査ではなく検索に変えます。

関連記事