設定で VPN の行をタップし、他の誰かがインストールしたプロファイルを見て、それを追加する呼び出しを探し始める。そんな呼び出しは存在しない。アプリは VpnService を通じて独自のトンネルを開けるが、その一覧のエントリはプラットフォームと、それを作成したクライアントのものだ。境界がどこにあるかを正確に知っていれば、隠しクラスを追いかける一週間を節約できる。

設定画面が表示しているものから始める

VPN 一覧は、システムやベンダーのクライアントがインストールしたプロファイルの登録簿だ。PPTP、L2TP/IPsec、IKEv2、そして OEM アプリや MDM が押し込んだ行が並ぶ。各行はサーバーアドレス、資格情報、そしてあなたのプロセスの外で動くプロトコル実装を抱えている。ユーザーがそこで接続をタップすると、トンネルを立ち上げるのはプラットフォームのクライアントであって、あなたのコードではない。だからその行は再起動後も残り、アプリをアンインストールしても決して消えないのだ。

ユーザーの側から見ると、一覧は API の表面のように見える。VPN の追加がインテント一つ分の距離にあるべき場所のように。そうではない。そこに触れる公開の入り口はナビゲーションのインテントだけで、そこに書き込むものはすべてプラットフォームか特権コンポーネントに属する。

実際の結果として、サポートの文章がユーザーに設定でプロファイルを追加するよう指示しているなら、あなたのアプリはその流れにまったく関与していない。

VpnService が実際に与えてくれるもの

VpnService と VpnService.Builder は、アプリが所有する一つのトンネルを記述する。アドレス、ルート、DNS サーバー、MTU を追加し、addAllowedApplication() や addDisallowedApplication() で通信を絞り、そして establish() が TUN インターフェース用の ParcelFileDescriptor を返す。その時点からアプリは IP パケットを読み、応答を書く。つまりプロトコルは自分で実装するか組み込むものだ。 OpenVPN、WireGuard、独自のトランスポートなど。

その手順のどこにも PPTP、L2TP/IPsec、IKEv2 は登場しない。Builder にはシステムの L2TP クライアントを使えというスイッチはなく、プラットフォームがすでに所有するプロファイルを動かせるコールバックもない。これが本当の境界であり、権限の隙間ではなく設計上の境界だ。

だからアプリは Android で VPN クライアントに十分なれる。ただ、端末に同梱されるクライアントの管理者にはなれない。

同意ダイアログは飾りではない

あなたのトンネルは他のアプリの通信を捕捉できるため、システムはユーザーに明示的な承認を求める。prepare() は起動すべき Intent を返し、承認はあなたのパッケージに結び付き、トンネルが動いている間はステータスバーに常駐の表示が出る。二つ目の VPN が上がると一つ目は無効になる。Android は同時に一つの VPN しか動かさないからだ。

この「一つだけ有効」の規則はテストで人をつまずかせる。社内クライアントが接続したまま自分のトンネルを上げ、コードが壊れたと結論づけるが、実際にはプラットフォームがもう一方のクライアントから切り替えただけだ。承認は後から取り消せるし、同意のアクティビティがない端末では prepare() が null を返すこともある。だから establish() は今も失敗を扱わなければならない。

隠された API と設定のインテント

古い回答は、内蔵クライアントが使う com.android.internal のクラスや AIDL インターフェースを指す。それらは SDK になく、リリース間で予告なく変わり、アプリストアはそのアクセスをリスクと見なす。端末を root 化しても安定はしない。root と隠し API へのアクセスは無関係な問題だからだ。

サポートされるナビゲーション手段は Settings.ACTION_VPN_SETTINGS で、その値は android.net.vpn.SETTINGS だ。起動前に解決すること。一部の OEM ビルドはそのアクティビティを含まないため、解決に失敗したら書面の手順に切り替える。これはユーザーを画面へ導くだけで、何も設定しない。

デバイスオーナーと MDM の経路は、人が普通に意味する例外だ。DevicePolicyManager は常時接続 VPN のパッケージを指定でき、管理対象の構成はそれをサポートするクライアント向けに設定を事前に読み込めるが、それでも公開 SDK が PPTP プロファイルを作っているわけではない。

ユーザー側から判断をたどる

製品が通信を運ぶ必要があるなら、VpnService と本物のプロトコルスタックを計画し、同意の流れと一つの VPN の規則に時間を見込むこと。既存の社内プロファイルにつなぐのが目的なら、あなたの仕事は案内だ。設定画面へのディープリンク、項目の文書化を行い、トンネリングはプラットフォームのクライアントに任せる。アプリが私的なエンドポイントに届くだけでよいなら、独自のトンネルはまったく要らない。

コードを書く前にその判断を書き留めること。三つの道は最初のスプリントを過ぎるとほとんど共通点がない。

始める前の最後の確認。要件は「私たちのアプリが接続する」と言っているのか、「端末が接続する」と言っているのか。前者は VpnService、後者はあなたが作れないプロファイルだ。その一文を正しくするほうが、どんなリファクタリングよりも安い。

関連記事