LAN のクライアントは自分の VPN には正しく到達しているのに、その DNS 問い合わせは誤った上り回線に現れる。これは通常の意味での漏れではない。ルーターが問い合わせの転送をやめて自分で応答すると、こうなる。傍受は宛先をローカルのソケットに書き換え、そこでリゾルバーは自分自身の送信元アドレスと自分自身の経路判定を持つ、無関係な二つ目の会話を開く。クライアントのフローに付けたマークが説明するのは一つ目の会話だけで、二つ目については何も説明しない。

マークは問い合わせと一緒には移動しない

パケットのマークは conntrack エントリと、飛んでいる途中のパケットの上に載っている。REDIRECT のルールはその飛行を終わらせる。ペイロードはローカルのソケットに渡され、元のエントリは役目を終える。その後ルーターから出ていくのはリゾルバーのプロセスが作った新しいソケットで、送信元はルーター自身のアドレスである。

だから LAN クライアントのアドレスを鍵にしたポリシールートが上流の問い合わせに一致することは決してない。新しいソケットはクライアントではなく dnsmasq に属しており、カーネルはルーター自身の通信として経路を評価する。送信元ルール、インターフェースルール、マークのルールはどれも違うタプルを見ている。マークが伝わるものと決めてかかるのがこの構成で最も多い誤りで、クライアント側のキャプチャでは見えない。

二つの会話は確かに名前とタイミングを共有しており、だからこそキャプチャをざっと眺めると一つのフローに見える。

転送がローカル解決に変わる場所

まず、ポート 53 がリダイレクトされているのか、別のホストへ変換されているのか、そのまま放置されているのかを突き止める。三つの場合は振る舞いが違う。ローカルのポートへのリダイレクトは転送を終わらせるが、別のマシンへの変換はパケットを動かし続け、クライアントのタプルを意味あるものに保つ。

両方のプロトコルを見る。UDP と TCP の両方で到達できるリゾルバーは両方で試されるし、UDP だけを対象にしたルール群は大きな応答のために静かな穴を残す。ルーター自身の設定マネージャーが生成するルールも同様に数えられ、生成されたファイルを編集しても次の再読み込みに負けるのが普通だ。

sudo nft list ruleset | grep -n -B2 -A4 'dport 53'
sudo iptables -t nat -L -n -v | grep -E ':53|dns'
sudo ss -lunp | grep ':53'

これらの出力は三つの問いに順に答える。何が問い合わせを書き換えるのか、何がそれを受け取るのか、そして待ち受けソケットを持っているのはどのプロセスか。持ち主が分かれば、上流のポリシーにも置き場所ができる。

リゾルバーに専用の出口ポリシーを与える

一部の名前だけがトンネルを必要とするなら、ドメイン単位の上流が最もすっきりした道具になる。dnsmasq はドメインに限定した server 行を受け付け、任意でインターフェースや送信元アドレスを指定して、選んだデバイスから問い合わせを出せる。この誘導があるからこそ、二つ目の会話が VPN をたどる。

server=/corp.example/10.30.0.53@tun_work
log-queries

上のドメイン、アドレス、インターフェースは自分の値に置き換えるための目印である。ドメインの選択と出口の制御は別々の判断のままだ。経路が存在し、送信元アドレスが上流のリゾルバーに許可され、ファイアウォールが応答を許さなければならない。インターフェースによる誘導だけでは fail-closed ではない。トンネルが消えれば、リゾルバーが別の経路に落ちることがあるからだ。公の代替経路は、私的に保つべき名前空間から遠ざけておく。

その行は生成されたファイルではなく、ルーターが対応している設定の持ち主に置く。そうしないと次の再読み込みで修正が消える。

共有キャッシュが保てないもの

一つのキャッシュを持つ一つのフォワーダーがすべての LAN クライアントに応えるので、一部のネットワークが必要とする区別が潰れる。二人のクライアントが同じ名前を別々に解決しなければならないなら、共有ソケットにはどちらが尋ねたのかを取り戻す手立てがない。クライアントごとのポリシーは、別々のリゾルバーのインスタンスか、身元を保つ転送機能の中に置くしかない。

キャッシュされた応答も、新しい経路についての証拠にはならない。上流の経路が壊れたままでも、応答はメモリから返ってくる。

ポート 53 のルールは、DNS を HTTPS や TLS の上で話すアプリケーションについては何も言わない。そうした問い合わせはリダイレクトに決して届かないので、ポート 53 だけに基づくポリシーは通信の一部分を記述しているにすぎない ということを明記しておくべきだ。

確かめるべきは二つ目の会話で、一つ目ではない

自分が管理する名前を一つ使って両側を見る。クライアント側のキャプチャは問い合わせが LAN を出たことを示すだけで、そのあとどこへ行ったかを示すのはルーター側だけだ。

sudo tcpdump -ni tun_work udp port 53
ip route get 10.30.0.53
dig +short corp.example @10.30.0.53

試すたびにキャッシュを消す。温かい応答は壊れた上流を隠してしまう。TCP で繰り返し、次にトンネルを落として繰り返し、好んで使う経路が消えたときリゾルバーがどう振る舞うかを見る。その最後の振る舞いが、設計が fail-closed なのか、ただ運がいいだけなのかを決める。

関連記事