マークベースのVPNファイアウォールでブロックされたlibvirtゲストのDNSを修正する
マークベースのVPNファイアウォールは、どのフィルタチェーンが見るよりも前にゲストのDNSを破棄します。mangle優先度のルールを1つ使い、クエリとその応答をマークして、LANを開かずに済ませます。
既定のlibvirtネットワーク上のゲストは、WireGuardインターフェースが上がるまでは名前を完全に解決しますが、上がった途端にどの検索も同じ場所で止まります。dnsmasq は今も 192.168.122.1:53 で待ち受けており、ゲストは今もそのアドレスをリゾルバとして使い、クエリも今も届いています。変わったのは、ホスト自身の上流クエリ、またはその応答が、編集しようと思うどのフィルタチェーンよりも先に評価されるルールで破棄されるようになったことです。回避策は 応答が継承するコネクションに対して、十分早い段階でマークを付けること です。
トンネルが上がった瞬間にゲストはDNSを失う
何かを変える前に形を確認します。トンネルを下げ、ゲストから dig @192.168.122.1 example.com を実行して応答することを確かめます。トンネルを上げて繰り返し、タイムアウトすることを確かめます。この二つの結果で、壊れたlibvirtネットワーク、誤ったリゾルバアドレス、死んだdnsmasqを一度に除外できます。
次にホスト側を見ます。dnsmasqがクエリとその上流転送を記録していれば、パケットは届いており、障害は出口側です。dnsmasqが何も記録しなければ、ゲストのクエリは入口で破棄されており、例外はこの記事がマークするフックとは別のフックに属します。
後段のチェーンにあるacceptルールが決して発火しない理由
マークベースのVPNファイアウォールは、送信元アドレスではなくパケットマークやコネクションマークを見て判断します。期待するマークなしで物理インターフェースに届くものは拒否され、その拒否はマークテストを所有するルールセットで起きます。後段のチェーンに追加した ip saddr 192.168.122.0/24 accept の行は、そのパケットでは決して参照されません。だからこそ明白に見えるルールが正しく見えて、何も変えないのです。
何かが動くまで広げるのは二つ目の罠です。ホストの入力パスでlibvirtサブネット全体を許可すると、ゲストにホストの全サービスを開くことになりますが、実際に必要なのは一つのポートだけです。
狭い例外を可能にする仕組みはコネクション追跡です。ct mark をパケットに設定すると、値はパケットだけでなくconntrackエントリに保存されるため、そのコネクションの全パケットが以後それを運び、ホストからゲストへの応答も含まれます。これがすべてのカギです。要求を一度マークすれば、戻り経路はすでにマークされています。
VPNテーブルが見る前にクエリをマークする
例外を専用のテーブルに置き、後から読む人がVPNルールを解析せずに見つけられるようにします:
table inet allowedLocal {
chain guest_dns {
type filter hook input priority mangle; policy accept;
ip saddr 192.168.122.0/24 meta l4proto { tcp, udp } \
th dport 53 ct mark set 0x00000f41
}
}
priority mangle は負なので、このチェーンはVPNパッケージが優先度ゼロでインストールする通常のフィルタチェーンより前に置かれます。ルールを効かせるのはフックの順序です。VPNチェーンの後に並ぶチェーンにある同じルールは飾りにすぎません。
マーク番号そのものは移植できません。インストール済みのVPNルールが自分のトラフィックに使う値を受け取り、稼働中のルールセットから読み取って使います。間違ったマークでは、きれいにロードされ、どのクエリにも一致し、何も通さないルールになります。
穴はポート53の幅に保つ
書かれているとおり、これはlibvirtサブネットからホストへのDNSをTCPまたはUDPのポート53で許可し、それ以外は何も許可しません。ゲストがトンネル外のインターネットに届くことはなく、ホストがゲストへの接続を開くこともありません。後でゲストに別のホストサービスが必要になったら、このルールを緩めるのではなく、その宛先ポートを名指しする別のルールを追加します。
何を引き換えにしているかに正直になりましょう。リゾルバに届くゲストはDNSクエリを通じて任意のデータをトンネリングできます。この例外はVPN境界における意図的な隙間であり、ローカルネットワークが信頼できるという宣言ではありません。送信元を 192.168.122.0/24 に制限すれば隙間を他のどのローカルサブネットからも遠ざけられ、脅威が重要ならその送信元へのクエリを数えるのは安上がりです。
再起動を生き延びさせ、両方の半分を証明する
シェルのプロンプトで入力したルールは次回の起動で消え、VPNインターフェースがすでにトラフィックを通した後にロードされるルールは最初のコネクションで競争に負けます。テーブルをホストの永続的なnftables設定に置くか、VPNサービスより前に並ぶユニットに置き、順序を仮定せずに確認します。
検証には三つの別々の答えが必要です。チェーンがロードされ最初に並んでいることを確認し、conntrackエントリが両方向でマークを示すことを確認し、ゲストの検索がアドレスを返すことを確認します。次にもう半分を確認します。公開ホストへのゲストの通常のWebトラフィックは、トンネルが下がっているかブロックしている間は依然として失敗します。DNSが動き、それ以外が漏れているなら、あなたのルールは上のものより広いのです。
マークを入れた後も検索が失敗するなら、送信元を広げたい衝動を抑えます。dnsmasqがどこへ転送するか、ゲストのリゾルバが 192.168.122.1 を指しているのか誰かが入力した公開アドレスを指しているのか、クエリがそもそもホストに届くのかを確認します。マークはパケットを通すだけでそれ以外は何もしないので、上流が悪いか仮想ネットワークが壊れていれば、ルールをいくら足しても壊れたままです。