Always-on と kill switch は同じ設定画面の二行違いに並んでいるため、一つの機能の二つの名前だと読まれがちです。そうではありません。Always-on は再接続のポリシーです。起動時にトンネルを開始し、クラッシュやネットワーク変更の後に復帰させ、失敗している間は再試行し続けます。Kill switch は通信のポリシーです。トンネルが落ちている間にどのパケットならマシンから出てよいかを定めます。前者だけを有効にすることはでき、ほとんどのプラットフォームでは既定の結果としてその隙間の間も通信が外に出ます。

確認 1: ブロックは独立したスイッチか

Android 10 以降では、保存済みの VPN プロファイルは歯車アイコンの奥に二つの独立したトグルを開きます。Always-on VPN と Block connections without VPN です。kill switch なのは後者だけです。always-on だけでは、切断後にトンネルは確かに戻りますが、その間は要求処理中のアプリが Wi-Fi やモバイルデータ経由でネットワークに到達できます。

管理対象デバイスでもポリシー API に同じ分離があります。setAlwaysOnVpnPackage() はパッケージ名と lockdownEnabled フラグを受け取ります。パッケージを設定してフラグを false のままにした管理者は、閉じたデバイスではなく常設のトンネルを作ったことになります。コンソールの文言がその違いを見せることはほとんどありません。

状態はコンソールではなくデバイスから読みます。adb shell settings get global always_on_vpn_app と adb shell settings get global always_on_vpn_lockdown です。前者が null を返すなら、プラットフォーム層で強制されているパッケージはありません。後者が 0 を返すなら、プラットフォームは何もブロックしていません。Android の VPN ガイドはこれらを一つのモードではなく別々の機能として説明しています。

確認 2: ブロックを持っているのはどの層か

ラベルはクライアントにあり、ブロックはふつうファイアウォールにあります。だから同じ言葉がプラットフォームごとに違う意味になります。下の行は、どれかを信じる前に確認する価値のあるものです。

Where you see the labelWhat the label promisesWhat actually blocks
Android VPN profileAlways-on VPNBlock connections without VPN
Android device policyAlways-on packagelockdownEnabled flag
Windows built-in VPNAlways On tunnelOutbound firewall rules
WireGuard for WindowsTunnel serviceBlock untunneled traffic option
wg-quick on LinuxRoute via AllowedIPsYour own nftables or iptables rules

Windows はブロックしないラベルの最も明快な例です。ここでの Always On はトンネルの持続性を指します。マシン認証用のデバイストンネルと、サインインしたアカウント用のユーザートンネルで、プロファイル XML や Add-VpnConnection で構成します。そのプロファイルのどこにも、トンネルがないときに外向きパケットを止めるものはなく、外向きフィルタは既定で有効ではないため、Wi-Fi 上のノート PC は VPN が落ちても動き続けます。

WireGuard も同じように分かれますが、分かれ方はクライアントごとです。WireGuard for Windows にはトンネルごとの Block untunneled traffic オプションがあり、フィルタ規則を入れます。Linux のツールに同等のスイッチはありません。wg-quick は AllowedIPs の各項目にルートを、さらに fwmark 規則を追加し、wg-quick down はその両方を取り除くので、物理的な既定ルートがパケットを落とさずに再び引き受けます。

Apple のプラットフォームでは、always-on は監督下デバイス向けの管理プロファイル機能として提供され、チェックボックスではなく on-demand 規則として表現されます。ペイロードから答えるべき問いは、失敗したトンネルが通信を拒否するのか、別の接続を要求するだけなのかです。メニューの題名はそれを教えてくれません。

確認 3: アイコンではなく隙間を測る

重要な遷移は四つあり、それぞれ違う壊れ方をします。誰かがログインする前の起動、スリープと復帰、Wi-Fi からモバイルデータへの切り替え、そしてトンネルインターフェースを中途半端な設定のまま残すクライアントのクラッシュです。その一つで再接続がきれいに決まっても、残る三つについては何も証明できません。

最も安い試験は、わざとトンネルを止めてから外に出ようとします。トンネルが落ちている状態で curl -m 5 -sS -o /dev/null -w '%{http_code}' https://example.com が 200 を返すなら、ポリシーは fail-open です。タイムアウトや接続拒否なら、まだ何かがブロックしています。ip route get 1.1.1.1 はパケットを運ぶインターフェースを教えてくれ、これは通常トレイのアイコンより情報量が多く、systemctl status wg-quick@wg0 はインターフェースがきれいに削除されたかどうかを示します。

同じ検査を、ネットワークが落ち着いた後だけでなく、変化している最中に二つ目のウィンドウで実行してください。切断と再試行の間の間隔こそが要点であり、名前解決は通信が漏れたことに気づく最短の方法です。トンネルが落ちているのにローカルのリゾルバが答えるなら、ブロックは完全ではありません。

確認 4: 閉じる前に例外を決める

二つの例外は構造的なものです。クライアントは VPN サーバー自体に到達しなければならないので、すべてを落とす規則セットは、保護を戻すはずの再接続も妨げます。キャプティブポータルも、上がっていないトンネル越しには到達できないため、ホテルの Wi-Fi 上で閉じたデバイスには、その場しのぎではなく文書化されたポータルの開き方が必要です。

最小の形は、エンドポイントを許可し、他はすべて落とします。

table inet vpnlock {
  chain output {
    type filter hook output priority 0; policy drop;
    oifname "lo" accept
    ip daddr 203.0.113.10 udp dport 51820 accept
    oifname "wg0" accept
  }
}

そのエンドポイントの行は定数ではなく変数として扱ってください。事業者はアドレスを入れ替えますし、古くなった例外は、動いていた kill switch が再接続できないデバイスに変わる通常の理由です。ローカルのプリンタ、NAS 共有、開発用サービスも同じ文書化された一覧に入れるべきで、そうしないとサポートチケット一件ずつで見つかることになります。

確認 5: 実際に何を証明したかを記録する

四列の表を保ち、試験で埋めてください。状態、期待する挙動、観察した挙動、証拠です。行は確認 3 の遷移に、意図的な失敗を一つ加えたものです。コマンド出力で埋められない行は、試験していない行です。再接続の行は通るのに起動の行で落ちるデバイスは珍しくないので、追加の十分には値があります。

完了と呼ぶ前に:

  • ブロックの設定が再接続の設定と別であることを確認し、その値をデバイス上で読む。
  • ブロックを強制する層がどれか - クライアントのオプション、デバイスポリシー、ファイアウォール - と、その層がどこで設定されるかを知る。
  • ブロックを証明する検査コマンドを一つ保ち、遷移の後ではなく遷移中に実行する。
  • 例外の一覧を、エンドポイントを含めて書き出し、誰が更新するかを記す。
  • 管理されたハードウェアでは、強制されたポリシーに手を触れない。ブロックを外すことはトンネルを直すのではなく要件を変えることになる。

関連記事