RFC 7766: DNS Transport over TCP - Implementation Requirements
こんにちは、まれいんです。
この記事は ひとりRFCの旅 Advent Calender 15日目の記事です。
本日はDNS over TCPについて書かれた、RFC7766を読んでいきます。
概要
DNS初期はUDPでの通信が基本で、TCPはあくまでTruncateしたとき(=response sizeがオーバーしたとき)にだけ使われるものでした。 しかしDNSSECの影響でresponse sizeが大きくなったこと、EDNS0でbuffer sizeは拡張されたもののfragmentした応答が信用できないことが分かってきました。(第1フラグメント便乗攻撃や、一部の実装ではreassembleしないらしい)
そのため、このRFCでは主に以下の2つの観点でアップデートが行われました。
- TCPをUDPと等価に扱うようにすること
- 一部の記載では「TCPでの実装はoptional」「UDP優先で解決する」などがありましたが、TCPもUDP並みの重要度で扱われるようになりました。
- TCPの効率をよくするためのテクニックの導入
- 単にTCPを使うとRTTが長くなり効率が落ちますが、分かりやすい例だとconnection reuseなどで性能向上を推奨するようになりました。
1. TCPをUDPと等価に扱うようにすること
シンプルにTCP周りの実装要求がほぼほぼ厳しくなりました。
- 権威サーバ、キャッシュサーバ、forwarder、スタブリゾルバはそれぞれ、TCPのサポートがMUSTになりました
- 今までは名前解決はとりあえずUDPで尋ねてから、ダメだった場合にTCPで尋ねるように規定されていましたが、最初からTCPで尋ねてもよいことになりました。
2. TCPの効率をよくするためのテクニックの導入
以下のテクニックが導入されました。
- Connection reuse
- Query pipelining
- Concurrent connection
- idle timeout
- TCP fast open
Connection reuse
3-way handshakeの都合でRTTが大きくなってしまいがちなので、1つのTCP connection内で複数のクエリを送ってよいことになりました(SHOULD)
ちなみにTCPでDNS packetを送るときは、まず最初の2bytesでquery/responseの長さを送ってからpayloadを送信するようになっています。(TCP message length field)
Query pipelining
クライアントが1つのTCP connectionで複数のクエリを投げるとき、最初のクエリの応答を待ってから次のクエリを送るのではなく、応答を待たずに立て続けにクエリを送っていいことになりました。
またサーバはpipeliningされた複数のクエリの応答をクエリが来た順番で返さなくてよくなりました。(out-of-order response) つまり、
クエリ1 --> クエリ2 --> クエリ2の応答 --> クエリ1の応答
という挙動が許されるようになりました。
Concurrent connection
クライアント側はサーバの過負荷を抑えるために同時に張るTCP connection数を出来るだけ少なくするように要求されるようになりました。(MUST)
一応、DNS over TCPで1つ、Zone transferで1つ、DNS over TLSで1つ、みたいにして複数張るのはOKのようです。
idle timeout
クライアント側はサーバの過負荷を抑えるために不要なTCP connectionを少なくするように要求されるようになりました。
サーバ側もidle timeoutの概念自体はありましたが、当時のtimeout値の目安として2分くらいと掲げられていました。 これが秒のオーダーで設定するように改められました。さらに、リソース状況に応じてtimeout値を変えてよく、負荷が高い場合は0秒でもよいことになりました。
またconnectionを切るのはクライアント側の仕事でしたが、サーバ側からもやっていいようになりました。
クライアント側はクエリを送ったのに応答が得られない状態でcloseされた場合はretryしてよいですが、サーバ側は応答しきってないのにcloseされた場合は応答を送ってはいけないことになりました。
TCP fast open
RFC 7413 - TCP Fast Open で導入されたTCP fast openの仕組みを使ってよいことになりました。
これはTCPにもcookieを導入するようなもので、事前にcookieが分かっている場合はSYNパケットにpayloadを含めて送ることで、3-way handshakeのオーバーヘッドを減らす仕組みです。
また、対応していない場合は普通のTCPにfallbackできるのがポイントのようです。