暗号化されたDNSサーバの探索

2025年07月10日 木曜日


【この記事を書いた人】
山本 和彦

Haskellコミュニティでは、ネットワーク関連を担当。 4児の父であり、家庭では子供たちと、ジョギング、サッカー、スキー、釣り、クワガタ採集をして過ごす。

「暗号化されたDNSサーバの探索」のイメージ

通信のプライバシを守るために、DNSのトラフィックも暗号化されるようになってきました。具体的には、以下のようなプロトコルを用いて、DNSの通信を暗号化します。

  • DoT(DNS over TLS): RFC 7858、TCP ポート853
  • DoQ(DNS over QUIC): RFC 9250、UDP ポート853
  • DoH (DNS over HTTP): RFC 8484、HTTP/2の場合はTCP ポート 443、HTTP/3の場合はUDP ポート 443

以下のような、みなさんがご存知のパブリックDNSサーバでも、これらのプロトコル(の一部)がサポートされています。

  • CloudFlare (one.one.one.one, 1.1.1.1)
  • Google (dns.google, 8.8.8.8)
  • Quad9 (dns.quad9.net, 9.9.9.9)
  • AdGuard (unfiltered.adguard-dns.com, 94.140.14.140)

ちょっとややこしいのは、これらのサーバは、基本的には暗号化されてないDNSサーバであり、暗号化されたDNSサーバは別のホストで運用されている可能性があることです。また、暗号化されたDNSサーバは上記の標準的なポート以外を使っているかもしれませんし、DoHの場合は標準的でないURIのパスを利用していることも考えられます。

このような柔軟な運用を許すには、クライアントは、暗号化されたDNSサーバのIPアドレスと、それらに付随したパラメータを入手する必要があります。この記事では、その方法について説明します。

DNR と DDR

暗号化されてないDNSのIPアドレスを自動的に得るには、IPv4ネットワークではDHCP(Dynamic Host Configuration Protocol)、IPv6ネットワークではRA(Router Advertisement)やDHCPv6を利用するのが一般的です。これらのプロトコルに対して、暗号化されたDNSのIPアドレスやパラメータを配布するための拡張は、DNR(Discovery of Network-designated Resolvers) (RFC 9463)と呼ばれています。

一方で、パブリックDNSを指定する場合は、手書きしたり、プロファイルを読み込んだりすることが多いでしょう。この暗号化されてないDNSサーバに、暗号化されているDNSのIPアドレスやパラメータを問い合わせる仕組みはDDR(Discovery of Designated Resolvers)(RFC 9462)と呼ばれています。今回は、DDRについて説明します。

余談ですが、DNRでは「暗号化されたDNSサーバの名前」も配布します。また、DDRでは「暗号化されたDNSサーバの名前」から、「暗号化されたDNSサーバのIPアドレス」を解決可能です。このため、DNRで得た「暗号化されたDNSサーバの名前」を用いて、DDRで「暗号化されたDNSサーバのIPアドレス」を解決することもできます。

SVCB リソースレコード

暗号化されたDNSのIPアドレスやパラメータは、SVCB(Service Binding)リソースレコード(RFC 9460)で記述します。DHCPやRAでは、SVCBリソースレコードを格納するための拡張が定義されています。

DDRでは、暗号化されてないDNSが、特殊な問い合わせに対して SVCB リソースレコードを返答します。特殊な問い合わせとは、ドメイン名として “_dns.resolver.arpa” を指定することです。dugで、CloudFlare(1.1.1.1)の暗号化されたDNSサーバを調べてみましょう。

% dug @1.1.1.1 _dns.resolver.arpa svcb
...
;; ANSWER SECTION:
_dns.resolver.arpa. 300(5 mins) IN  SVCB    RD_SVCB {svcb_priority = 1, svcb_target = "one.one.one.one.", svcb_params = {alpn=["h2","h3"], port=443, ipv4hint=[1.1.1.1,1.0.0.1], ipv6hint=[2606:4700:4700::1111,2606:4700:4700::1001], dohpath="/dns-query{?dns}"}}
_dns.resolver.arpa. 300(5 mins) IN  SVCB    RD_SVCB {svcb_priority = 2, svcb_target = "one.one.one.one.", svcb_params = {alpn=["dot"], port=853, ipv4hint=[1.1.1.1,1.0.0.1], ipv6hint=[2606:4700:4700::1111,2606:4700:4700::1001]}}
...

(... は文字列が省略されていることを表します。)

