ツール
三つのアプリが交差しないトンネルで別々の出口につながり、VPN Instance ごとの独立経路を表す概念図。リアルタイムの状態図ではありません。

エンジニアリング · 研究ノート

私が作りたい VPN Manager:アプリごとに別々の VPN を使う

VPN Manager を開発したい。アプリやサービスごとに独立した VPN Instance、Linux ネイティブの隔離、障害時の遮断、Provider のブートストラップ、トラフィック監視を設計する。

VPN MANAGER / 開発ノート

最初に作りたかったもの

ホストの元のネットワークは変えない。
指定したアプリは指定した出口だけを使う。

最初に VPN Manager を作りたいと思ったのは、一般的な VPN クライアントの多くが、コンピューター全体をまとめて切り替えるものだと感じたからです。
でも、私が必要としているのはそれではありません。
どの App がどの VPN を使うかを指定し、管理対象外のプログラムにはホストの元のネットワークをそのまま使わせたいのです。

例えば、ブラウザー、ダウンロード用プログラム、そのほかの App を同時に開いているとします。
ブラウザーの担当は Instance A、ダウンロード用プログラムの担当は Instance B、そのほかは元のネットワークを使います。
A と B は、それぞれ独自の WireGuard identity、DNS、Gateway、接続リソース、ライフサイクルを持ちます。

A が切断されたら、ブラウザーはそのままネットワークにつながらなくなる方がよいと思っています。
自動的に B に移ってはいけませんし、ISP の元の出口に戻ることも許しません。

ブラウザー管理対象アプリケーション
Instance AID A/DNS A
WireGuard ピア A独立した VPN 出口
ダウンロード用プログラム管理対象アプリケーション
Instance BID B/DNS B
WireGuard ピア B別の独立した出口
そのほかの App など管理対象外アプリケーション
ホストの元のネットワーク元のルーティングを維持
元のネットワークVPN Manager は変更しない

A が切断された場合:ブラウザーはネットワークにつながらなくなります。
Instance B と管理対象外のプログラムは通常どおり動作し、一緒に切り替える必要はありません。

利用例|同じホスト上の 3 つの異なる経路(小さい画面では左右にスクロールできます)。これは想定するアーキテクチャの動作を示す図であり、リアルタイムの接続画面ではありません。

異なる VPN プロバイダーのアカウント、Profile、Gateway も、同じシステムで管理できるようにしたいです。
指定した App や Service は、できれば OS のネットワーク層で直接隔離したいです。Docker を使わず、コンピューター全体の default route も変更しません。

トラフィック情報も、作りたい機能の一つです。
各 Instance がどれだけ通信したのか、どの Gateway が混雑しているのか、DNS にどれだけ時間がかかったのか、どのプロセスが最も CPU やメモリーを使っているのかを見たいです。
Connected と表示されるだけでは、あまり意味がないと思っています。

似たことを実現している人はすでにいますし、netns、Proxy、firewall を自分で組み合わせて必要な環境を作っている人も少なくありません。
ただ私は、複数 VPN の管理、App の隔離、障害復旧、監視を一つにまとめ、毎回いろいろなものを自分でつなぎ合わせずに済むようにしたいのです。

まず、システム全体のアーキテクチャから始めます。

現在の操作画面

VPN Manager の概要:2 本の VPN 接続、それぞれのローカル Proxy Port、ダウンロード・アップロード統計、トラフィック経路図
画面 01VPN Manager の概要:2 本の接続、それぞれのプロキシポート、トラフィック経路。実際のインターフェースのスクリーンショット・2026-11-01 (左右にスクロールできます)
VPN Manager の接続ページ:オランダと香港の 2 本の WireGuard 接続。接続状態、プロキシポート、リアルタイムのトラフィック、リソース監視を含みます
画面 02接続ページ:各接続の状態、リアルタイムのトラフィック、リソース情報。実際のインターフェースのスクリーンショット・2026-11-01 (左右にスクロールできます)

01システム全体の骨格

