システム設定に保存済みの VPN サービスがあり、ターミナルから起動したいとします。macOS でサポートされる経路は scutil --nc ですが、システム自身のネットワーク接続マネージャが所有するサービスに限られます。メニューバーは API ではありません:サードパーティのクライアントは通常独自の自動化を持ち、管理サービスが配布したプロファイルはローカル起動を拒否することがあります。これが私が実行するチェックリストです:正確なサービスを特定し、状態を読み、起動し、状態が変わるのを待ち、最後に通信が実際にトンネルを通ることを証明します。食い違う最初の手順で止めてください——以降のコマンドはすべて前の手順が成功したと前提するからです。

起動を試みる前にサービスを特定する

覚えているプロファイル名ではなく、一覧から始めてください:

scutil --nc help
scutil --nc list

各行は状態を、サービス識別子・種類・表示名と組み合わせています。表示名は重複し編集もできるため、スクリプトで使う値は識別子です。Apple の scutil マニュアルはネットワーク接続モードを文書化しており、インストール済みの --nc help がその Mac におけるサブコマンドの正式な一覧です;古いガイドのフラグが今の macOS にもあると決めつけないでください。

空の一覧は結果であり、エラーではありません。ベンダーのクライアントや管理プロファイルが作ったサービスは、ここに見えないまま動作していたり、ネイティブのコントローラが扱えない種類で見えたりします。その場合はベンダーのツールかプロファイルの文書を使ってください。起動するためだけに並行するネイティブプロファイルを作るのは悪い取引です。

現在の状態を読み、セレクタを確認する

起動する前にサービスの状態を問い合わせ、成功した起動で何が変わるべきかを把握してください:

scutil --nc status "Office VPN"

二つのサービスが表示名を共有している場合、その名前は曖昧です。代わりに識別子を渡してください——コマンドはどちらも受け付けますが、一つのサービスを確実に選べるのは識別子だけです。スクリプトで解析する前に、状態の語が既知のものか確認してください:通常は Connected、Disconnected、Connecting、Disconnecting です。プロファイルが管理されているかも確認してください;ロックされた構成はコマンドを受け付けても無視したり、確認を求めたりすることがあります。

この状態呼び出しは安価なので、自動化のガードに使ってください:状態が Disconnected のときだけ起動し、Connecting を失敗とみなさないこと。サービスがすでに Connected なら、稼働中のトンネルを中断せず起動を飛ばします。

接続を起動し、状態が落ち着くのを待つ

起動要求は非同期です。コマンドが戻るのは要求が受け付けられたときで、認証が終わったときやルートが入ったときではありません。実行したら、上限付きの待機で状態を問い合わせ、サービスが Disconnected に戻ったら抜けてください:

scutil --nc start "Office VPN"
n=0
while [ "$n" -lt 30 ]; do
  state=$(scutil --nc status "Office VPN" | head -n 1)
  [ "$state" = "Connected" ] && break
  [ "$state" = "Disconnected" ] && { echo "start failed"; exit 1; }
  sleep 2
  n=$((n + 1))
done

解決しない Connecting 状態は通常、認証情報・MFA・サーバーを示し、scutil ではありません。コマンドは対話的なログインを提供できないため、トークンやハードウェアキーを期待するプロファイルは Connecting に留まり、やがて戻ります。再試行を足す前に状態の語を読んでください;認証プロンプトに対するループは成功しません。

シェルの終了ステータスを準備完了とみなさないでください。有用な信号は状態文字列であり、その後にトンネルが通信を運んでいる証拠が続きます。静かに戻る起動コマンドでも、決して認証されない接続が残ることがあります。

トンネルが通信を運ぶことを証明する(Connected と言うだけでは不十分)

Connected は接続を表すもので、ルーティングの判断ではありません。ネイティブの PPP と IKEv2 サービスは ppp または utun インターフェースを作り、ルートをインストールします——一方、分割トンネルは既定ルートを意図的にそのままにします。インターフェースと両方のアドレスファミリを確認してください:

ifconfig -l
netstat -rn -f inet
netstat -rn -f inet6
scutil --dns

番号を推測せず、サービスが作ったインターフェースを見つけ、そのうえで特定の宛先がどのインターフェースを使うかをルーティングテーブルに問い合わせてください(route -n get 10.0.0.10 の部分は内部ホストに置き換えます)。物理インターフェースと返るなら、トンネルは上がっているがそのフローを運んでいません。分割トンネルのルート、リゾルバの順序、トンネルインターフェースは許可するがプロセスは許可しないローカルファイアウォール規則を確認します。

名前解決がいつもの偽陰性です。トンネルが完全に健全でも、内部名が誤ったリゾルバで解決されることがあり、それは VPN の障害に見えて障害ではありません。scutil --dns は macOS が使う順序を示します;リゾルバを変えた後は dscacheutil -flushcache と sudo killall -HUP mDNSResponder でキャッシュを消してください。

認証情報と権限をコマンドラインから遠ざける

パスワード、事前共有鍵、トークンを引数に入れないでください。引数はプロセス一覧に見え、シェル履歴にも残り、コピーされたサポート記録から漏れることがあります。プロファイル自身の認証情報処理、つまり Keychain 項目・システムのプロンプト・クライアントの認証フローが、秘密の置き場所です。

sudo でコマンドを実行しても、対話的なログインが無人になるわけではありません。変わるのは実効ユーザーであり、GUI プロンプト・ハードウェアトークン・ログイン Keychain の可用性ではありません。LaunchAgent はユーザーがログインし Keychain が解錠された後にサービスを起動できます;LaunchDaemon は通常、ユーザーセッションのその部分に届きません。組織がサービスを管理している場合、そのライフサイクル規則がローカルのスクリプトより優先されます。

サービスを停止し、停止したままであることを確認する

起動に使ったのと同じセレクタで停止し、トンネルが消えたと決めつけずに状態を読み直してください:

scutil --nc stop "Office VPN"
scutil --nc status "Office VPN"

Disconnected に戻る停止でも、確認すべきことが一つ残ります:プロファイルが要求時接続に設定されていると、スクリプトなしで復帰することがあります。同じ Mac で別のクライアントがルートを持っている場合、そのルートはネイティブの停止を生き延びることがあります。マシンが VPN の外にあると結論する前に、ルートの確認をやり直してください。

手で実行するラッパーでは順序を固定してください:一覧、状態、起動、問い合わせ、ルートテスト、停止、確認。識別子があるのに自由形式の出力から表示名を解析したり、起動コマンドの終了を準備完了とみなしたりしないでください。接続マネージャは状態を報告します;あなたの証拠は状態と、トンネルを指すルートです。

関連記事