たとえば、プライオリティが1であるエントリから、暗号化されたDNSサーバに関して、以下のことが分かります。

  • 暗号化されたDNSサーバの名前は one.one.one.one
  • TLSのALPNは、h2 (HTTP/2) と h3 (HTTP/3)
  • ポートは 443 (h2 の場合は TCP、h3 の場合は UDP)
  • IPv4アドレスは 1.1.1.1 と 1.0.0.1
  • IPv6アドレスは 2606:4700:4700::1111 と 2606:4700:4700::1001
  • URIのパスは “/dns-query{?dns}”

もし、お使いのOSのリゾルバがDDRに対応しているなら、以下のように動作します。

  • 指定されたIPアドレスに対して、“_dns.resolver.arpa” の SVCB リソースレコードを引く
  • 検索結果が正常なら、名前解決に関して、得られた暗号化されたDNSサーバを利用する
  • そうでなければ、名前解決に関して、指定されたIPアドレスを平文で利用する

DDRの利用

最近のmacOSでは、DDRが有効になっています。つまり、特に設定しなくても、指定されたDNSサーバに SVCBリソースレコードを問い合わせます。「システム設定」を使って、暗号化されてないDNSに上記のパブリックDNSサーバを設定すると、暗号化されたDNSサーバが自動的に利用されます。

Windowsでも、同様にDDRが有効になっているようですが、私は試せていません。詳しい方がいたら教えてください。

LinuxでDDRを利用する方法は、発見できませんでした。そこで、Linux で DDR を利用可能にするために、ddrd (Discovery of Designated Resolvers daemon)というローカルで動くプログラムを作ってみました。暗号化されてないDNSサーバを指定して起動したddrdは、以下のように動作を繰り返します。

  • 127.0.0.1:53 でローカルのリゾルバからの DNS クエリを読み込む
  • 暗号コネクションがない場合、指定された暗号化されてないDNSサーバにSVCBリソースレコードを問い合わせる。そして、暗号化されたDNSサーバに対して暗号コネクションを張る
  • 暗号化されたDNSサーバへDNSクエリを転送し、DNSレスポンスを得る
  • DNSレスポンスをローカルのリゾルバに送り返す

並列化されたパイプラインを使っていますので、あるクエリが他のクエリを待たせることはありません。興味のある人は、ddrdを試してみてください。

Designated とは何か?

DNRにもDDRにも、Designatedという単語が含まれています。おそらく、適切な訳語は「指名」だと思われます。DNRやDDRでいう「指名」とは何でしょうか?

説明に前の例を使います:

  • ユーザが指定した暗号化されてないDNSのIPアドレスは 1.1.1.1
    • SVCB の結果を見れば、暗号化もされている
  • 暗号化されたDNSサーバのIPアドレスは 2606:4700:4700::1111

DoT、DoQ、DoH いずれも、下位で TLS が利用されます。このとき、クライアントが以下の手続きを踏みます。

  1. 暗号化されたコネクションを張るときの相手のIPアドレスは 2606:4700:4700::1111
  2. TLSのSNI(ServerName Indication)は指定しない
  3. 2606:4700:4700::1111 という名前で証明書を検証する
  4. 証明書の SAN (Subject Alternative Name)に 1.1.1.1 が含まれていることを確認する

注目していただきたいのは4)です。暗号化されたDNSサーバの証明書に、暗号化されてないDNSサーバのIPアドレスが含まれていることを確認するのです。これで、2つのサーバが同じ管理下にあり、暗号化されたDNSサーバが指名されていることを確認できます。

2)に関する仕様は発見できていませんが、私が知るすべてのパブリックDNSサーバでは、SNIが指定されてないことを期待していました。

加えて、DoHでは以下の指名も必要です。

  • Host ヘッダ (:authority) に対して 1.1.1.1 を指定する

HTTP上で、暗号化されたDNSサーバに対して、暗号化されてないDNSサーバ名を指定するので、かなり指名されている感がありますね。

おわりに

残念ながら、IIJのパブリックDNSサーバではDDRが利用できません。DoTやDoHをサポートしていますが、暗号化されてないDNSのサービスは提供していないからです。すいません。

山本 和彦

2025年07月10日 木曜日

Haskellコミュニティでは、ネットワーク関連を担当。 4児の父であり、家庭では子供たちと、ジョギング、サッカー、スキー、釣り、クワガタ採集をして過ごす。

Related
関連記事