システム全体のアーキテクチャ VPN Controller は意図だけを渡し、Instance Manager が Instance を独立して管理します。永続データはすべて Store helper を経由して Store に渡り、データフローは管理プレーンを通りません。 図 1|VPN Manager 全体:制御、データ、永続化、観測 Web リソースサービス React 静的リソースを同一オリジンで配信 React ユーザーインターフェース コア操作/プロバイダー/性能パネル 静的リソース HTTP/SSE VPN プロバイダーマネージャー Mullvad/AIRVPN アダプター プロバイダーごとの出口 IP 検査 候補の設定と状態 VPN コントローラー VPN Core/Web API VPN プラットフォーム制御コーディネーター Profile 検証/Store helper 操作意図の調整だけを担当 セキュリティポリシーマネージャー DNS プロファイル検証 ファイアウォールポリシー/自動遮断 IP ブロックリストポリシー プロファイル ポリシー 性能マネージャー 分析/ベンチマークセッション 比較/性能レポート Instance マネージャー ライフサイクルの唯一の管理権/外側の監視 世代、リソースリース、自動復旧 プラットフォームマネージャー Linux/macOS システムアダプター OS 権限/サンドボックス/リソース制御 型付きライフサイクル意図 明示的に許可された性能テスト テスト用リース OS への要求 メトリクスサービス メトリクスコレクター メトリクス圧縮モジュール VPN Instance A … N それぞれが完全な内部サービスを所有 独立した GotaTun/DNS/Gateway App の通信は Controller を通らない Store helper 共通の型安全な永続化クライアント 設定/起動/開通/停止 ファイルディスクリプター(FD) 監視メトリクス 上限のあるデータ照会 メトリクスとレポートの永続化はすべて Store helper 経由 Store ストレージサービス 唯一の SQLite アクセス主体/内部モジュール SQLite 制御 DB+メトリクス DB 管理対象 App Gateway クライアント VPN ピア 暗号化済み WireGuard UDP 青:制御 緑:データ 黄:OS リソース 紫:観測 永続化はすべて Store helper 経由。秘密情報には独立した非公開の受け渡しチャネルを使います。
図 1VPN Manager の全体アーキテクチャ制御プレーン、データプレーン、観測、永続化を分離。(左右にスクロールすると図全体を確認できます)

全体の制御経路は、まず次のように計画しています:

React UI→VPN Web API / VPN Core→Instance Manager→Instance Controller

ここでは、 Instance Manager を独立プロセスにします。
すべての Instance の起動、停止、generation、リソースリース、復旧を担当させます。
Controller は UI/API の操作意図だけを受け取り、App の TCP/UDP データは転送しません。

OS 権限を扱う部分の担当は Platform Manager。
network namespace、process sandbox、FD、firewall policy、macOS の capture/deny のいずれも、これが担当します。
ほかのコンポーネントが、便利だからという理由でシステムのネットワーク設定を直接変更することは避けたいです。

Store が状態を保存し、Metrics がデータを収集し、Performance Manager が分析と許可されたテストを処理します。
これらが App のパケット経路を妨げてはいけません。
例えば Metrics やデータベースへの書き込みが遅くなっても、そのために送信中のパケットを待たせてはいけません。新たな状態変更は、元の閉鎖ルールに従って処理します。

表 1|実行境界と主な責任
実行境界 主な責任
vpn-controllerd React の静的アセット、VPN Web API、VPN Core、および Provider/Security/Metrics/Performance の制御調整を提供。
vpn-instance-managerd Instance をまたぐライフサイクルの唯一の owner。desired state、generation、identity/resource lease、外側の Watchdog、復旧時の照合を管理。
vpn-platformd 高権限 OS 操作を独占。Linux/macOS Adapter、sandbox、FD/policy 作成、spawn、seal、安全性の証拠収集を実行。
vpn-stored SQLite の唯一の owner。管理対象は control.sqlite、metrics.sqlite、secret、revision、operation journal、バックアップ。
vpn-instance × N VPN ごとの独立 worker。専用の GotaTun、DNS、Gateway、Port、Metrics、内側の Watchdog を所有。
vpn-provider-worker Provider job ごとに API 操作、route-bound IP preflight、制限付き connector を実行し、Instance の作成や起動・停止を独断で行わない。
macOS system extension(有効な場合) 管理対象 App の flow capture、deny、bridge を処理し、ネイティブプラットフォームの能力に応じた安全境界を提供。

コンポーネントの所属全体

まず、誰が管理し、誰が操作権限を持つかに従ってコンポーネントを分類します。
単に箱をたくさん描くだけでは役に立ちません。重要なのは、あるプロセスが落ちたときに誰に影響するのか、誰が再起動できるのかです。

