イベントレポート

DNS Summer Day 2023

WindowsのChromeやEdgeでネットにつながりにくくなる現象、一部の家庭用ルーターが原因かも?

DNSの“TCPクエリ”うまく扱えない機種も存在。ChromeのTCPクエリ送信が引き金に

 日本DNSオペレーターズグループ(DNSOPS.JP)の主催によるカンファレンス「DNS Summer Day 2023」が6月23日に開催された。今回は、事前に話題となっていた、ChromeにおいてTCPを使ったDNSのクエリが出された件を取り上げる。

ChromeがTCPクエリを出したことと、ネットにつながりにくくなったことは切り分けるべき

 本題に入る前に、まず、TCPを使ってDNSのクエリを出す(以降、単に「TCPクエリを出す」と記述する)ということ自体に問題は無いということを確認しておきたい[*1]。また、ユーザー側から見た場合、本質的な問題は、それによって「インターネットへのアクセスに支障が出た」という点にある。なぜ、ChromeはTCPクエリを出したのかという点と、それによってネットにつながりにくくなったという問題が起きたことは別の話である。この点は、明確に切り分けるべきだろう。

 そもそもの話として、この「ChromeがTCPクエリを出している」という話題がネットワーク関係者の関心を引き寄せたのは、2022年11月ごろからChromeにおいてネットにつながりにくくなる現象が発生したことに起因する。その原因を調べたところ、ネットにつながりにくくなる現象は、Windows、かつChrome、もしくはChromeベースのブラウザーにおいてのみ発生するということが明らかになった。

 「DNS Summer Day 2023」で行われた2つの発表――株式会社インターネットイニシアティブ(IIJ)の山口崇徳氏による「ChromeのTCPクエリ問題」と、同じくIIJの草場健氏による「ChromeはなぜTCPクエリを出したのか」は、いずれもChromeがなぜTCPクエリを出したのかについて考察を行ったものだ。GoogleやMicrosoftから公式の発表が無いことから、仮説に基づいて動作を確認し、その因果を考えたものと言えるだろう。以降、山口氏と草場氏の発表を適宜使用しながら今回の話題を解説する。

[*1]…… DNSではUDPを使うのが一般的である(RFC 1123には「MUST send a UDP query first」と書かれていた)が、RFC 5966を経て2016年3月に発行されたRFC 7766により「TCP MAY be used before sending any UDP queries.」と再定義され、UDPを使用せず最初からTCPで問い合わせしてもよいことになった。
https://datatracker.ietf.org/doc/html/rfc1123
https://datatracker.ietf.org/doc/html/rfc5966
https://datatracker.ietf.org/doc/html/rfc7766

TCPクエリでネットにつながりにくくなったのは、主としてホームルーター側の問題

 まず、2022年11月ごろからの経緯を整理したものとして、山口氏のスライド(図1と図2)を見ていただきたい。スライドでは、Chromeを使っているユーザーからのつながりにくいという報告は2023年2月ごろから多くなったと記述しているが、実際には1月後半からすでに多かったということである。特定ISPのユーザーにおいて多く発生したこと、ホームルーターの機種に関係していそうなことなどが書かれている。

図1 何があった?――Chromeでネットにつながりにくくなる現象が発生
図2 起きていたこと――Chromeの出すDNSクエリがなぜか突然UDPからTCPに切り替わる

 その際に提示された回避方法は図3で示された通り。ホームルーターの中にはTCPクエリを正しく扱うことのできないものがあるようで、通信に問題が出たユーザーはホームルーターのDNS機能を使わずにISPのキャッシュDNSサーバーやパブリックDNSを使うようにする、それでも改善しなければ、ファームウェアのアップデートを試すことや、ホームルーターそのものをTCPクエリに対応しているものに交換するようにとのことであった。一方、フルサービスリゾルバー(キャッシュDNSサーバー)を運用している人・組織には、多数のTCPクエリを受けられるかどうかの確認と対応をするよう求めている[*2]

