本文将讲一下使用 PowerDNS 的 GeoIP Backend 建立自己的 DNS 权威服务器(服务于自己的域名,这不是公共 DNS 缓存服务器)。

通过使用 PowerDNS,你可以在自己的服务器上搭建支持分区解析(精细到国家、城市、ASN、自定义 IP 段)、EDNS-Client-Subnet、DNSSEC、IPv6 的 DNS 服务器。

本文面向域名所有者和站长,讲述的是权威 DNS 服务器而非 DNS 缓存服务器。如果你想要了解更多的关于 DNS 的知识,请参考 DNS 域名解析系统详解——基础篇 以及 DNSSEC 简介,签名的工作原理

本文是对自建 PowerDNS 智能解析服务器的更新。自建 DNS 的优缺点、配置 DNSSEC 以及分布式 DNS 服务在之前的那篇文章有所描述,本文将其省略。本文主要更新以下内容:

  1. 本文适用于最新的 PowerDNS 4.2.0
  2. 使用 MaxMind GeoIP 2 mmdb 格式的数据库
  3. 讲述如何为根域名设置分区解析
  4. 使用 dnsdist 以实现 IP 访问速率的限制,防御 DOS 攻击。

安装 PowerDNS Authoritative Server 和 dnsdist

考虑到操作系统软件源默认版本不一,建议前往 PowerDNS repositories 重新为 PowerDNS Authoritative Server 配置软件源(请添加 4.2 或以上版本)。同样你也可以配置 dnsdist 的软件源(请添加 1.2 或以上版本)。

添加软件源后,更新软件列表并安装 pdns-server 和 pdns-backend-geoip 即可。在 Debian/Ubuntu 下:

sudo apt-get install pdns-server pdns-backend-geoip

需要注意的是,在 Ubuntu 18.04 LTS 后可能默认包含了 systemd-resolved 服务,这个服务占用了 loopback 地址的的 53 端口,这与 PowerDNS 所需要使用的端口(0.0.0.0:53)冲突。你可以:1. 禁用这个服务,2. 修改 PowerDNS 的 local-addresslocal-ipv6 配置仅监听外网 IP 地址,3. 修改 local-port 为非 53 端口,并使用 dnsdist 进行转发(详见后文)。

如果你想要配置用户侧 IP 访问速率,请安装 dnsdist:

sudo apt-get install dnsdist

禁用 systemd-resolved

如果你选择禁用 systemd-resolved,可以执行以下代码:

sudo systemctl disable systemd-resolved.service  
sudo systemctl stop systemd-resolved

安装 geoipupdate 并下载 GeoIP2

为了实现分区解析,我们需要 GeoIP 数据库。最新版的 PowerDNS GeoIP Backend(4.2.0)已经支持了 GeoIP2 的 mmdb 格式,同时也支持 dat 格式。而老板的只支持 dat 格式的数据库。由于 MaxMind 已经停止维护免费的 dat 格式数据库,因此强烈建议使用 4.2 版本及以上的 PowerDNS 并换用 mmdb 格式的 IP 数据库

首先建立配置文件,创建 /etc/GeoIP.conf 为如下内容(包含了 IPv4 和 IPv6 的国家、城市、ASN 数据):

ProductIds GeoLite2-Country GeoLite2-City GeoLite2-ASN  
DatabaseDirectory /usr/share/GeoIP

建议安装 geoipupdate,在 Ubuntu 下可以这样安装:

sudo add-apt-repository ppa:maxmind/ppa  
sudo apt update  
sudo apt install geoipupdate

然后下载/更新 GeoIP2:

sudo geoipupdate -v

All done!

同时建议配置 Crontab 定期执行上面指令更新数据库文件(并重载 PowerDNS 服务),以更新 GeoIP 数据库。

配置 PowerDNS

PowerDNS(下简称 pdns)的配置文件位于 /etc/powerdns,首先删除 PowerDNS 原本的 demo 配置,然后建立文件夹以存储 DNSSEC 的密钥文件:

sudo rm /etc/powerdns/pdns.d/*
sudo mkdir /etc/powerdns/keys

然后创建文件,创建 /etc/powerdns/pdns.d/geoip.conf 为如下内容:

launch=geoip  
geoip-database-files=/usr/share/GeoIP/GeoLite2-Country.mmdb /usr/share/GeoIP/GeoLite2-City.mmdb /usr/share/GeoIP/GeoLite2-ASN.mmdb  
geoip-zones-file=/etc/powerdns/zones.yaml  
geoip-dnssec-keydir=/etc/powerdns/key

geoip-database 选项中,你可以按照需要设置自己所需要的一个或多个 GeoIP 数据库。

配置 DNS 记录

你可以建立文件 /etc/powerdns/zones.yaml 以配置 DNS 记录。具体格式参见 Zonefile format

需要注意的是,若使用 mmdb,则 %re 是 ISO3166 的两位区域缩写。若使用 dat,则是 GeoIP 的区域代码

由于 PowerDNS GeoIP Backend 仅支持按域名配置分区解析,而不支持按记录配置按记录配置分区解析,所以若要配置根域名分区解析,可以参考如下配置(YAML 格式,下同):

domains:
- domain: example.com
  ttl: 300
  records:
    unknown.cn.example.com:
      - soa: &soa ns1.example.com hostmaster.example.com 2014090125 7200 3600 1209600 3600
      - ns: &ns1
           content: ns1.example.com
           ttl: 600
      - ns: &ns2 ns2.example.com
      - mx: &mx 10 mx.example.com
      - a: 10.1.1.1
    bj.cn.example.com:
      - soa: *soa
      - ns: *ns1
      - ns: *ns2
      - mx: *mx
      - a: 10.1.2.1
    unknown.unknown.example.com:
      - soa: *soa
      - ns: *ns1
      - ns: *ns2
      - mx: *mx
      - a: 10.1.3.1
    ns1.example.com:
      - a: 10.0.1.1
      - aaaa: ::1
    ns2.example.com:
      - a: 10.0.2.1
  services:
    example.com: [ '%ci.%cc.service.geo.example.com', 'unknown.%cc.service.geo.example.com', 'unknown.unknown.service.geo.example.com']

可以看到,为了配置跟域名分区域解析,我们需要为每个区域配置所有类型的记录。而根域名通常有很多记录类型,所以配置起来相对繁琐。上述 YAML 写法使用了变量,这样可以减少配置重复的记录。(&variable 设置变量,*variable 使用变量)

调试

你可以通过配置如下记录进行调试:

debug.tlo.xyz:  
  - a: "%ip4"  
  - aaaa: "%ip6"  
  - txt: "co: %co; cc: %cc; cn: %cn; af: %af; re: %re; na: %na; as: %as; ci: %ci; ip: %ip"

在客户端通过类似下方的指令进行测试(替换 10.11.12.13 为服务所监听的 IP 地址),有如下结果:

$ dig @10.11.12.13 debug.example.com   
  
debug.example.com.        3600    IN  TXT "co: us; cc: us; cn: na; af: v4; re: ca; na: quadranet enterprises llc; as: 8100; ci: los angeles; ip: 10.11.12.13"  
debug.example.com.        3600    IN  A   10.11.12.13

这样你就可以看到每一个变量的具体值了。

Lua 记录

在 PowerDNS 4.2 中,新增了 Lua 记录功能。其 Lua 记录的最主要的功能是可以配置宕机自动切换记录。

由于 Lua 记录可以使用 Lua 编程语言执行任意代码,相对危险,所以 PowerDNS 默认关闭了这个功能,需要在配置文件中开启:

enable-lua-records=yes

配置如下记录,将返回动态的可用 A 记录。如果两个 IP 的端口都可用(这里是 443 端口),则随机返回一个 IP 地址,若有一个不可用,则只返回可用 IP 的地址,否则同时返回两个 IP:

sub.example.com:  
  - lua: A "ifportup(443, {'192.0.2.1', '192.0.2.2'})"

配置如下记录,就可以自动对指定 URL 进行状态检查,默认返回第一组 IP 地址,若第一组 IP 地址不可用,则返回第二组:

sub.example.com:  
  - lua: A "ifurlup('https://sub.example.com/', {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}})"

Lua 记录可以与 GeoIP 功能共同使用,且可以与其他类型的记录共存。另外,状态检查并非与请求同步,而是在后台周期性的检查是否可用,所以使用状态检查不会增加 DNS 请求的延迟。

配置 dnsdist

截止到现在,你的 DNS 服务器应该已经可以工作了。下面将介绍配置 dnsdist。

dnsdist 相当于在 pdns 之上加了一层代理,可以为其配置访问速率的限制,起到抗 DOS 的作用。正常情况下,安装完成 dnsdist 后,其服务就自动启动了。

在这个样例中,将 pdns 的主程序监听改为 127.0.0.1:8053。创建或修改文件 /etc/dnsdist/dnsdist.conf 为以下内容:

newServer{address="127.0.0.1:8053",useClientSubnet=true}
setLocal("10.0.1.1")
setACL({'0.0.0.0/0', '::/0'})
setECSSourcePrefixV4(32)
setECSSourcePrefixV6(128)
addAction(MaxQPSIPRule(10, 32, 56), TCAction())
addAction(MaxQPSIPRule(20, 32, 56), DropAction())
addAction(MaxQPSIPRule(40, 24, 48), TCAction())
addAction(MaxQPSIPRule(80, 24, 48), DropAction())
addAction(MaxQPSIPRule(160, 16, 40), TCAction())
addAction(MaxQPSIPRule(320, 16, 40), DropAction())

newServer 语句设置了 dnsdist 所代理的服务器,也就是 pdns 所监听的 IP 地址和端口号。setLocal 设置了 dnsdist 的监听 ip 地址。setACL 设置了允许的 IP 地址,这里允许了所有的 IPv6 和 IPv4 访问。setECSSourcePrefixV4setECSSourcePrefixV6 设置了 EDNS Client Subnet Client 功能传输 IP 地址的比特数,这里为 IPv4 设置了 32 位,IPv6 设置了 128 位,也就是保留了 IP 地址的完整长度。实际生产中也可以设置比这个更小的值。

后几行 addActionMaxQPSIPRule 定义了访问速率限制。其中 MaxQPSIPRule 拥有三个参数。第一个参数是 qps,即 queries per second,每秒钟请求数。第二个参数是 IPv4 的 CIDR 值,默认为 32,第三个参数是 IPv6 的 CIDR 值,默认为 64。TCAction 代表要求客户端使用 TCP 请求,DropAction 代表拒绝请求。普通的客户端在收到 DNS 服务器要求使用 TCP 时,会使用 TCP 进行 DNS 请求,这样不影响普通用户的使用。而 DOS 一般不会对 TCP DNS 服务进行攻击。

举例,下面语句的意思是:

addAction(MaxQPSIPRule(40, 24, 48), TCAction())

限制 /24 的 IPv4 地址(256 个)和 /48 的 IPv6 地址(2^80 个)的 qps 为 40,若超出则要求客户端使用 TCP 请求。

除了 TCAction 和 DropAction,还可以使用 DelayAction(一个整数参数,代表为延迟的毫秒数)和 NoRecurseAction(专门用于限制标记了递归((RD))的请求)。

因此,将 dnsdist 与 pdns 配合使用,就可以使 DNS 服务有一定的抗 DOS 能力。