コンポーネント所属の全体図 正式な 55 コンポーネントをすべて掲載。同名の Process Manager、Instance Watchdog は親で区別します。インデントは所属を表し、各 helper が daemon であることを意味しません。 コンポーネントの所属|実行境界と権限上の責任で分類 React ユーザーインターフェース(UI - React) · コア UI(Core UI) · 性能パネル(Performance Panel) · プロバイダー管理パネル VPN コントローラー(VPN Controller) · VPN コア(VPN Core) · VPN Web API · VPN プラットフォーム制御コーディネーター · VPN プロファイル検証器 · Store helper(ストレージ接続) プラットフォームマネージャー(Platform Manager) · Linux システムアダプター · プロセスマネージャー · プロセスサンドボックス · ネットワークリソース制御 · macOS システムアダプター · プロセスマネージャー · プロセスサンドボックス · ネットワークリソース制御 プロバイダーマネージャー(VPN Provide Manager) · Mullvad プロバイダーアダプター · Mullvad 出口 IP 検査器 · AIRVPN プロバイダーアダプター · AIRVPN 出口 IP 検査器 セキュリティマネージャー(Security Manager) · DNS プロファイル検証器 · ファイアウォールプロファイル · 自動遮断検知器 メトリクスサービス(Metrics Service) · メトリクスコレクター · メトリクス圧縮モジュール 性能マネージャー(Performance Manager) ストレージサービス(Store) · 設定データ補助モジュール · 秘密情報補助モジュール · セキュリティポリシー補助モジュール · DNS プロファイル補助モジュール · プロバイダーデータ補助モジュール · Mullvad データ補助モジュール · AIRVPN データ補助モジュール · メトリクス保存補助モジュール インスタンスマネージャー(Instance Manager) · 外側の Instance 監視器 · VPN Instance(単一 Profile) · Instance コントローラー · Instance DNS コントローラー · Instance ポートコントローラー · ポート転送(Port forward) · Instance メトリクス · Instance 内側監視器 · Gateway マネージャー · Gateway サービス · Gateway メトリクス · GotaTun Web 静的リソースサービス コンポーネントの所有権、権限、障害隔離境界に基づいて実行位置を決定します。
コンポーネント所属図全コンポーネントと責任の所属parent/owner で分類し、UI、Controller、Platform、Store、各 Instance 内部のコンポーネントを示します。(左右にスクロールすると図全体を確認できます)

02VPN Instance アーキテクチャ

単一 Instance の内部全体とデータ経路 各 Instance は専用の Controller、DNS、Port、Gateway、Metrics、Watchdog、GotaTun を持ちます。内側の Watchdog は遮断できますが、独断で再構築や遮断解除はできません。 図 2|単一 VPN Instance:独立サービスとデータ経路 Instance マネージャー コマンド発行/実行世代/リソースリース VPN Instance|独立 Rust Worker Instance コントローラー この Instance だけを制御 Gateway マネージャー Gateway サービス × N Gateway メトリクス Instance DNS コントローラー 独立キャッシュ/接続プール/リゾルバー DNS は VPN トンネルのみを使用 Instance ポートコントローラー ポート転送(Port forward) 同じ Instance の App のみ許可 指定された管理対象 App Proxy クライアント/ 保護されたプロセス ポート転送は同じ Instance の App のみへ TCP/IP 処理+GotaTun Linux カーネル/macOS Rust プロトコルスタック 独立した WireGuard デバイスと鍵 Instance メトリクス ローカルカウンター/遅延分布 Instance 監視器 トンネル/DNS/プラットフォームポリシーの健全性 VPN ピア ピアを限定した暗号化 WireGuard UDP プラットフォームマネージャー プロセスサンドボックス/TUN/ 制限付き UDP FD メトリクスサービス 上限のあるスナップショット転送 App はホストの Underlay、秘密情報、Store、namespace ディスクリプターを取得できません。
図 2単一 VPN Instance のサービスとデータ経路各 Instance は専用の runtime、コントローラー、Gateway、DNS、Metrics、Watchdog を持ちます。(左右にスクロールすると図全体を確認できます)

各 Instance は専用の GotaTun、DNS Controller、Port Controller、Gateway Manager、Metrics、Watchdog を持ちます。
WireGuard identity、DNS cache、socket、buffer、queue も分ける必要があります。
CPU、NIC、OS は共有できますが、A の実行状態を B と混在させるべきではありません。