図3 回避方法――TCPクエリの扱いに問題のあるDNSサーバーを使わない

 ここで注目すべきは、TCPクエリが出たことでネットにつながりにくくなるという現象が起こっているという点である。理由を問われれば以下の箇条書きに示した2つの点に要約できるが、本質的には、そのことでネット接続に支障が出てしまう機器を使っていることの方が問題であろう。TCPクエリをきちんと処理できるホームルーターを使っていて、利用しているフルサービスリゾルバーが多数のTCPクエリを処理できる環境下にあれば、今回の問題は表面化しなかった可能性が大きいのである。

  • ホームルーターがTCPクエリを正しく処理できない
  • DNSによる名前解決に失敗し、アクセスしたい先につなげられない

 さて、ここで1つの例として、ホームルーターにおけるHTTPとDNSの扱いなどにも触れておこうと思う。このことを知ることで、なぜそのような回避策となるのか、裏で何が起こっていたのかを知る(想像する)ための一助となるはずである。

 ホームルーターにおけるHTTPとDNSの扱いは、以下のように異なる。ここで注目して欲しいのは、レイヤーの違いとセッションを張る先である。

【HTTPの場合】

  • ホームルーターは、IPパケットのフォワード(転送)を行う
  • このフォワードは、ネットワーク層で行われる
  • そのため、クライアントと外部ホストがTCPセッションを張るかたちになる
    (クライアントは、ホームルーターとはセッションを張らない)

【DNSの場合】

  • ホームルーターは、DNSパケットのフォワードを行う
  • このフォワードは、アプリケーション層で行われる
  • TCPクエリを使用する場合には、クライアントとホームルーターがTCPセッションを張る必要がある

 HTTPでは「IPパケットのフォワード」を行うため、クライアントと外部ホストとの間にTCPセッションを張ることになる。それに対してDNSでは「DNSパケットのフォワード」を行うことになるため、TCPクエリが出された場合にはクライアントとホームルーターの間にTCPセッションを張ることになる。つまり、TCPクエリが発生すると、ホームルーター側が使えるTCPセッションがどれだけあるかが、まず問題になる。

 カンファレンスの発表後のQ&Aで、TCPセッションを1つしか持たないホームルーターがあったらしいといった話題が出ていたが、そのような場合、仮にTCPクエリを処理できたとしても、あるTCPクエリを処理している間は他のTCPクエリを処理できないためにタイムアウトになるなどして名前解決に支障が出ることになるだろう。

 次に、ホームルーター側で正しくTCPクエリを処理できるかという点がある。それは、通信の際にUDPを用いた場合とTCPを用いた場合との違いであるとも言える。TCPを用いた場合には、1つのTCP接続で応答を待たずに複数のクエリを送ってもよいし、TCPセッションを張りっぱなしにしてときどきクエリを送るというような使い方をしてもよいことになっている(そのため、RFCではTCP closeなどについて明確化されている)[*3]。ただし、TCP接続ではパケットの区切れを送ることができないため、DNSデータの前にデータ長を2オクテット(16ビット)で付加して区切りを識別できるようにするという取り決めがある(16ビットのため、扱える最大のデータ長は65535オクテット)。

 DNSクエリとしては同じでも、使用するプロトコルの違いによってDNSデータの取り扱いに違いが生じるわけである。この違いをホームルーターのDNS機能が正しく処理できないと、TCPクエリが飛んできたときに行き詰まってしまうことになる。つまり、名前解決に失敗する。

 説明の都合上、先の箇条書きと説明の順が逆になってしまったが、このような背景があるということは知っておいていただきたい。また、これはおそらくだが、ホームルーターがこのような仕組みのために、同じDNSクエリを出したとしても、クエリをホームルーター(のDNS機能)宛てにするのではなく、外部のフルサービスリゾルバー宛てにすることで、ホームルーター側の制限に引っかかることを回避することができるかもしれない。ホームルーターのDNS機能を使わずにISPのフルサービスリゾルバーやパブリックDNSを使うようにしたことでこの問題を回避できたという話は、この点ではないだろうか。

[*2]…… カンファレンスの最後のライトニングトークおける、株式会社大塚商会の髙野遼氏による「tcp/53 接続を舐めて痛い目にあった話」では、フルサービスリゾルバー運用者の立場から、この問題に際して何が起こり、どう対応したかが語られている。

