WireGuard クライアントの DNS クエリを別のリゾルバーに向けるのは、一つのルールに見えて三つの判断のように振る舞う。宛先を書き換え、パケットを転送し、応答がゲートウェイの作った変換状態を通って戻る道を見つけなければならない。三つ目を飛ばすと、クエリはきれいに出ていくがクライアントは永遠に待つ。

10.77.0.0/24 の許可されたクライアントが wg0 から到着し、広告されたアドレス 10.77.0.1 を問い合わせ、203.0.113.53 のリゾルバーを必要とする例を考えよう。上流アドレスはドキュメント用のプレースホルダーであり、稼働中のサービスではない。

クエリのリダイレクトが変えるのは宛先だけ

PREROUTING の宛先 NAT ルールは、ルーティング判断の前にアドレスを書き換えるので、パケットはその後、元の宛先ではなく新しい宛先へルーティングされる。クライアントの送信元アドレスはその操作では変わらない。だから完全に見えるルールでも沈黙を生むことがある。要求はいまや、クライアントのプライベート範囲を知らないリゾルバーへ向かう。ゲートウェイは一つのフィールドを変換し、二つ目の問題を引き継いだ。

DNS の両トランスポートに一致するルールが必要だ。応答が切り詰められるとクライアントは TCP にフォールバックするからだ。

以下のルールは、一つの受信インターフェース、一つのクライアント範囲、一つの元の宛先アドレスを選ぶ。ゲートウェイ上の他の何も影響を受けず、ゲートウェイ自身からのクエリはまったく一致しない。

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -i wg0 -s 10.77.0.0/24 -d 10.77.0.1 -p udp --dport 53 -j DNAT --to-destination 203.0.113.53:53
iptables -t nat -A PREROUTING -i wg0 -s 10.77.0.0/24 -d 10.77.0.1 -p tcp --dport 53 -j DNAT --to-destination 203.0.113.53:53

応答経路は別の判断

応答を戻す方法は二つあり、早めに一つ選べばデバッグの一手間が省ける。上流リゾルバーがこのゲートウェイ経由で 10.77.0.0/24 への戻り経路を持つか、ゲートウェイが出口で送信元を変換して上流がすでに知るアドレスに応答が届くかのどちらかだ。どちらも動く。上流に対する制御と合うのは一方だけだ。

まずはルーティングを試す価値がある。クライアントのアドレスをリゾルバーから見えるままにし、二層目の変換を避けられるからだ。上流が自分の制御外にあるなら、特定の出口インターフェースでの送信元変換が現実的な選択だ。

その変換はリダイレクトと同じくらい狭く限定しよう。さもなければルールは、たまたま一致したものすべてに対する汎用 NAT になる。

iptables -t nat -A POSTROUTING -s 10.77.0.0/24 -d 203.0.113.53 -o eth0 -p udp --dport 53 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 10.77.0.0/24 -d 203.0.113.53 -o eth0 -p tcp --dport 53 -j MASQUERADE

コネクション追跡が対応付けを対で保つので、戻りでは応答がクライアントの元のタプルに書き戻される。出口ルールがなければ、リゾルバーは到達できないプライベートアドレスへ応答を送り、クライアントはエラーではなくタイムアウトを見る。

ルールは見た目より狭く書く

転送の許可も同じ範囲に属する。マシン上のすべての DNAT 接続を受け入れるのではなく、クライアントからリゾルバーへの流れと、conntrack がすでに認識する戻りのトラフィックを許可しよう。包括的な許可は、DNS と無関係なポート転送まで認めてしまう。

ゲートウェイ自身のリゾルバーは別のコード経路だ。ローカルで発行したクエリは OUTPUT を通るので、ゲートウェイのシェルからのテストは PREROUTING ルールも転送ポリシーも試さない。だからローカルのテストが通りながら全クライアントが失敗する。逆も同じくらいよくある。

ここではポート 443 の HTTPS 上の DNS も、ポート 853 の TLS 上の DNS も扱わない。IPv6 も扱わない。IPv6 リゾルバーを設定したクライアントは、この仕組み全体を喜んで無視する。

新しいクエリが証明すること

コネクション追跡は以前の対応付けを覚えているので、ルール変更前に送ったクエリが古い状態で応答され、新しいルールが壊れて見えることがある。新しい送信元ポートで始め、クライアントが応答をキャッシュする場合は新しい名前で始めよう。

dig @10.77.0.1 example.com A +short
conntrack -L -p udp --dport 53 | head

最初のコマンドはクライアント経路がそもそも解決するかを示し、二つ目はゲートウェイが実際に設定した変換を、タプルの両方向を含めて示す。正しいリゾルバーを指しているのに応答カウンターがゼロで止まっている対応付けは、フィルターの問題ではなく戻り経路の問題だ。

何かをリダイレクトする前に、リゾルバーが期待する名前を知っているか確認しよう。公開名には応答するがあなたのプライベートゾーンには応答しない代替 は機能する検索を、壊れたトンネルのように見える NXDOMAIN に変える。クライアント経路が関わる前に、ゲートウェイから直接リゾルバーを試そう。そして iptables 拡張のリファレンス は、これらのルールが依存する DNAT と masquerade の意味論を記述している。

関連記事