先に明確にしておきたい点があります。
GotaTun は WireGuard エンジンであり、それ自体が完全な TCP/IP socket stack ではありません。
そのため、GotaTun を接続しただけで TCP/UDP の接続処理がすべて揃うわけではなく、この部分は別途処理する必要があります。

Linux では、隔離された network namespace 内の kernel TCP/IP stack を直接使う方向で考えています。
macOS の明示的な Proxy では、各 Instance が個別に smoltcp と GotaTun を使います。
それぞれの socket、buffer、queue も独立させ、Controller の SQL や Provider HTTP 呼び出しが実際のネットワーク通信を妨げないようにします。

DNS も Instance に従わせる必要があります。
名前解決に失敗したら失敗として報告し、こっそり host resolver に問い合わせ直してはいけません。
Gateway が HTTP CONNECT や SOCKS5 TCP/UDP を受け取ったら、実際に自分の Instance に渡して宛先への接続を確立させます。
Port Forward の転送先は、同じ Instance 内で検証済みの App endpoint に限ります。手間を省くために直接接続してはいけないのが、host の 127.0.0.1。

管理対象 App、Gateway、Port Forward の違い

トラフィックの入口を 3 種類に分けています。
管理対象 App は先に隔離環境に入り、自分で host ネットワークを選ぶことはできません。
Gateway は、HTTP/SOCKS Proxy に明示的に渡されたトラフィックだけを担当します。App が別途確立した接続は自動的には保護されません。
Port Forward は VPN から入ってくるトラフィックです。転送先は同じ Instance 内で有効な次のリースを持つ App のみです: AppEndpointLease 。

DNS と IPv6 も例外にはできません。
Instance の DNS 経路が使えなければ、問い合わせを行いません。
IPv6 も、検証できる tunnel 経路がない場合、ホストのネットワークを自動的に通してはいけません。

macOS の App 隔離は少し厄介

macOS 側は、実際に少し厄介です。
明示的な Proxy は作れますが、実際に Proxy に渡された通信だけを扱います。
QUIC、WebRTC、あるいは App 自身が使うシステム DNS は、別途 socket を作る可能性があります。
そのため、Proxy があるからといって、App 全体が隔離されたことにはなりません。

ブラウザーツールボックスのサイト別プロキシルーティングページ:香港 SOCKS5 127.0.0.1:1080 とオランダ SOCKS5 127.0.0.1:1082 の 2 つのプロキシプロファイル
画面 03ブラウザーツールボックスのサイト別プロキシルーティング:香港は 127.0.0.1:1080、オランダは 127.0.0.1:1082 を使用。実際のインターフェースのスクリーンショット・2026-11-01 (左右にスクロールできます)

03Linux:OS 層で「逃げ道をなくす」

Linux の隔離データ経路 host で事前作成した制限付き UDP FD は GotaTun だけに渡します。App の経路は netns 内の lo/TUN のみです。worker の世代交代時も閉鎖済み sandbox を維持でき、ホストの default route/DNS は変更しません。 図 3|Linux:管理対象プロセスの隔離と WireGuard UDP 出口 ホストネットワーク namespace|既存のネットワーク設定を維持 Instance 隔離サンドボックス:lo と TUN のみ、veth/Host NAT なし ホスト loopback リスナー Gateway TCP/UDP 転送 結び付け済みクライアントの通信のみ受付 プロセスサンドボックス 指定された管理対象 App のプロセスツリー ホスト IPC とネットワーク FD の継承を禁止 Instance 内部サービス Gateway/DNS/ポート接続 宛先 socket は隔離 netns 内で作成 事前 bind 済みリスナー FD を受け渡し Linux カーネル TCP/IP+TUN 独立ルーティング/DNS/ファイアウォールポリシー GotaTun Instance ごとに独立デバイスを所有 制限付き UDP FD ホスト netns で事前作成 許可された VPN peer にのみ送信可能 ホスト NIC → VPN ピア プラットフォーム管理:プロセスとネットワークリソースの制御 Worker/TUN の障害時も App に別の外部ネットワーク出口はありません。サンドボックスの寿命は Worker 世代をまたげます。 このシステムが所有する namespace、cgroup、bind、フィルタールールだけを作成または削除します。
図 3Linux namespace の隔離と WireGuard UDP 経路通常の host ネットワークは変更しません。隔離 netns の App に、許可されていない平文の host 出口はありません。(左右にスクロールすると図全体を確認できます)

