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を運用する上では当然の知識らしいが、 DNSUDPで通信を行い、最大長は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を介して行うことができるようになる。