デバイスで shell を取って /etc/resolv.conf を読むか、古い net.dns1 の値を掘り出すと、アドレスが一つ返ってきます。目の前のアプリはまったく別の経路で解決しています。現在の Android では resolver はネットワークごとで、システムは Network ごとにポリシーを保持し、要求を出した socket に適用します。ファイルも旧プロパティも残骸であり、どちらもクエリが実際にたどった経路を説明しません。

この隙間は split-tunnel と Private DNS の展開時に、同じ端末の二つのアプリが正当に違う答えを見る場合に現れます。本稿は誤った、あるいは欠けた解決から始め、それを所有する設定へ遡ります。

resolver はデバイスではなくネットワークに属する

Android 9 以降の名前解決は、socket がバインドされた Network を基準にするシステム resolver を通ります。各ネットワークは独自の LinkProperties を持ち、そこにはそのネットワークだけの DNS サーバー一覧、検索ドメイン、Private DNS の状態が入ります。そのモデルに、ファイルから読めるデバイス全体の一つの一覧は対応しません。

VPN はこの分離を明快にします。トンネルが上がると、VPN アプリは VPN 自身のネットワークに登録された DNS サーバーを提供するため、VPN 経由でルーティングされるアプリはそれらのサーバーで解決し、除外されたアプリは下のネットワークを使い続けます。一台のデバイス、二つの応答経路、矛盾はありません。

影響を受けているアプリの内側からスナップショットを読む

アプリ自身に聞いてください。Context と通常の android.permission.ACCESS_NETWORK_STATE 権限を持つメソッドは、システムプロパティに触れずにスナップショットを読めます:

val cm = getSystemService(ConnectivityManager::class.java) ?: return
val network = cm.activeNetwork
val links = network?.let { cm.getLinkProperties(it) }

val dnsServers = links?.dnsServers
val privateDnsActive = links?.isPrivateDnsActive
val privateDnsName = links?.privateDnsServerName

activeNetwork はあなたのプロセスの既定ネットワークであり、デバイス唯一のネットワークではなく、呼び出し側に使える既定がなければ null になり得ます。既定 VPN のブロックがよくある理由です。isPrivateDnsActive はそのネットワークのクエリが TLS で名前付き resolver へ行くかを示し、単純な UDP 53 プローブが見せるものを変えます。API の表面は LinkProperties の下に文書化されており、その中のどの値も設定のスナップショットであって、特定のクエリが特定のサーバーに届いた証拠ではありません。

既定ネットワークが仕事をしているネットワークでないとき

スナップショットが答えるのは既定についてだけです。アプリは自プロセスや一つの socket を別のネットワークにバインドでき、その場合解決はその選択に従いますが、診断は依然として既定を報告します。独自の DNS-over-HTTPS resolver を積んだブラウザーはシステムの一覧をまったく参照しないので、その答えはデバイスの残りについて何も語りません。仕事用プロファイルのアプリは別ユーザーの下で独自のポリシーで動きます。サーバーを責める前にバインドを確認してください。

VPN の統合が残り半分です。dhcp-option DNS の行を OpenVPN プロファイルで受け取ることと、そのアドレスを Android の VPN ネットワークに登録することは別々の手順であり、サーバーへの経路が欠けると誤ったアドレスと同じくらい静かに失敗します。開発マシンから adb shell dumpsys connectivity はビルドが許すとき同じリンク特性をネットワークごとに出力し、コードを書かずに一覧を確認できます。

設定を、許されている 一回の解決と比べる

プライベートな名前空間で解決されるべき名前と公開名を一つ選び、両方の結果を、ネットワーク、設定されたサーバー、Private DNS の状態、エラーの正確な文言としてスナップショットの横に記録します。名前付きサーバーへの直接クエリは、そのサーバーがあなたに答えることを証明しますが、通常のアプリ解決がそれを選んだことは証明しません。キャッシュされた答えは新しいテストではないので、キャッシュを消すか、今日誰も問い合わせていない名前を使ってください。

resolver が特定のアクセスネットワークのクライアントだけに応答するなら、VPN 出口の後にそれが見るものが結果を左右し得ますが、これは運用者にとってのポリシーの問題であり、通信事業者が UDP 53 を遮断している証拠ではありません。Private DNS が有効な場所では、非修飾のホスト名が設計上拒否されることがあり、同じサーバーは公開名には通常どおり答えます。プロパティを読めばどのモードが有効か分かるので、パケットフィルターの推測をやめられます。

不整合を所有する層を直す

グローバル状態を編集するのではなく、上へたどってください。アプリが誤ったネットワークにバインドされているならバインドを直します。プロファイルが誤った DNS オプションを配るならプロファイルを直します。resolver があなたのプライベートゾーンを扱えないネットワークで Private DNS が厳格なら、それはデバイス管理の決定であり、機能をどこでも切る理由ではありません。

公開 resolver を DHCP フックやシステムプロパティに押し込むのは、事故を絶えず生む修理です: プライベート名が解決されなくなり、変更は VPN セッションより長く残り、次にそのデバイスを使う人は所有者のいない古いアドレスを見つけます。変更はその不整合を所有する VPN、アプリ、管理層の内側に保ち、その後スナップショットを読み直してサーバー一覧が実際に変わったことを確かめてください。

最終的に欲しいのは、一つのネットワーク、resolver 一覧、Private DNS の状態に紐づいた一つのアプリ要求です。その連鎖を示せれば、ファイルの中の見慣れたアドレスは何の証拠でもなくなります。

関連記事