Linux の方法は比較的直接的です。
ホストの default route を変更するつもりはありませんし、物理 NIC を別の namespace に移すこともしません。
管理対象外の App は、通常どおり元のネットワークを使います。

Platform Manager は最初に host network namespace 内で、指定した WireGuard peer だけを許可する UDP socket を作ります。
続いて、制限付き FD を隔離 namespace 内の VPN worker に渡します。
その namespace には loopback と TUN しかなく、host に通じる veth/NAT の平文出口は設けません。

App が送った IP パケットはまず TUN に入り、GotaTun で暗号化され、host namespace 内で先に作成した UDP FD を通じて WireGuard peer に送られます。
ただし、socket を host で作成しただけでは不十分です。
どの IP/port に接続できるのか、誰が FD を持つのか、Platform policy が本当に有効なのか、すべて確認する必要があります。

私がより重視するのは、App に通常の host internet 出口がそもそもないことです。
worker が落ちたら、まずネットワークを切断します。代替経路を探す必要はありません。
VPN が切断された後に App が勝手に ISP の出口へ迂回することは、受け入れられません。

ただし、 netns も完全な sandbox ではありません。
App が別プロセスの FD を継承したり、Unix socket、D-Bus、host IPC を通じて迂回路を見つけたりする可能性があります。

04Instance の起動と障害復旧の流れ

ライフサイクル、自動復旧、停止の優先 manual Stop と緊急停止は復旧より優先します。Manager/intent/generation/sandbox の tuple 全体が一致して初めて CommitResume が可能です。Manager の世代交代では worker を置き換え、closed sandbox だけを引き継ぎます。 図 5|安全な起動、自動復旧、手動停止の優先権 意図の永続化とリソース予約 期待状態/手動停止/緊急停止 Manager の任期/意図の版 Worker 世代/ID リース 先に環境を閉鎖し、次にプロセスを起動 サンドボックス世代は Worker 世代から独立 未設定 Worker/検証済み ID Store が一度限りの秘密情報を非公開で受け渡し ヘルスプローブだけを許可 VPN トンネル/DNS/ポリシーを検証 App/Gateway/ポート転送を閉鎖 健全性を独立して検証 開通の確定(CommitResume) Manager が最新の意図を再確認 Platform が単回の許可を消費 この段階で初めて通信ゲートを開く 準備完了/VPN トンネルのみ使用 設定済み App/Gateway を有効化 現在の Worker 世代だけを許可 Controller の障害はマネージャーに影響しない 障害 → 閉鎖/封印 関連するデータ入口を即座に閉鎖 障害検知時も直結出口なし 封印済み世代は再開通不可 自動復旧 永続化された意図が desired=running の場合のみ 手動停止/緊急停止は解除済みであること 旧 Worker/FD の確実な失効が必要 世代を新設、Instance ID は継続 手動停止/緊急停止 意図の版を進め、許可を撤回 期限切れ復旧処理による再開通は禁止 緊急状態解除後も停止を維持 その後はユーザーの明示的な起動が必要 Manager または Platform の再起動 旧世代を封印 閉鎖を証明済みのサンドボックスリースだけを維持 稼働中の Worker を置換 停止意図を証明できない → 閉鎖 CommitResume を承認できるのは Instance Manager だけです。ApplyPolicy 単独では再開通できません。 CPU/Store/Metrics の停止によって、許可されていないホスト直結の代替経路が生じてはいけません。
図 5Instance の起動と障害復旧の流れ図中の Resume には、現在の意図、worker generation、Platform enforcement の証拠が必要です。(左右にスクロールすると図全体を確認できます)

WireGuard のハンドシェイクが成功しても、App にインターネット接続を始めさせてよいとは限りません。

Instance を起動するときは、先に隔離環境を作り、デフォルトでは閉鎖しておきます。
worker が起動した後も、WireGuard のハンドシェイク、DNS、ヘルスプローブだけを許可します。
現在の設定、Platform policy、worker generation に問題がないことを確認してから、App と Gateway を開通させます。

途中で一つでも失敗したら、維持する状態は blocked。
worker を再構築するときは、必ず新しくするのが generationです。 Seal された旧 generation は再開通できません。
Manager の世代交代、Platform epoch の変更、またはその間にユーザーが Start/Stop を押した場合、それ以前に発行したコマンドはもう有効にしてはいけません。

