VPN を切断しても ping 1.1.1.1 は応答し、ページが最終的に諦めるまで、どの名前解決もぶら下がったままになる。そこで cat /etc/resolv.conf を見ると、トンネルの中にしか存在しなかったネームサーバーが、たいていリストの先頭に載っている。その行を消すと十分ほど直ったように見えるが、サスペンド、Wi-Fi ローミング、再起動のいずれかで元に戻る。編集したファイルは設定そのものではない。それは別のコンポーネントが所有するリンク状態の描画にすぎない。

古いエントリ一つが問い合わせごとに数秒を奪う理由

glibc のリゾルバはネームサーバーのリストを順にたどり、次のサーバーを試す前にタイムアウトの経過を待つ。そして resolv.conf(5) はそのリストを三件までに制限する。トンネルが消えたあと、10.8.0.1 のようなトンネル側リゾルバをリストの先頭に置くと、応答するサーバーに落ちるまで、どの問い合わせもタイムアウトを払う。症状が DNS エラーではなく切断のように見えるのはそのためだ。ブラウザーは固まり、時間切れになり、やがて古くなるキャッシュ済みアドレスを返すこともある。

「インターネットが壊れた」という苦情が届く理由もこれで説明できる。IP アドレスへの ping が通ればリンクは正常だと分かるので、不具合はアプリ側に見える。リゾルバのリストは最初に読むべきものであり、最後に編集すべきものだ。

俗説:resolv.conf は設定ファイルである

変更する前に、そのパスが実際に何なのかを確かめよう。

ls -l /etc/resolv.conf
readlink -f /etc/resolv.conf

systemd-resolved のホストでは 127.0.0.53 を指すスタブファイルへのシンボリックリンクで、実際のサーバーは resolvectl が表示するリンクごとの状態に住んでいる。NetworkManager のホストでは、NetworkManager がアクティブな接続から書き出すファイルかもしれない。一部のベンダークライアントでは、接続時にその場で書き換えられ、運が悪ければ切断時に決して復元されない単なるファイルだ。これは三者が異なる所有者であり、最初の二つを手で編集しても何も起きないか、次のネットワークイベントまでしかもたない。

resolvectl status は真実を語るビューだ。リンクごとの DNS サーバー、ルーティングドメイン、トンネルリンクがまだ存在するかが分かる。resolvectl query example.com はさらに進んで、どのインターフェースが応答したかを示す。ファイルに触る前に両方を使い、独自のセキュア DNS を持つブラウザーはこれらすべてを無視することを覚えておこう。

何かを編集する前に所有者を突き止める

通常は三つのコンポーネントが同じ設定を奪い合う。systemd-resolved、NetworkManager、そして VPN クライアントが導入したものだ。まず生きているものを特定する。

systemctl is-active systemd-resolved
nmcli connection show --active
ps aux | grep -E "openvpn|wg-quick|vpn"

resolved が稼働中でシンボリックリンクがそのスタブを指しているなら、グローバルファイルは生成された出力であり、編集は見かけだけのものだ。NetworkManager が接続を所有しているなら、サーバーは接続プロファイルに属するので、修正はファイル編集ではなく nmcli のプロパティ変更になる。ベンダーのデーモンが動いているなら、次の接続を生き延びるのはその独自の復元経路だけだ。

クライアントが残したリンク状態を片付ける

殺されたクライアントはクリーンアップフックを実行できない。SIGKILL はまったく捕捉できず、SIGINT と SIGTERM は捕捉できる。そのため強制終了後に「ただ止まった」トンネルは、インターフェースの登録とリゾルバのエントリの両方をそのまま残すことが多い。特定の PID を見つけて、そのプロセスだけにシグナルを送ろう。

ps aux | grep -E "openvpn|wg-quick"
sudo kill <pid>
resolvectl revert tun0

一致リスト全体を読む前に pkill -f vpn や killall vpn に手を伸ばしてはいけない。-f はコマンドライン全体に一致するので、ラッパースクリプト、エディターのセッション、あるいはパスにその文字列を含む無関係なプロセスまで終わらせてしまう。作業の途中で間違ったプロセスを終わらせることが、リゾルバの問題を失われたファイルに変える道だ。

resolvectl revert はそのリンクの実行時 DNS 設定をリセットする。リゾルバの再起動より狭く、グローバルファイルの編集よりずっと狭い範囲だ。トンネルリンクが完全に消えているなら、systemctl restart NetworkManager が接続状態を再構築する。ソースのスレッドが何度も戻ってくるのはこの手順だ。Restart=always を持つサービスマネージャーには注意が必要で、殺されたクライアントとその DNS 設定をそのまま戻してしまうことがある。

もう一つの方向:押し込まれたのに届かなかった DNS

古いエントリの鏡像は、使えるリゾルバがまったくないトンネルだ。プロファイルの DNS オプションは要求であり、接続を管理するコンポーネントがそれをリゾルバに渡さなければならない。ログにサーバーは受け取ったと出ているのに、トンネルリンクが DNS 設定を一切運んでいないなら、欠けているのは連携であり、サーバーではない。

手作業で引き渡して試す。これで「サーバーが間違っている」と「誰も適用していない」を切り分けられる。

resolvectl dns tun0 10.10.10.10
resolvectl domain tun0 ~.

すべてを捕まえるルーティングドメインは、あらゆる名前をそのリンクへ送る。フルトンネルのクライアントが望む設定であり、分割 DNS が使ってはいけない設定だ。これらの設定は次の接続で消える。NetworkManager で永続化するには、自動 DNS を無効にし、接続にサーバーを設定し、ドメインを追加する。

nmcli connection modify office ipv4.ignore-auto-dns yes
nmcli connection modify office ipv4.dns 10.10.10.10
nmcli connection modify office ipv4.dns-search "~."

社内の名前だけに応答すべき社用トンネルでは、すべてを捕まえる代わりに、先頭にチルダを付けてそれらのゾーンを列挙する。こうすれば公開名はローカルのリゾルバを使い続ける。

次の障害になる回避策

接続前に resolv.conf をコピーし、あとで戻すのはランブックとしては正当な一行だが、修理ではなく回避策だ。クラッシュや強制再起動は復元を飛ばすので、本当に必要なときに失敗する。次に読む人がその上に積み上げないよう、回避策として書き残しておこう。

systemd-resolved を無効にして公開サーバーを固定するのも人気の一手だ。接続と切断が予測可能になり、ほかのすべてのネットワークからリンクごとの DNS が消える。その結果、社内名の分割 DNS が壊れ、問い合わせを内側に留めるためのトンネルの目的が台無しになる。resolvconf を入れて公開サーバーを固定するのも、層が一つ増えるだけで同じ費用がかかる。どちらも診断として扱おう。閲覧が戻ったなら、公開サーバーが正しいのではなく、リゾルバの連携が原因だと分かったのだ。

持続する修正は層ごとに所有者を一つにすることだ。トンネルリンクの DNS をどのコンポーネントが持ち、グローバルの既定値をどれが持つかを決めて書き留め、ほかを経路から外す。そのうえで切断後、VPN でしか解決できない名前で確認する。社内名がまだ応答するなら登録が消えていない。何も解決しないなら、ファイルはもう存在しないサーバーを指している。どちらもブラウザーを開く前に resolvectl で見える。

関連記事