ルーターにログインし、フォーラムの投稿からキルスイッチのルールセットを貼り付けると、トンネルが切れた瞬間に LAN はブラウジングを止める。二日後、ルーターは依然として通常の WAN 経由で動的 DNS レコードを更新しており、ゲストのノート PC は依然として IPv6 サイトに到達している。どちらの挙動も正常だ。貼り付けたルールは一つのチェーンと一つのアドレスファミリーしか覆っておらず、そのどちらもルーター全体ではないからだ。パケットの経路を順にたどれば、何が見落とされていたかが正確に分かる。

ノート PC ではなくルーターから始める

ルーターレベルのポリシーは小規模オフィスに魅力的だ。背後にあるすべてのデバイスが同じ挙動を継承し、クライアント側のスイッチを有効にし忘れる人がいなくなるからだ。ファームウェア更新のたびに試験できる場所も一つにまとまる。だが、ルーター自身のトラフィックや二つ目のアドレスファミリーを自動で覆うことはできない。多くの導入失敗の背後にある二つの隙間はそこにある。

何かを編集する前に、紙に三つの事実を書き留めておこう。WAN インターフェース名、LAN ブリッジ名、VPN エンドポイントのアドレスとポートだ。ルールではこの三つすべてが必要であり、インターフェース名を推測することが、正しく見えるルールセットが何もしない最もよくある理由だ。

パケットが通る三つの経路をたどる

ノート PC からインターネットへのトラフィックは転送されるので、FORWARD チェーンを通る。ルーター自身が生成するトラフィック、たとえば DNS 転送、NTP クエリ、DDNS 更新、VPN の制御チャネルは OUTPUT を通る。ルーター宛ての管理トラフィックは INPUT を通って到着する。

したがって FORWARD だけで作られたキルスイッチはクライアントを守るだけで、それ以外は何も守らない。冒頭の漏洩が不思議ではなく予測可能である理由は、この表が示している。

Packet pathExampleChain that sees it
LAN client to WAN or tunnelBrowser requestFORWARD
Router-originatedDDNS update, NTPOUTPUT
Addressed to the routerSSH from the LANINPUT

ほとんどの設計では INPUT にキルスイッチは不要だが、トンネルが落ちている間も管理アクセスを維持する必要がある。そうでなければ、VPN が最初に失敗したとき、コンソールケーブルを持ってオフィスまで車を走らせることになる。

トンネルが起動できるようにルールを並べる

機能するキルスイッチは拒否で終わる許可リストであり、それをキルスイッチたらしめているのは順序だ。レンガにしてしまうかどうかは並べ方で決まる。まずループバック、VPN エンドポイント、確立済みフローの応答、トンネルインターフェースを許可し、残りは最後に拒否する。

iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -d 198.51.100.7 -p udp --dport 51820 -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -A OUTPUT -o eth0 -j REJECT

この時点でエンドポイントはホスト名ではなくアドレスでなければならない。最後の拒否が DNS を遮断すると、プロバイダーのホスト名を指定したルールは解決できなくなり、再起動後にトンネルが二度と起動しなくなる。プロバイダーがエンドポイントをローテーションする場合は、ポリシーを適用する前に名前を解決し、同じスクリプトからそのルールを更新する。

クライアントトラフィックについても FORWARD で同じ形を鏡写しにし、拒否の上に明示的な管理ルールを置く。その管理ルールは狭く保ちつつ到達可能にし、最初の SSH セッションを閉じる前に二つ目のセッションを開いて確認する。キルスイッチの変更中に自分を締め出すのはよくあることなので、計画に入れておく。WireGuard が動くルーターでは、ピアの allowed-ips がトンネルに入るものを決め、これらのルールはトンネルなしで出てよいものを決める。

IPv6 には専用の玄関が必要だ

ここまではすべて IPv4 だ。LAN で IPv6 が動いていて、トンネルが IPv4 しか運ばないなら、覆われたすべてのデバイスには新しいルールが見ることのない直接経路が依然として残っている。デュアルスタックのノート PC は喜んでそちらを選ぶので、IPv4 で試験している人にだけキルスイッチが壊れて見える。

ip6tables -A FORWARD -i br-lan -j REJECT
ip6tables -A OUTPUT -o eth0 -j REJECT
ip6tables -A OUTPUT -o lo -j ACCEPT

ここでも順序が重要で、ループバックの許可は WAN の拒否より前に置かなければならない。そうしないと、ルーター上のローカルサービスが妙な形で失敗し始める。ネットワークが IPv6 をまったく使わないなら、WAN と LAN で無効にするほうが単純で、忘れるものも残らない。

VPN クライアントの中には、IPv4 用のデフォルトルートだけをインストールし、既存の IPv6 デフォルトには手を付けないものがある。クライアントが処理したと仮定せず、トンネル接続後にルーティングテーブルを確認しよう。

トンネルを落として証明する

ルーターからトンネルを停止し、次の順序で三つの場所から試験する。

# systemctl stop wg-quick@wg0
# ip route get 203.0.113.1
# curl -m 5 -sS http://example.com || echo blocked
# iptables -vnL OUTPUT | head
# ip6tables -vnL OUTPUT | head

ルート検索はパケットがどこへ向かうかを教え、カウンターはルールが実際に一致したかどうかを教える。失敗した試験の後にパケット数がゼロのルールは何も守っておらず、よくある原因は入力した名前と異なるインターフェース名だ。LAN 側の試験では OUTPUT を動かせないので、ルーター発のトラフィックを明示的に試験する。

必要になる前に障害時の動作を決めておく。fail-closed はトンネルが戻るまでクライアントをオフラインに保ち、fail-open は通常の WAN アクセスを復元して、気にしていたトラフィックにとっての目的を台無しにする。メトリックの高い VPN プールへのブラックホールルートは fail-closed 設計の有効な第二層になる。トンネルのルートが消えたときにデフォルトへフォールバックせず生き残るからだ。

LAN クライアント、ルーター発のリクエスト、IPv6 リクエストの三つすべてがトンネルを落とした状態で失敗し、トンネルが上がれば三つとも再び動くときだけ、その設定を受け入れよう。

関連記事