VPN が接続した瞬間に名前の解決が止まる一方、公開アドレスへの ping はまだ応答し、ファイアウォールの状態には異常に見えるものは何も出ていません。ポリシーは何も変わっておらず、変わったのは新しいインターフェースが現れたことで、デフォルトの拒否が最初のパケットからそれを覆っています。UFW は方向とインターフェースで判断するため、無線リンク向けに書かれたルールはトンネルを出ていく通信について何も語りません。修正は、特定の順序で適用するいくつかの狭いルールです。

症状はトンネルが上がった後にだけ現れる

デフォルト拒否のポリシーは、言われたとおりに動いており、一致するルールのないものは存在するすべてのインターフェースで拒否されます。ルールを書いた時点でトンネルは存在しなかったため、どのルールもそれに一致できず、もともとあった拒否が今は名前解決に適用されています。リテラルなアドレスへの直接の通信は、より広い許可がそれを覆っていればまだ動くことがあり、だから公開リゾルバーへの ping は成功するのに名前の検索は失敗します。抜けているのは、ポリシーが見たことのないインターフェース向けのルールです。

これは、問い合わせがルールを持つインターフェースに戻るため、トンネルを落とせば名前がまた解決する理由でもあります。トンネルが上がっている間だけ失敗するポリシーは DNS の問題ではなく、リゾルバー自体を調整するのがここで最も時間を無駄にします。リゾルバーの設定を読む前に、インターフェースの一覧を読んでください。

三つの方向、三つの異なるルール群

一つのセッションは複数の方向でルールに触れ、トンネルはそのほぼすべてに二つ目のインターフェースを加えます。通信をまず方向とインターフェースで整理すれば、下の各行がそれぞれ自分の場所で自分のルールを必要とするため、残りは機械的になります。例では tun0 と 10.8.0.0/24 のプールを使っているので、名前と範囲はクライアント自身の出力にあるものに置き換えてください。

DirectionExample packetRule that admits itWritten on
Client to the pushed resolverA DNS query to 10.8.0.1Destination port 53 over the tunnelThe client's tun0
Client to the VPN serverA handshake to the server's public addressServer address and port on the physical linkThe client's physical interface
Server to a serviceSSH from the pool to port 22Source pool, destination port, tunnel interfaceThe server's tun0
Anyone to the listenerThe VPN listener itself1194/udp on the public sideThe server's public interface

二つの行は、どちらもトンネルに触れているためにしばしば混同されます。リゾルバー向けのルールはクライアントがトンネルに送る通信を許可し、SSH 向けのルールはサーバーがそこから受け取る通信を許可しており、両者は別のマシンにあり独立して失敗します。最も忘れられやすい行は二つ目であり、サーバーの公開アドレスへの通信が許可されるまでクライアントはトンネルをまったく確立できないからです。上から下へ作業すれば、どの失敗にも候補となる原因が一つあります。

プッシュされたリゾルバーには独自のルールが必要

トンネルが DNS を運ぶかどうかは、サーバーが何を送るかで決まります。リゾルバーのオプションをプッシュするサーバーは、クライアントの設定にプライベートアドレスを入れ、そのアドレスはトンネル経由でしかルーティングできません。サーバーが何もプッシュしなければ、クライアントは元からあるリゾルバーを保ち、下のルールには許可するものがありません。何かを書く前に、プッシュされた状態と現在の問い合わせ結果を確認してください。

dig +short @10.8.0.1 example.com
resolvectl status tun0
sudo ufw status numbered

状態の出力は使用中のリゾルバーとそれが結び付いているリンクを示し、その背後の経路検索はどのインターフェースがそのアドレスへの問い合わせを運ぶかを示します。リゾルバーが別のリンク上にある場合、問題はファイアウォールより前の段階で、どんなルールも助けになりません。両者がトンネルについて一致するなら、問い合わせの大半は UDP が運び、応答が切り詰められると TCP が引き継ぐため、二つのルールが必要です。ルールが名指しするのはその値なので、両方の値を記録してください。

sudo ufw allow out on tun0 to 10.8.0.1 port 53 proto udp comment 'pushed resolver'
sudo ufw allow out on tun0 to 10.8.0.1 port 53 proto tcp comment 'truncated answers'

内側より先に外側の経路を開く

クライアントはトンネルが存在する前にサーバーの公開アドレスへ届かなければならないため、そのルールは物理リンク上にあり、最初に整えておく必要があります。リゾルバーは許可するがサーバーを忘れるポリシーでは、クライアントはトンネルをまったく構築できず、その症状はたいてい kill switch のせいにされます。そのあと、実際にトンネル越しに使いたかったサービスを、インターフェースを丸ごと開くのではなく、狭いルールを一つずつ許可します。狭いルールこそが、拒否をそのまま残せる理由です。

sudo ufw allow out to 198.51.100.10 port 1194 proto udp comment 'vpn server'
sudo ufw allow out on tun0 to any port 443 proto tcp comment 'web through tunnel'

試験中は、トンネルをそもそも上げられるのが最初のルールだから、順序が重要です。それを追加して接続し、両方を一度にやると失敗してもどちらの半分が悪いのか分からなくなるため、サービスのルールを追加する前にトンネルが上がっていることを確認してください。上のドキュメント用アドレスは、クライアントが使うよう設定されているエンドポイントに置き換えてください。

わざと切断してクライアント側を仕上げます。トンネルが上がっている間に応答するリゾルバーは疑わしいときに拒否するポリシーの半分しか覆わないため、拒否は意図的に除外した通信を依然として拒否するはずです。

接続したクライアントはまだ受け入れられていない

サーバーには考えるべき自分の方向があり、ハンドシェイクを完了したクライアントも、サーバーが受け入れるまでサーバー上で動くものには一切届きません。公開リスナーのルールはトンネルの存在を許すだけで、トンネルの通信を通すものではありません。サーバー側では、二つのルールに加えて、サービスが実際に待ち受けているかの確認が一つ必要です。

sudo ufw allow 1194/udp comment 'openvpn listener'
sudo ufw limit in on tun0 from 10.8.0.0/24 to any port 22 proto tcp comment 'admin over tunnel'
ss -ltn | grep ':22'

別のインターフェース向けのルールはルールがないのと区別できないため、ルール内のプールはサーバーが割り当てる範囲と等しくなければならず、トンネル番号もサーバーが作ったものと一致しなければなりません。limit は管理用ポートに対して意図的なもので、無制限に受け入れるのではなく、繰り返しの接続を制限します。クライアント側からは同じに見えるため、リスナーの確認は閉じたポートと破棄されたパケットを切り分けます。番号付きのルールも全体像ではなく、UFW の before ルールや手書きの nftables 行はより早く一致するため、一覧が許可しているように見えるパケットでも拒否できます。

関連記事