Watchdog は 2 層に分けます。
Instance 内の Watchdog は問題を見つけたら自分の ingress を先に閉じ、その後で障害を報告します。
外側の Instance Manager が意図の記録、旧リソースの回収、再構築を担当します。
両者が同時に同じ Instance の restart を奪い合うような状態にはしたくありません。

手動の Stop と EmergencyStop の優先順位は、必ず最上位にします。
Stop の際にデータベースが壊れ、最新の停止意図を保存できたかさえ分からないとします。その場合、システムが自動的に再開する根拠にしてはいけないのが、古い desired=running です。
このときは、入る状態を recovery_requiredとして、人の確認を待つ方がよいと思っています。

元の TCP session が crash 後に再接続できるかどうかについては、無理に保証するつもりはありません。
間違った出口を通るより、接続が切れる方がよいからです。

接続状態は Connected/Disconnected だけでは足りない

UI が Connected/Disconnected の 2 状態だけになることは望んでいません。
例えば、A に設定したのは desired_state=runningですが、現在の状態は reconnectingで、さらに enforcement=blocked。
この 3 つは分けて表示すべきです。
動作を続けてほしいという意図と、今実際に外へ送信できるかどうかは、別のことです。

起動、障害時の閉鎖、自動復旧:START/ACTIVATE シーケンス図

この流れでは、まず desired=running を Store に書き込み、Manager が generation と identity lease を予約し、Platform に実行を依頼するのが PrepareClosed。
worker は最初に probe_only で起動します。
tunnel、DNS、policy、現在の権限情報がすべて一致していることを確認して初めて、実行するのが CommitResume と Activate。

START/ACTIVATE シーケンス図 すべての payload gate はデフォルトで閉鎖。現在の Manager と Platform の証拠だけが開通を許可できます。 START/ACTIVATE 起動・開通シーケンス図 データゲートはデフォルトで閉鎖。現在の Manager と Platform の検証証拠だけが開通を承認できます。 ユーザーインターフェース VPN コア ストレージサービス Instance マネージャー プラットフォームサービス Instance Worker 01 · 起動+版の照合 02 · CAS で desired=running を永続化 03 · 起動意図を発行 04 · Worker 世代と鍵リースを予約 05 · PrepareClosed:先に閉鎖 06 · サンドボックス/ポリシーの証拠を返却 07 · Worker と制限付き FD を作成 08 · 制限付きリソースを渡して起動 09 · 単回の秘密情報リースを受け渡し 10 · Configure/Start:プローブのみ 11 · VPN トンネルと DNS の検証完了 12 · CommitResume:開通を承認 13 · Activate:正式に準備完了 14 · 操作成功 15 · SSE で最新状態を返却
シーケンス図 ASTART/ACTIVATE シーケンス図意図の永続化、環境の閉鎖、probe-only を経て、最後に Activate を許可します。(左右にスクロールすると図全体を確認できます)

05最初の Provider はどう作るのか?これはブートストラップの問題

Provider の初回接続とアクセスごとの検査 初めて Profile を取得したら Instance を自動作成し、元のネットワークを撤回してから latch を封印します。全 Provider は手動選択した出口を共有しますが、各 Adapter が個別に検査し、HTTP ごとに preflight をやり直します。 図 6|Provider の初回ブートストラップ、出口切替、要求ごとの検証 Provider の初回作成ジョブ 単一アカウント/制限付きホスト網のみ許可 許可は 10 分間/1 セッション 60 秒 API 要求ごとの検査は引き続き必要 最初の有効な Profile を取得 検証器と一意な子操作の永続化 コントローラー → Instance マネージャー Instance を自動作成・起動 新 Instance が初期プローブに合格 独立トンネル/DNS/システム制限 候補接続はヘルスプローブにのみ使用可能 元の直結出口を閉鎖 1.閉鎖意図を永続記録 2.全直結 socket を撤回 3.永久 latch と出口の紐付けを記録 全体で選択された Instance 全 Provider が同じ出口を共有 切替や無効化は手動のみ ホスト直結への自動 fallback 禁止 遮断/無効化 元の選択 Instance を引き続き表示 古い監視情報を正常扱いしない 復旧が手動停止を上書きしてはいけない 実際の Provider API 要求すべて:再試行、ページ取得、トークン更新、照合を含む 1.クォータを計画・予約 IP ファミリーと不変の要求を固定 IP プローブと API のクォータを予約 現在の接続 ID を読み取る 2.Provider の出口 IP 検査 API と同じ接続/IP ファミリーを使用 IP、所在地、プロバイダー所属を個別照合 プローブ要求にアカウント認証情報は含めない 3.単回の要求許可を発行 要求、ルート、接続世代を結び付ける 乱数値+送信期限 2 秒 検査処理が自身を再帰呼び出ししない 4.送信前に原子的検証 世代、IP ファミリー、期限を再検証 API の最初のバイト前に許可を消費 自動リダイレクトや隠れた再試行は禁止 5.Provider API を実行 選択した VPN トンネルからのみ送信 遠隔変更の結果が不明 → unknown 先に照合し、再試行するか判断 Provider 管理パネル Instance がオフラインでも選択を維持 出口 IP/国/都市/時刻 プロバイダー所属は確認できない場合あり IP の観測だけでは DNS、IPv6、QUIC、すべての App 経路は証明できません。プラットフォーム制限による強制的な保護が必要です。 切替完了後:ホスト DNS、ホスト HTTP、ほかの出口への自動切替は禁止。
図 6Provider の初回ブートストラップ、出口切替、要求ごとの事前検査初回ブートストラップの制限付き直結権限は、VPN への切替後に撤回します。自動 fallback は許可しません。(左右にスクロールすると図全体を確認できます)

