あらゆるプラットフォームでコードからVPNトンネルの稼働を検出する方法
トンネルは存在するのか、この宛先はトンネルを通るのか、それとも遠端が応答するのか。それは三つの異なる確認と三つの異なる信号です。
「VPNはつながっているか?」という問いは、実は一つの文に三つの問いが詰まったものです。コードが必要とするのはそのどれなのかが最初の判断であり、ほとんどの実装が飛ばしてしまう点でもあります。この端末にトンネルはあるのか、この宛先への通信はトンネルから出ていくのか、それとも遠端が期待どおりの身元で応答するのか。性質を選び、次にそれを測る信号を選び、最後に実際にどれを測ったのかをログに書きます。
三つの性質、三つの信号
それぞれ一行に圧縮すると、三つの問いには三つの異なる答えがあります。下の表は要約ではなく、判断そのものです。
| Property | Signal that answers it | What it still leaves open |
|---|---|---|
| Existence | The platform's own connection state: RAS on Windows, NetworkManager on D-Bus on Linux, scutil on macOS | Tunnels of a type the platform does not manage, which includes most third-party clients |
| Path | A route lookup for the one destination, or a scoped proxy or interface key that names the tunnel | Whether the far end is the peer you trust |
| Identity | An authenticated response from the far end, or a source-address policy applied on the server | Which local interface carried the packets |
最後の行をゆっくり読んでください。応答するプライベートホストは、何かが答えたことを示すだけで、答えがトンネルを通って来たことは示さず、向こう側が誰かについても何も言いません。必要な性質が経路なら、経路を測ってください。
存在: プラットフォーム自身の状態
Windowsはネイティブプロファイルについて、Remote Access Service API を通じてこれに答えます。RasEnumConnections は接続されているものを列挙し、RasGetConnectStatus は一つのエントリの状態を報告し、RasGetLinkStatistics は速度、バイトカウンタ、接続の経過時間を加えます。限界こそが要点です:OpenVPN、WireGuard、ベンダーのクライアントはRAS接続を登録しないため、その種の健全なトンネルは空のリストを示し、素朴なコードはそれを切断と報告します。
Linuxのデスクトップは同じ性質をD-Bus上のNetworkManagerを通じて公開し、VPN接続オブジェクトのPropertiesChangedが確認をイベントに変え、Stateプロパティがリンクが接続状態に達したときを報告します。シェルからは nmcli connection show --active が同じ情報を読み取れ、デバッグ中に欲しいのはこれです。macOSはそのリストをSystem Configurationフレームワークに保持し、scutil がそのコマンドライン表示です。三つともプラットフォームが管理する接続を説明しており、管理していないトンネルについては三つとも沈黙します。
経路: パケットが実際にどこへ行くか
名前の照合は誰もが最初に手を伸ばす近道です:インターフェースを列挙し、文字列をtun、tap、ppp、ipsecと照合し、一致を証拠とみなします。それはヒューリスティックであり、インターフェースの集合は何かが通っていてもいなくても同じに見えることがあります。古いiOSのビルドはこの方法で答え、後のビルドがやめたのは、見えるインターフェースが二つの状態を区別しなくなったからです。
そこで取って代わったのがシステムのプロキシ辞書です。VPNが接続されている間は、プロキシ設定の呼び出しがトンネル関連のインターフェースに限定されたキーを返し、何も接続されていなければ辞書は空で返ります。これは聞こえるより弱い主張で、トンネルが存在するというシステムの信念を記録しているだけで、どの宛先がそれを使うかは記録していません。
Androidにはこの罠の独自版があります:vpn.connectivityのブロードキャストは、ビルドによってはマニフェストで宣言されたレシーバに届き、別のビルドでは実行時に登録されたレシーバに届きます。最善努力として扱い、その背後に直接の検査を残してください。
宛先が重要なときは、アダプタの一覧ではなくルーティングテーブルに問い合わせてください。デフォルトゲートウェイのヒューリスティックは、複数ネットワークのマシンや、独自のゲートウェイをインストールするトンネルでは破綻します。
身元: 遠端を調べる
数秒ごとに遠端へICMPエコーを送るループは、この種の確認では最も安価であり、有益な方向に失敗します。プライベートネットワークの内側にしか存在しないアドレスに向けてください、なぜなら公開アドレスは機能している上り回線があればどこでも応答し、何も証明しないからです。ICMPがフィルタされている場所では、サービスポートへの短いTCP接続が同じ役割を果たします。
サーバー側の確認に使える信号はちょうど一つ、接続の送信元アドレスです。範囲の台帳を維持するのは運用の手間であり、同じブロックを使う別のネットワークからトンネルを切り分けることはできません。
粗いポリシーはそれで済みます。身元は済まないので、パケットがどこから来たように見えるかを信じるのではなく、クライアントを認証してください。
判定ではなく分類を記録する
障害の分類をログで分けてください:インターフェースがまったくない、インターフェースはあるが宛先に到達できない、宛先には到達できるが認証が拒否された。これは三つの異なる持ち主であり、それぞれが別の調査を開きます。
それらを一つの「VPN障害」にまとめると、直せないチームに報告が飛びます。有用な出力は分類であり、判定はそれを誤りにしてしまうものです。