Android、iOS、Windows で ping を使わずに VPN の状態を確認する方法
ping が証明するのは 1 台のホストが応答したことだけで、トンネルが稼働していることではありません。Android、iOS、Windows の状態 API に問い合わせ、アプリが実際に必要とする経路を検証します。
192.0.2.10 に ping を送っても分かるのは、あるアドレスが ICMP エコーに応答したという一点だけです。トンネルのないネットワークでも成功し得ます。そのアドレスがローカルサブネット上にあるかもしれないからです。健全なトンネルがあっても失敗し得ます。サーバーが HTTPS を提供しながら ICMP を落とすからです。スクリプトに必要なのは、プラットフォームの状態照会と、アプリが使う宛先への経路テストです。この二つは別々のフィールドとして扱い、決して一つの真偽値にまとめないでください。
エコー応答が実際に証明するもの
ICMP は到達性のプロトコルであり、インターフェースやトンネルという概念を持ちません。応答が届くと、カーネルはあるホストが応答したと報告しますが、要求を運んだ経路や応答を運んだ経路は報告しません。フィルタリングは信号をさらに不明瞭にします。エコー要求を遮断するファイアウォールは、その背後のポートについて何も語らないからです。また分割トンネルのプロファイルは、他のすべての宛先が機能していても、テストしたアドレスを意図的に除外できます。
本当に知りたいのは、アプリが通る経路で特定のサービスが使えるかどうかです。アプリが呼ぶホストとポートを選び、アプリケーション層での応答を要求してください。
到達性とトンネルの状態は別の事実であり、片方が真でもう片方が偽であり得ます。両者を分けておけば、障害がどちらの責任か分かります。一つにまとめると、誰も振り分けられないチケットが生まれます。
Android には ping ではなくトランスポートを尋ねる
Android は ConnectivityManager を通じて状態の問いに直接答えます。アクティブなネットワークの機能には VPN トランスポートのフラグが含まれ、トラフィックが VPN インターフェース経由でルーティングされるときに立ちます。この呼び出しはローカルで安価であり、遠隔ホストが応答するかどうかとは無関係です。
この Java 版は、アクティブなネットワークがないときに例外を投げずに false を返します。アクティブなネットワークを読むため、ユーザーが設定で選んだプロファイルを反映します。
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkCapabilities caps = cm.getNetworkCapabilities(cm.getActiveNetwork());
boolean tunnel = caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
変更通知には、タイマーでポーリングする代わりに NetworkCallback を登録します。これを VpnService.prepare と混同しないでください。前者はユーザーが VPN の許可を与えたかだけを報告し、パケットが流れているかは報告しません。取り消しは VpnService.onRevoke で届きますが、設定でプロファイルを忘れるとブロードキャストは一切発生しません。
iOS: インターフェース名は経験則であり、契約ではない
多くの iOS スニペットはプロキシ設定を調べ、__SCOPED__ 辞書を見つけ、tap、tun、utun2、ppp、ipsec といったインターフェースキーを探します。この方法は命名の慣習であり、文書化された保証ではないため、インターフェース名を変える OS 更新で壊れる可能性があります。また、トンネルより長く生き残る古いエントリに対して true を返しがちです。
アプリが構成を所有している場合は、代わりに NEVPNManager が実際の状態を与えます。アプリが開始したトンネルを記述しますが、アプリ単位の VPN では、他のアプリがそのトンネルの完全に外側にいることがあります。
状態を読む前に設定を再読み込みしてください。トンネルが上がり下がりするたびに値が変わるからです。
let manager = NEVPNManager.shared()
manager.loadFromPreferences { error in
let up = manager.connection.status == .connected
print(up)
}
それでもプラットフォームの状態フラグは、トラフィックがピアに届くことを証明しません。その隙間を埋めるのが経路テストであり、だからこそサポートには一つの真偽値では足りません。
Windows のプロファイルは実際の状態フィールドを公開する
Windows は VPN プロファイルをアダプターとは別に追跡し、Get-VpnConnection は認識している各プロファイルの ConnectionStatus を公開します。Windows クライアントやプロビジョニングパッケージで作成したプロファイルはここに現れます。このコマンドレットは汎用のトンネル検出器ではありません。
名前付きプロファイルのフィールドを読み、プロファイルが全ユーザーに属する場合は -AllUserConnection を加えます。
Get-VpnConnection -Name "Fabrikam" | Select-Object Name, ConnectionStatus, ServerAddress
サードパーティのクライアントはトンネルが稼働していてもこのコマンドレットから見えないことが多いため、空の結果は不明を意味し、切断ではありません。アダプターの一覧も同じくらい誤解を招きます。ベンダーのアダプターはアイドル状態でも残ることがあり、セッション中だけ存在するアダプターもあります。Windows が所有するプロファイルなら、rasdial が分岐に使える終了コードを報告します。いずれもローカルの認識を述べるもので、ピアへの到達性ではありません。
状態と経路: 二段階の確認
接続済みのプロファイルは、パケットがどこへ行くかを何も語りません。分割トンネルは、重要な宛先を除外したままトンネルを維持できます。常時接続プロファイルは死んだサーバーでも接続されたままで、キルスイッチはトンネルが落ちると意図的に通信を遮断します。
両方の層をテストします。VPN トランスポートまたはプロファイルが有効であることを確認し、次に宛先が解決され、その経路で応答することを確認します。Linux では、カーネルにどのインターフェースを使うか尋ねます。
ip route get 192.0.2.10
resolvectl query intranet.example.test
クライアントを制御できない場合は、両方の結果を記録し、パターンに層を指し示させます。接続済みなのに経路テストが失敗する場合は、分割トンネル、DNS、ポリシーのいずれかを示唆します。切断済みで経路テストが通る場合は、別の経路が通信を運んでいます。
順番に実行できるテスト
ネットワークの状態を変える前に、まずプラットフォームの状態を記録します。ホスト名を解決し、アドレスファミリーを記録します。AAAA レコードは A レコードと違う経路を取り得るからです。サービスのポートを開き、単なるハンドシェイクではなくアプリケーションの応答を要求します。各観測はそれぞれ別のログフィールドに保ちます。
dig と curl のある Linux マシンなら、三つのコマンドで名前解決、経路、サービス自体を網羅できます。トンネルを上げた状態と下げた状態で実行してください。
dig +short intranet.example.test
ip route get 198.51.100.20
curl -sS -o /dev/null -w "%{http_code}" --max-time 8 https://intranet.example.test/health
四つの独立したフィールドは一つの真偽値に勝ります。状態、DNS、経路、ポリシーは独立して失敗し、それぞれ別のチームに届くからです。端末での ping は手作業の健全性確認としては今も有効です。自動化にとっては接続性の貧弱な定義であり、そこではプラットフォーム API と経路テストが本当の問いに答えます。