Provider にも問題があります。
VPN Manager をインストールした直後には、VPN Profile がまだありません。まず Mullvad や AIRVPN からアカウント、device、WireGuard 設定を取得する必要があります。
では、最初の管理要求はどこを通すのでしょうか?

現在は、初回だけ条件付きの直結を許可することを考えています。
Provider worker が受け取る、時間、アカウント、HTTPS origin、DNS、通信量予算を制限した許可が BootstrapGrant。
最初の有効な Profile を取得したら、Controller が Instance Manager に最初の VPN の作成と起動を要求します。

tunnel が管理要求を処理できることを確認したら、まず direct を閉鎖する意図を Store に書き込み、元の grant と socket を撤回し、最後に付ける印が bootstrap_consumed=trueで、そのうえで Provider 全体の出口を設定します。
この順番を崩してはいけません。特に、直結がまだ生きているのに切替完了と言ってはいけません。

以降、すべての Provider API はユーザーが選んだ VPN Instance を通します。
その Instance が落ちたら、表示するのは blockedで、host への切戻しも、別の Instance への自動切替も禁止です。
どうしても処理できなければ、ユーザーが新しい出口を手動で選ぶか、明示的に一度限りで許可するのが rebootstrap。

また、実際に Provider HTTP request を送信する前には、毎回 IP preflight を実施したいと考えています。
retry、pagination、token refresh を含め、同一の route tuple、IP family、connector を確認します。この request だけに使える許可である ProviderRequestPermit を取得して初めて送信できます。
現時点では、preflight deadline を 3 秒、dispatch window を 2 秒と仮定しています。
これでは遅くなり、checker quota も増えますが、少なくとも UI に数分前に表示された IP を、現在の経路選択の証拠にはしません。

UI では、どの Instance を選んだのか、今回の request が本当に制御された tunnel を通ったのか、外部 IP がその Provider の出口なのかを、分けて表示する必要があります。
例えば、Mullvad Instance を選んで AIRVPN の管理 API を呼び出すことは、私の設定に完全に沿った動作になり得ます。
外部 IP が AIRVPN ではないからといって、通信が漏れているとは限りません。

Provider Adapter は何を担当するのか

プロバイダーの統合は、できるだけ同じ流れにしたいと考えています。
Profile の検証、Instance の作成、出口の紐付けは共通の制御ロジックで処理します。Adapter に任せるのは、Mullvad、AIRVPN それぞれの device、server、port-forward API です。
Provider を追加するとき、各 Adapter に Instance の直接起動・停止や host socket の作成権限を持たせたくありません。

Provider API にはもう一つ厄介な点があります。request の送信後に timeout しても、相手が実行しなかったとは限りません。
例えば、device の作成自体は成功していて、返答だけ届かなかった可能性があります。
この場合、まず記録する状態は unknownで、その後 operation journal を使って遠隔側の状態を確認します。そのまま再送すべきではありません。

PROVIDER 初回ブートストラップのシーケンス図

ブートストラップ全体が完了したといえるには、新 Instance が管理要求を送信でき、元の direct 権限が確実に撤回されたことを確認し、最後に実行するのが CommitEgress。
切替中のある結果を確認できない場合、まず留めておきたい状態は blockedで、状態を明らかにします。

