iPhone は VPN につながっている? iOS アプリが実際に見えるもの
iOS SDK に「VPN 中」を示す単一のフラグはありません。アプリが観測できるのは自身の構成、状態通知、限定的なプロキシのスナップショットだけです。
iOS アプリに「この iPhone は VPN につながっているか」と尋ねても、正直な答えは「あなたが意図する意味では分からない」です。SDK は「デバイスがトンネル内にある」というフラグを公開しておらず、それに近いことを示す可能性のある非公開インターフェースは出荷するアプリには閉ざされています。観測できるのはアプリ自身の構成、それに紐づく状態、そしてネットワークスタックからの間接的な手がかりが一つか二つです。それぞれが別の問いに答えるものであり、壊れたチェックの多くは、そのどれか一つを答えのすべてとみなすことから生まれます。
単一の VPN フラグという神話
「SDK のどこかに、デバイスがトンネル内にあるかを報告するプロパティがあるはずだ。到達性フラグがホストの応答を報告するように」という信念があります。そのような公開プロパティは存在しません。仮にあったとしても、「トンネルが存在する」と「自分の通信がそれを使っている」は別の主張であり、ほとんどのアプリにとって重要なのは後者だけです。
その隙間こそが、一行で使える VPN 検出器をうたうライブラリが、インターフェース名やプロキシのキーのような脆いものを読み、それを判定として提示してしまう理由です。
nil を信じる前に設定を読み込む
共有マネージャのインスタンスは要求した時点で存在しますが、そのプロトコルプロパティは読み込みコールバックが完了するまで nil のままです。つまり nil のプロトコルは、「まだ読み込まれていない」を「インストールされていない」と同じくらいの頻度で意味します。読み取りは完了ブロックの中で行い、そのうえで判断してください。存在するプロトコルオブジェクトで有効化フラグが立っているものが「インストール済み」の姿です。
二つ目の手がかりは接続オブジェクトにあります。このアプリに構成が一つもインストールされていないときにシステムが報告する「無効」状態です。読み込み完了後の nil プロトコルと無効状態が揃っていれば、それは本物の不在であり、タイミングの競合ではありません。
接続の変化は通知として届く
load と save に渡すハンドラが報告するのは構成に起きたことであり、トンネルが何をしているかではありません。接続時にどちらかが発火するのを待つのは、決して解決しない競合です。代わりに状態変化の通知を監視し、届いた時点で接続オブジェクトの現在値を読んでください。
構成がインストールされると、接続状態は短い順序をたどります。切断、接続中、接続済み、再確認中、切断中です。「接続済み」はトンネルを表すもので、その背後のサーバーを表すものではないため、通信が流れている証拠にはなりません。On-demand はアプリが一時停止中でもトンネルを立ち上げることがあり、その場合、更新するものが何もないうちに通知が届きます。
限定的なプロキシのスナップショットは手がかりであって判定ではない
CFNetwork は現在のシステムプロキシ設定をアプリに渡すことがあり、その結果の中にはインターフェース名をキーにした限定的な辞書があります。tun、utun、ppp、ipsec、tap のいずれかで始まる名前が見えれば、トンネルが存在する手がかりです。ただしそれはこのアプリが見える範囲のスナップショットであり、パケットがどこへ行くかの表明ではありません。
プロバイダによって命名は異なるため、接頭辞がないことは何も証明せず、インターフェースが存在しても通信を運んでいないことがあります。到達性はさらに弱く、プライベートアドレスへの成功した要求は、その瞬間にその要求の経路が機能したことを示すだけで、VPN がインストールされていることを示しません。
シミュレータは Mac を借り、他のアプリは見えないまま
シミュレータには独自のネットワークスタックがなく、行う接続はすべて Mac のものです。したがってホストの Mac で動いている VPN は、シミュレートされたアプリにとってすでに有効であり、シミュレータ自身の設定には何も表示されません。シミュレータの起動後にトンネルが立ち上がった場合は、アプリをデバッグするより再起動するほうがたいてい速く済みます。標準外のポートを使うホストなら、そのポートを URL に入れるか、socat TCP-LISTEN:12345,fork TCP:host:12345 のようなローカル転送が必要です。
公開 API は他のアプリや構成プロファイルに属する VPN プロファイルを列挙しません。既知の一つのアプリについては、canOpenURL を使った URL スキームの検査が移植可能な定石であり、iOS 9 以降は問い合わせるスキームを Info.plist で宣言する必要があります。宣言を省くと、その呼び出しは false を返します。OpenVPN Connect は openvpn:// に応答するので、良い例であり、汎用の検出器としては貧弱な例です。
三つの観測を組み合わせ、それが何を意味するかを述べる
実務では三つの結果を重ねます。読み込まれて有効になっている構成、無効ではない接続状態、そしてトンネル経由でしか届かないアドレスへの要求です。どれか一つでは誤解を招きますが、三つ揃えば、利用者が想定する事柄ではなく、アプリが知り得る事柄を示します。
結果は診断情報に三行で書き、一つの真偽値にはしないでください。利用者が「VPN は切れている」と言い、あなたのアプリがそれに同意しないとき、三行はどの観測が食い違ったかを示します。それが、行動につなげられるサポートチケットと「こちらでは動きます」で終わるチケットの違いです。