powerdns+dnsdistの構築
powerdns+dnsdistの構築
概要
もともとpdns(権威DNS)が動作していた環境にdnsdistを導入した。 以下のような構成。
+ | 53/udp | +--------------+ | +----v-----+ | | | | | | | dnsdist | | | | | | | +----+-----+ | | | 10053/udp | +----v-----+ | | | | | | | pdns | | | | | | | +----------+ | +--------------+
大量のクエリでpdnsがパンクするという障害を起こしたため、それの対策としてdnsdistによるQuery Per Second(QPS)の制限を入れた。
苦労した点
geobackend(既に開発が止められている拡張機能)にリゾルバのIPが伝わらないという問題を回避するための設定に苦労した。
geobackendはリゾルバのIPが所属する地域毎にレスポンスを変えることができる機能だが、pdnsが受け取るDNSリクエストはdnsdist経由で受け取るため、接続元IPアドレスがdnsdistが実行されているホスト(上記の構成ではlocalhost)のものになってしまう。
この問題はEDNS0をdnsdistとpdns間で強制することで解決できた。 EDNS0の一部であるedns-client-subnet (ECS)はRFC7871で定義されており、このECSが有効なリクエストであればdnsdist越しであってもpdnsがクライアント(リゾルバ)のIPを特定できる。 またgeobackendは以下のissueでECSに対応していることが確認できた。 https://github.com/PowerDNS/pdns/issues/1482
そして、dnsdist上のECSを有効化する設定方法については以下のML上のやり取りが非常に参考になった。 https://mailman.powerdns.com/pipermail/dnsdist/2016-September/000216.html
You can pass the full client IP address using EDNS0 client subnet extension (https://tools.ietf.org/html/draft-ietf-dnsop-edns-client-subnet-08). newServer({ ... useClientSubnet=true .. }) setECSOverride(true) setECSSourcePrefixV4(32) setECSSourcePrefixV6(128)
dnsdistのインストール
dnsdistのインストールはrpmで入れたが、公式が案内する通りの方法でインストールできたため、ここでは書かない。
設定
pdns
- ECSを有効化(edns-subnet-processing)
- pdnsを53から10053に変更(local-port)
$ diff pdns.conf pdns.conf.20161221 80c80 < edns-subnet-processing=yes --- > # edns-subnet-processing=no 135c135 < local-port=10053 --- > # local-port=53
dnsdist
- ECS有効化および強制(useClientSubnet=True, setECS*)
- QPSの制限(addAction(MaxQPSIPRule())
-- balancing all packets to local pdns server newServer({address="127.0.0.1:10053", useClientSubnet=true) -- EDNS settings setECSOverride(true) setECSSourcePrefixV4(32) setECSSourcePrefixV6(128) -- enable control socket for dynamic configration controlSocket("0.0.0.0") -- allow access from everyone addACL("0.0.0.0/0") -- QPS for remote clients addAction(MaxQPSIPRule(100, 28, 64), DropAction())
動作確認
以下のように外部ホスト上のリゾルバ経由でednsが有効/無効なクエリを行い、ログを確認する。
$ dig ${geobackendが効いているレコード} +noedns $ dig ${geobackendが効いているレコード} +edns=0
ednsが有効か無効かにかかわらず、以下のようなログがでる。 /var/log/messages
Dec 27 17:31:52 dns001-v pdns[9116]: Remote 127.0.0.1<-x.x.x.x/32 wants '***|TXT', do = 0, bufsize = 512: packetcache MISS Dec 27 17:31:52 dns001-v pdns[9116]: [geobackend] Serving *** CNAME *** to x.x.x.x/32 (392)
noednsでクエリを投げてもdnsdistとpdnsの間はEDNS0が強制されることを確認できた。
番外編: powerdnsとEDNS0とTCPフォールバックの関係
DNSを運用する上では当然の知識らしいが、 DNSはUDPで通信を行い、最大長は512bytesであるらしい。 512bytes以上のDNSへのリクエストはTCPフォールバック(UDPではなくTCPで通信を行う)を使用するか、EDNS0で拡張されたプロトコルを使用する必要がある。
powerdnsはEDNSがデフォルトで有効になっているので、512bytes以上の通信をEDNSを使ってUDPで行うことができるが、実は1680bytes以上の通信はTCPフォールバックさせる。
これは以下に記載されているとおりリフレクション攻撃の影響を小さくするためであるらしい。
https://doc.powerdns.com/md/authoritative/settings/
udp-truncation-threshold Integer Default: 1680 EDNS0 allows for large UDP response datagrams, which can potentially raise performance. Large responses however also have downsides in terms of reflection attacks. Up till PowerDNS Authoritative Server 3.3, the truncation limit was set at 1680 bytes, regardless of EDNS0 buffer size indications from the client. Beyond 3.3, this setting makes our truncation limit configurable. Maximum value is 65535, but values above 4096 should probably not be attempted.
udp-truncation-thresholdの値を大きくすれば(最大で4096bytes?)より大きなサイズの通信をUDPを介して行うことができるようになる。