PROVIDER 初回ブートストラップのシーケンス図 初回の制限付き直結 → 最初の Profile → Instance 自動起動 → seal/revoke → CommitEgress。 Provider が初めて VPN を作成するシーケンス図 初回の制限付き直結 → 最初の Profile → Instance 自動作成 → 直結閉鎖 → VPN 出口を紐付け。 UI/VPN コア ストレージサービス プラットフォームサービス Provider ワーカープロセス Provider API インスタンスマネージャー VPN Instance 01 · ブートストラップ記録を作成 02 · 期限付き直結権限を準備 03 · 許可された直結チャネルのみ受け渡し 04 · 出口 IP 事前検査+Profile 取得 05 · Profile を返却 06 · Profile と一意な子操作を保存 07 · Instance を自動作成・起動 08 · 環境を閉鎖し、Worker を作成 09 · FD を渡し、Worker を起動 10 · VPN/DNS/管理チャネルが準備完了 11 · 閉鎖意図を永続化 12 · 直結権限と socket を撤回 13 · 撤回の検証証拠を返却 14 · 出口と永久 latch を確認 15 · 以降は選択した Instance を使用
シーケンス図 BPROVIDER 初回ブートストラップのシーケンス図BootstrapGrant → 最初の Profile → Instance → Seal/Revocation → CommitEgress。(左右にスクロールすると図全体を確認できます)

06VPN ごとのトラフィックとリソース使用量を明確に見たい

VPN Manager が、最後には VPN 接続を示す緑のランプをいくつか表示するだけのものになってほしくありません。
どの Instance が通信量を消費しているのか、Gateway にアクティブな接続がいくつあるのか、DNS の遅延が上がっていないか、どのプロセスが CPU、メモリー、queue を占有しているのかを知りたいです。

Metrics Service と Performance Manager は分けます。
Metrics Service は統計データを収集します。例えば Instance counters、Gateway payload bytes、WireGuard UDP bytes、DNS の成功・失敗、active flows、CPU、RSS です。
Performance Manager は、スループット、遅延、リソース効率の分析、または私が明示的に開始した benchmark の実行を担当します。
速度測定のために、勝手に Instance の状態を変えてはいけません。

トラフィック観測と性能分析のアーキテクチャ Instance のローカル counters は Metrics Collector に非ブロッキングで渡し、圧縮して保存します。UI と Performance Manager は集約データを照会します。App payload はこの経路を通りません。 図 7|トラフィックメトリクス収集と非ブロッキングの観測経路 Instance メトリクス メトリクスコレクター 圧縮と集約 Store/メトリクス DB 性能マネージャー React インターフェース 毎秒の累積カウンター パケット内容は転送しない 順序検証/重複排除/欠損表示 過負荷時はサンプル破棄可 CBOR+Zstd 時間集約/保持期間 制御とメトリクスを別 DB に保存 上限のあるデータ照会 分析/性能ベンチマーク レート/遅延/データ欠損 観測経路では統計データだけを渡します。App/Gateway の実際の通信は、引き続きそれぞれの Instance を通ります。
図 7Instance から UI への独立したトラフィック観測経路Metrics が飽和するとサンプルの完全性は下がっても構いませんが、データプレーンを待たせてはいけません。グラフには欠損を示す必要があります。

Metrics はデータプレーンにバックプレッシャーをかけない方がよいと考えています。
Instance は毎秒 1 回、上限のある snapshot を送り、Collector が順序検証と重複排除を行い、その後 CBOR/Zstd で圧縮・集約します。
収集側が詰まったら、いくつかのサンプルを捨て、表示するのは gap だけで十分です。パケットをデータベース待ちにしてはいけません。

トラフィックの数値も、いい加減に計算してはいけません。
Gateway payload bytes と WireGuard UDP bytes は異なる層の統計なので、足し合わせて総通信量にしてはいけません。
counter reset やデータ中断があった場合、グラフには欠損または不明を表示すべきで、そのまま 0 として描いてはいけません。
goodput にはさらに受信側の確認が必要で、sender counter だけで計算してはいけません。

トラフィック監視でも、すべてを記録する必要はありません。
デフォルトでは counters、histograms、必要なエラー情報を集めるだけで十分です。
DNS query name、完全な URL、パケット payload をすべて Metrics に詰め込みたいとは思いません。

コメントと議論

コメントサービスに接続しています…