[*3]…… 前出の脚注で示したRFC 7766を参照してほしい。

TCPクエリが出た原因は、Windowsのポート割当アルゴリズムとChromeのセキュリティ機構の相性問題

 次に、なぜChromeがTCPクエリを出し始めたのかについての調査発表を見ていこう。山口氏の発表では、Windows版ChromeにおいてAsyncDNSをデフォルトで有効にしたことが原因ではないかとしているが、これは、Windows版Chromeに対する変更履歴を追いかけると、変更と問題が報告された時期がちょうどよく一致するからということが理由となっている(図4~図6)。

 AsyncDNSとはChrome内蔵のスタブリゾルバーであり、HTTPSリソースレコードへの対応やHTTP/3を使うための実装だが、実は特別新しいというものではない。MacやAndroid、Linux、ChromeOSのそれぞれの版では、すでに先行してAsyncDNSがデフォルトで有効になっており、そちらではWindows版で起こったような現象は発生していないという。ちなみに、Windows版だけ別扱いになったのは、Chrome側が生成した乱数をソースポートに使うとなぜかファイアウォールの警告が出るからだという話もあるそうだ。この話については、草場氏の資料にその記載がある(図7)。

図4 11月に何があったのか――変更履歴と観測結果が一致する
図5 AsyncDNSとは――Chrome内蔵のスタブリゾルバーをデフォルト有効に
図6 回避方法その2――AsyncDNSが有効にされたことが原因なら、無効にすればよい
図7 MacやLinuxでは?――実装の違いによる挙動の違い

 なぜUDPクエリではなくTCPクエリを使うようになるのかの理由については草場氏の発表が詳しいので、そちらを使用する。まず、ChromeはいきなりTCPクエリを出すのではなく、起動直後はUDPクエリを出すことが確認されている(図8)。

図8 再現させよう――Chrome内蔵のスタブリゾルバーAsyncDNSを使うと再現する

 草場氏は、この理由を知るために愚直にソースコードをあたったそうだ。その結果、「low_entropy()」という関数を用いてTCPとUDPの切り替えが行われている部分を見つけたということである。low_entropy()の実態はフラグであり、Chrome内蔵のスタブリゾルバーAsyncDNSは、OS側のエントロピーが低そうな挙動を確認するとこのフラグを立てる(図9~図11)。

 このケースにおいてエントロピーが低いというのは、DNSにおけるセキュリティの強度が低いという意味と捉えてよい。つまり、UDPソケットの生成をOS側に任せると十分なセキュリティの強度が得られないということである。

 このことを確認するために、最近のDNSでは必須とされるポートランダマイゼーションが行われることを前提に、ソースポート番号の重複という視点から確かめた。その結果だが、あるポート番号については1024回の試行で13回もの重複を観測したそうだ(図12)。つまり、OSが提供するUDPソケットの生成では、ポート番号の選択が十分なランダム性を持たないということである。

 Chrome内蔵のAsyncDNSでは、使われるポート番号が直近256回中2つと重複するとエントロピーが低いと判定される。それを考えると、図12のような結果ではTCPクエリに移行するのはある意味当然なのかもしれない。

