一秒で終わる再接続は、クライアントが速いことを教えてくれる。トンネルが落ちている間に何かが漏れたかどうかは教えてくれない。一分かかる再接続は、クライアントが遅いことを教えてくれるが、空白の間に直接トラフィックが許可されていたかどうかはやはり教えてくれない。これは二つの測定であり、前者だけを報告するキルスイッチ検証は、スイッチを検証していない。

二つの観察、二つの別々の判定

可用性の問いは単純だ。トンネルがいつ再び使えるようになったのか、保護されたアプリがそこへ到達するために新しい接続を必要としたのか。強制の問いは別だ。保護が落ちた瞬間から戻った瞬間までの間に、どのパケットがマシンを離れたのか。

別々の記録で答えよう。一方の行は、空白が一定の秒数続いたと述べる。もう一方の行は、その空白の間に保護されていない要求が成功したかどうかを述べる。これを一つの合否にまとめると、最も重要なケース、つまり漏えいを伴う素早い再接続が隠れてしまう。

計時できるプローブを作る

使うのは、無害で安価、かつトンネル自身の制御チャネルから独立した要求だ。下のループは一秒に一度、タイムスタンプ、HTTP ステータス、経過時間を出力し、固まる代わりに大きな音を立てて失敗する。

while true; do
  printf '%s ' "$(date +%H:%M:%S)"
  curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' --max-time 2 https://example.com || echo "curl-exit-$?"
  sleep 1
done

ステータスだけでなく、curl の終了コードを読む。28 は要求がタイムアウトしたという意味で、ブロックされた経路はそう見える。6 は名前解決が失敗したという意味で、これは別の所見であり、たいていはより興味深い。空白の間に短い経過時間で 200 が返るなら、トラフィックは外へ出ることを許されていた。

出力はファイルに書き、保存しておく。ターミナルのスクロールバックは証拠にならないし、興味深い部分はたいてい真ん中の三行だ。

整った失敗ではなく、本物の失敗を起こす

クライアント自身のメニューから切断するのは協調的な経路の検証で、クライアントにはブロック規則を設定する時間がある。代わりにプロセスを強制終了するか、下位のリンクを落とせば、実際の失敗がたどる経路を検証できる。

トリガーごとに異なるコード経路を通る。サーバ側の障害、ローカルリンクの喪失、クライアントのクラッシュはそれぞれ別の状態を残しうるし、そのどれかが最終的に遭遇するケースである可能性が高い。少なくとも二つのトリガーでプローブを実行し、結果をトリガーごとにラベル付けする。

許可されたトラフィックの形をログで読む

空白の間の 200 や素早い接続失敗は、どちらを向いていても漏えいだ。空白の間ずっとタイムアウトするのが、ブロック方針の期待される形だ。接続が失敗する一方で名前解決が成功するのは、リゾルバにまだ到達できるということで、別種の漏えいだ。

既存のソケットには注意が要る。プログラムは切断前に開いた接続を使い続けられるので、生きているように見えるトラフィックが新しい経路の許可を証明するとは限らない。ソケット一覧は、その接続が出来事より前のものかを示す。

ss -tunp | grep -E 'ESTAB|SYN-SENT'
ip route get 198.51.100.7
resolvectl status | sed -n '1,20p'

ソケットの開始時刻と切断のタイムスタンプを比べる。障害より古い接続は漏えいではなくソケットの再利用の証拠であり、それを失敗として報告すると、次の人を存在しないバグの探索に送り出すことになる。

更新を生き延びる形で結果を記録する

トリガー、空白の秒数、ブロックされていない要求が成功したか、名前解決が成功したか、クライアントと OS のバージョンを書き留める。この五つの事実の組はリリース間で比較できるが、散文の一段落ではそれができない。

クライアントの更新やプロファイル変更のあとに同じ検証を再実行する。ブロックの挙動は実装の詳細であり、動くからだ。記録の狙いは、それがインシデントになる前に動きに気づくことであって、クライアントを一度だけ認定することではない。

関連記事