Client Subnet in DNS Requests (draft-vandergaast-edns-client-subnet) を読んだ
先日googleとakamaiは仲良し?というエントリを読んだとき,恐らくEDNS Client Subnet Optionだろうとは思った*1ものの,EDNS Client Subnet Optionの動作をきちんと把握してはいなかったので,この機会にdraftを読んでみた.
概要
- DNS権威サーバの中には,クエリ送信元のDNSキャッシュサーバのIPアドレスによって応答を変えるものが存在する
- クライアントとキャッシュサーバのネットワーク的な距離が離れている場合,権威サーバは適切でない応答を返す恐れがある
- キャッシュサーバから権威サーバへのクエリ内にクライアントのIPアドレスの一部を含めることで,権威サーバがより適切な応答を返すことを実現する
- 利用にはキャッシュサーバと権威サーバの対応が必要 (クライアントの対応は不要)
問題: Public DNSと"不適切"な応答の発生
CDN事業者やWebサービス事業者が運用しているDNS権威サーバの中には,問い合わせ元のDNSキャッシュサーバのIPアドレスの応じて応答を変えるものが存在する.例えば,日本のISP Aのキャッシュサーバからの問い合わせに対しては日本に設置されたエッジサーバのIPアドレスを返せば,クライアントからエッジサーバへのレイテンシは抑えられると考えらえる.
ここで問題となるのは,クライアントとキャッシュサーバのネットワーク的な距離が離れている場合に,適切でない応答が発生することである.例えば,日本のISP A内のクライアントが台湾のキャッシュサーバを利用していた場合,権威サーバは台湾に設置されたエッジサーバのIPアドレスを返してしまう.これにより,クライアントにとってより適切なエッジサーバが存在するにも関わらず適切でないサーバに接続してしまい,結果としてパフォーマンス上の問題が発生する.
以前であればISPのキャッシュサーバを利用することが殆どであったため問題は発生しなかった*2.しかし,最近はGoogle Public DNSをはじめとした公開DNSキャッシュサーバの普及によって,クライアントとDNSキャッシュサーバの距離が離れているケースが生じるようになった。その結果、上述したような問題が顕著化するようになった.
解決策: クライアントのIPアドレスをクエリに埋め込む
この問題を解決するために考えらえたのが、キャッシュサーバから権威サーバへのクエリ内にクライアントのIPアドレスを埋め込むことで,権威サーバが適切な応答を返せるようにする,というもの.
EDNS Client Subnet Optionに対応したキャッシュサーバは,クライアントのIPアドレスの一部 (ADDRESS) とプレフィクス長 (SOURCE NETMASK; 標準ではIPv4で/24*3 ) をOPT RRに埋め込んで権威サーバに送信する.
これを受け取ったEDNS Client Subnet Option対応権威サーバは,クライアントのIPアドレスに応じた応答を返す.このとき,適切な応答を生成するのに必要だったプレフィクス長 (SCOPE NETMASK) を含める.たとえば,キャッシュサーバからSOURCE NETMASKが24のIPアドレスが与えられたとして,権威サーバは/22の情報しか使用しなかった場合,SCOPE NETMASKは22となる.
キャッシュサーバはクライアントに対して応答を返すと共に,権威サーバからの応答を自身のキャッシュに格納する.このときのキーは (FAMILY, ADDRESS, SCOPE NETMASK) の組となる.
ちなみに,EDNS Client Subnet Optionに対応していない権威サーバにオプション付きのクエリを投げたとしても,単純に未対応のオプションとして無視されるだけである.
実際に試してみる
Client Subnet Option付きクエリを投げる機能を追加するpatchを当てたdig
で実験.ns1-4.google.comはClient Subnet Option対応権威サーバとして謳われているので,今回はこれを使用.
まずは適当な日本国内のIPアドレス帯でクエリを投げる.
$ dig @ns1.google.com. www.google.com. +norecurse +client=122.102.128.0/24 ; <<>> DiG 9.9.3 <<>> @ns1.google.com. www.google.com. +norecurse +client=122.102.128.0/24 ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42644 ;; flags: qr aa; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 512 ; CLIENT-SUBNET: 122.102.128.0/24/21 ;; QUESTION SECTION: ;www.google.com. IN A ;; ANSWER SECTION: www.google.com. 300 IN A 173.194.126.209 www.google.com. 300 IN A 173.194.126.210 www.google.com. 300 IN A 173.194.126.211 www.google.com. 300 IN A 173.194.126.212 www.google.com. 300 IN A 173.194.126.208 ;; Query time: 44 msec ;; SERVER: 216.239.32.10#53(216.239.32.10) ;; WHEN: Sun Aug 17 15:46:53 JST 2014 ;; MSG SIZE rcvd: 134
$ ./bin/dig/dig @ns1.google.com. www.google.com. +norecurse +client=118.189.0.0/24 ; <<>> DiG 9.9.3 <<>> @ns1.google.com. www.google.com. +norecurse +client=118.189.0.0/24 ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57557 ;; flags: qr aa; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 512 ; CLIENT-SUBNET: 118.189.0.0/24/20 ;; QUESTION SECTION: ;www.google.com. IN A ;; ANSWER SECTION: www.google.com. 300 IN A 74.125.130.104 www.google.com. 300 IN A 74.125.130.105 www.google.com. 300 IN A 74.125.130.147 www.google.com. 300 IN A 74.125.130.103 www.google.com. 300 IN A 74.125.130.106 www.google.com. 300 IN A 74.125.130.99 ;; Query time: 52 msec ;; SERVER: 216.239.32.10#53(216.239.32.10) ;; WHEN: Sun Aug 17 15:48:30 JST 2014 ;; MSG SIZE rcvd: 150
最後にイギリスの適当なIPアドレス帯で.
$ ./bin/dig/dig @ns1.google.com. www.google.com. +norecurse +client=212.140.0.0/16 ; <<>> DiG 9.9.3 <<>> @ns1.google.com. www.google.com. +norecurse +client=212.140.0.0/16 ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47653 ;; flags: qr aa; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 512 ; CLIENT-SUBNET: 212.140.0.0/16/18 ;; QUESTION SECTION: ;www.google.com. IN A ;; ANSWER SECTION: www.google.com. 300 IN A 74.125.230.112 www.google.com. 300 IN A 74.125.230.115 www.google.com. 300 IN A 74.125.230.114 www.google.com. 300 IN A 74.125.230.113 www.google.com. 300 IN A 74.125.230.116 ;; Query time: 42 msec ;; SERVER: 216.239.32.10#53(216.239.32.10) ;; WHEN: Sun Aug 17 15:49:24 JST 2014 ;; MSG SIZE rcvd: 133
各クエリで得られたIPアドレス宛に東京からpingを打ってみたところ,下のような結果になった.
- 173.194.126.209 (東京): 12ms
- 74.125.130.104 (シンガポール): 80ms
- 74.125.230.112 (イギリス): 230ms
東京-シンガポール,東京-イギリス間の一般的なRTTに近い値であることから,それぞれのクエリにおいてクライアントに近いサーバのアドレスが返されていると考えられる.
まとめ
EDNS Client Subnet Optionのdraftを読み,おおまかな動作の流れをまとめてみた.
現時点での対応状況として,キャッシュサーバ側ではGoogle Public DNSやOpenDNSが,権威サーバ側(コンテンツ提供側)ではAkamaiやCloudFrontが対応しているようである.逆に言うと,普通のキャッシュサーバや権威サーバが対応するメリットは無いので,特に気にする必要は無い.
ただし,Public DNSに限らずISPのDNSキャッシュサーバが対応するとメリットが出るような場合もある.たとえば,この資料ではインドネシアのように国土が分散している場合におけるClient Subnetのメリットが述べれらている.また権威サーバ側でも,海外など広い範囲でサービスを提供していて,かつ海外にもPOP (Point of Presence) を設置しているような事業者の場合,対応することでクライアントのレイテンシ改善を実現できるかもしれない.
何にせよ,現状はパブリックなサーバ側実装が無い状態なので,まずはオープンかつ信頼できる実装が出てくると良いのかなぁ,と感じた*4.
参考
grepcidr: IPアドレスをサブネットで一括grep
あるファイルに含まれるIPアドレスをgrepしたいとき,/32なり/128なりのIPアドレスそのものではなく,サブネットでgrepできると便利な場面が多々ある.それらしき物が無いか探したところ,grepcidrというツールがあったのでメモ.
導入
単純にソースコードをダウンロードして make & make install
するだけ.
$ wget http://www.pc-tools.net/files/unix/grepcidr-2.0.tar.gz $ tar xvf grepcidr-2.0.tar.gz $ cd grepcidr-2.0 $ make # make install
使い方
たとえば下のようなファイルがあるとする.
$ cat ipaddr.txt 192.0.2.0/24 192.0.2.0/25 192.0.2.128/25 192.0.2.254/32 2001:db8::/32 2001:db8:a:1::/64 2001:db8:a:2::/64 2001:db8:a:1::1/128
これが色々なpatternでgrepできる.
$ grepcidr 192.0.2.0/24 ipaddr.txt 192.0.2.0/24 192.0.2.0/25 192.0.2.128/25 192.0.2.254/32 $ grepcidr 192.0.2.1/25 ipaddr.txt 192.0.2.0/24 192.0.2.0/25 $ grepcidr 192.0.2.128/25 ipaddr.txt 192.0.2.128/25 192.0.2.254/32 $ grepcidr 2001:db8:a::/48 ipaddr.txt 2001:db8:a:1::/64 2001:db8:a:2::/64 2001:db8:a:1::1/128
かなり便利.
過去に蓄積されたデータの集計にNorikraを使う
最近Norikraを触っていて,ある程度使い方が分かってきたのと,丁度v1.0.0もリリースされたので,忘れないうちにこのタイミングでメモしておく.
本来Norikraはリアルタイムなログ等に対して処理をするものであるけども,今回は過去に蓄積されたログに対して集計処理を行ってみることにする*1.
今回のキーポイントは,
win:ext_timed_batch
を使って,ログの発生時刻を指定するLOOPBACK
を使って,あるクエリで発生したeventを別のtargetに直接流し込む
の2点.
やりたいこと
下のような,2台のホストで1分毎に生成されたデータがあるとする.
# host1.csv timestamp, metric1, metric2, ... 1396278000, 0, 0 1396278060, 1, 2 1396278120, 2, 4 1396278180, 3, 6 1396278240, 4, 8 1396278300, 5, 10 1396278360, 6, 12 1396278420, 7, 14 1396278480, 8, 16 1396278540, 9, 18 1396278600, 10, 20
# host2.csv timestamp, metric1, metric2, ... 1396278000, 0, 0 1396278060, 2, 4 1396278120, 4, 8 1396278180, 6, 12 1396278240, 8, 16 1396278300, 10, 20 1396278360, 12, 24 1396278420, 14, 28 1396278480, 16, 32 1396278540, 18, 36 1396278600, 20, 40
これらのデータに対し,同一時刻に発生したメトリックどうしを加算したい.
timestamp, metric1_sum, metric2_sum, ... 1396278000, 0, 0 1396278060, 3, 6 1396278120, 6, 12 1396278180, 9, 18 1396278240, 12, 24 1396278300, 15, 30 1396278360, 18, 36 1396278420, 21, 42 1396278480, 24, 48 1396278540, 27, 54 1396278600, 30, 60
さらに,5分間の平均値と最大値を調べたい.
timestamp, metric1_avg, metric1_max 1396278000, 6.0, 12 1396278300, 21.0, 27
実際には,ホスト数もメトリックももっとたくさんある,という想定. こういった用途であれば,データベースに突っ込んで集計するなり,自前で集計スクリプトを書くなりすれば対応できるような気がする.ただ,集計対象のメトリックが頻繁に変更されるような状況で,かつデータベースや集計スクリプトのメンテナンスにあまり重きを置けない場合*2,そのあたりを簡単に扱えるものが欲しくなってくる.そこでNorikra,という流れ.
過去のログを扱う::ext_timed_batchでログの発生時刻を指定する
通常,ログがnorikraに入った時のシステムの実時間=ログの発生時刻として扱われる.それだと過去のデータを処理対象とする場合困るので,発生時刻を外部から与えてやる必要がある.そこで使うのがext_timed_batch
.
例えばこんな感じのクエリを登録する.
select min(timestamp) as timestamp, sum(metric1) as metric_sum from host_data.win:ext_timed_batch(timestamp * 1000, 1 min, 1396278000000L) where hostname in ("host1", "host2")
このクエリを登録した状態で,ターゲットhost_data
に
[{"timestamp": 1396278000, "hostname": "host1", "metric1": 0}]
というようなデータを送ると,2014-04-01 00:00(JST) (=unix epochで1396278000)に発生したログとして扱ってもらえる.ext_timed_batch
の第3引数はtime windowの開始点を与える.設定しない場合,最初のイベントが発生した時刻を基準にbatchが実行されるようになる.外部から与えるtimestampのタイムゾーム周りがややこしい場合,明示的に指定しておいたほうが無難に思える.
注意点として,流し込むデータは時間順にソートされている必要がある.ext_timed_batch
に限らず*_batch
では,あるtime windowの境界を跨ぐデータが到着した時点で,前のtime windowに対するeventが発行される.なので,データが時間順にソートされていないと,正しい結果が得られない.
複数のクエリで処理する::LOOPBACK()でeventを別のtargetに送る
あるクエリを実行した後,その結果得られたeventに対して更にクエリを投げたい場合,以前であればeventをfetchした上で対象targetに送り直す必要があった*3.Norikra v1.0.0からLOOPBACK()
が導入され,この処理が自動化された.
使い方は簡単で,クエリ登録時のGroupをLOOPBACK(target名)
としてやればよい.
例えば,上で紹介したクエリのGroupをLOOPBACK(metric_aggregated)
とした上で,下のクエリを登録する.
select min(timestamp) as timestamp, avg(metric_sum) as metric_avg, max(metric_sum) as matric_max from metric_aggregated.win:ext_timed_batch(timestamp * 1000, 10 min, 1396278000000L)
この状態でターゲットhost_data
にeventを送ると,最初のクエリを実行した上でその結果がターゲットmetric_aggregated
に送られ,2番目のクエリが実行される.
あとはこのクエリのeventをfetchすれば,当初のお目当てのデータが得られる.
まとめ
今回は,過去に蓄積されたログデータの集計にNorikraを使ってみた.本来想定されている使い方ではない上に,ext_timed_batch使うのはオススメしないと@tagomorisさんが言っていたりするので,気付いていない落とし穴があるのかもしれない.
ただ,データ集計の条件が簡単に書ける&データストア類が必須でないという点は,コードを書いたりデータベースを運用することが日常的ではない場所で使う上で,意外とメリットになりそうな気はする.