図9 なぜTCP?――「low_entropy()」という関数を用いてTCPとUDPの切り替えが行われている
図10 なぜTCP?――エントロピーが足りなさそうな挙動を観測すると「low_entropy()」フラグが立つ
図11 なぜTCP?――挙動と整合性のある説明ができる
図12 試す――1024回繰り返して、ソースポート番号の重複を見てみる

 これは、発表を聞いた筆者の想像だが、おそらくAsyncDNSが有効となっているChromeでは以下の箇条書きのようなことが行われていると考えることができる。また、2.では、パケットを送信するまでの処理で得られた情報を利用して判定していると思われる。

  1. パケットを送信する準備をする
  2. このとき、OSを無条件に信用していないので、事前チェックを行っている
  3. その事前チェックに引っ掛かると、今回のようなことが起こる(TCPクエリに移行する)
  4. 上記の2.で問題があると判定されなければ、OSにパケットを送信するように依頼する

 こうした結果を受けて、Chrome側でソースポート番号の重複は直近256クエリ中2回までとしていたものを、修正版では3回までに緩和することにしたようだ。この修正は3月29日に行われ、5月2日にChrome 113としてリリースされた。ただし、IIJのフルサービスリゾルバーでの観測結果では、TCPクエリは減ってはいるが無くなってはいないとのことである。正直なところ、Windowsがポート番号の再利用を積極的に行っているのであれば、ここでnを2から3に変えたとしても大勢に影響は無いのではないだろうか。

 いずれにしても、このChromeがTCPクエリを出すという問題は、草場氏の言うように「Windowsのポート割当アルゴリズムとChromeのセキュリティ機構の相性問題」と考えるのが妥当であろう(図13)。山口氏も「(スタブリゾルバーへの)ポイズニング防止のためにポート番号をたくさん使いたいChromeと、消費を抑えたいWindowsの衝突」と述べている(図14)。他のOS環境下で今回のような問題(TCPクエリへの移行)が起こっていないのは、この事前チェックで問題があると判定されないからであろう。

図13 まとめ(草場氏)
図14 まとめ(山口氏)

 ちなみに、山口氏の発表では、その後半でWindowsの「ソケットキャッシュ」と呼ばれる機能についても言及している。ソケットキャッシュは、UDPポートの消費を抑えるために、同一サーバーへの通信であればソースポートをキャッシュして同じポートを使い回しするWindowsの機能のことであるらしい。「らしい」というのは、Microsoftの公式な情報が見つからないためである(図15)。

図15 ソケットキャッシュ(1)――Windows特有の機能

 今回は記事での扱いを小さくしたが、このソケットキャッシュという機能は悩ましい存在である。通信をするうえでUDPはNATとの相性が悪いという問題があるのだが、今後、普及が見込まれる「QUIC」という通信プロトコルはそのUDPがベースになっている。Microsoftは、その対策としてNATの負荷を下げることを考えたのであろうが、フルサービスリゾルバーでソケットキャッシュされるとキャッシュポイズニングのリスクが跳ね上がってしまう(図16~図18)。

図16 なぜソケットキャッシュ?(1)――UDPはNATと相性が悪い
図17 なぜソケットキャッシュ?(2)――「QUIC」を見据えた仕組みか
図18 Chrome以外では?

 今回の問題の核心部分は、Microsoftが考えたQUIC対策が、DNSにとっては脆弱性の原因になりかねないということなのかもしれない。極論すれば、IPv4を無理して使うのはそろそろ諦めてIPv6をメインに使ったほうがいいのではとも言えるのであるが、サービス提供側のIPv6対応がなかなか進まないという問題が足を引っ張っているのであろう。このソケットキャッシュについての詳細を知りたい方は、山口氏の公開スライドをご覧いただきたい。

最後に

 今回のDNS Summer Day 2023では、冒頭の「ChromeがTCPクエリを出している」という発表の内容があまりに広範な話題を含んでおり、かつ、さまざまなことが複雑に絡んでいることから筆者自身も悩みに悩んでしまった。含まれる情報も多く、解釈や確認などで手間取ったこともあり、通常の記事執筆よりも大幅に時間がかかってしまったことをここでお詫びしたい。

 とはいえ、他の発表にも興味深いものが多かったことも述べておきたい。DNSOPS.JPの白井出氏による「合併でわかるドメイン名の管理者」は、ドメイン名の管理をする方々に見ていただきたいし、GMOインターネットグループ株式会社の永井祐弥氏による「ランダムサブドメイン攻撃についてドメイン名登録者が出来ること」は、ドメイン名登録者に見ていただきたい内容である。

 また、スポンサーセッションではあるが、アカマイ・テクノロジーズ合同会社の松本陽一氏による「あたらしい dig」は、おそらくDNS管理者と思われる方々に大受けであった。

 Q&Aについても活発で、一人の参加者としてとても楽しめた。DNSOPS.JPでは「今後実施するイベントもハイブリッドを含めて開催形態を検討する予定なので、次回開催の案内をお待ちいただきたい」とのことであった。夏のイベントとして、しっかりと定着した感があるDNS Summer Dayに今後も期待したい。