PowerDNS 是一款支持多個後端的 DNS 服務端軟件,本次搭建基於 Debian 9,配置 PowerDNS 權威 DNS,使用 Mariadb 和 geoip 後端,並使用 lua 記錄實現按地區解析。
本文假設擁有一個 example.com 域名,本台機的主機名為 ns1.example.com,ns1 管理員郵箱為 [email protected]。
1. 安裝軟件
導入 PowerDNS repo [1],安裝最新版 PowerDNS 及後端
1.1. 新增 /etc/apt/sources.list.d/pdns.list 文件,填入如下內容。請根據閣下的發行版相應更改。
1 |
deb [arch=amd64] http://repo.powerdns.com/debian stretch-auth-master main |
1.2. 新增 /etc/apt/preferences.d/pdns 文件,並填入如下內容。
1 2 3 |
Package: pdns-* Pin: origin repo.powerdns.com Pin-Priority: 600 |
1.3. 導入 key 並安裝,此處安裝 powerdns mysql 和 geoip 後端。
1 2 3 |
curl https://repo.powerdns.com/CBC8B383-pub.asc | sudo apt-key add - sudo apt update sudo apt install pdns-server pdns-backend-mysql pdns-backend-geoip |
1.4. 安裝 Mariadb,在此過程中可能需要設定 MySQL root 密碼。
1 |
sudo apt install mariadb-server |
2. 設定數據庫
新建新的用戶、數據庫供 PowerDNS 使用,並且導入基礎數據表結構。
2.1. 新建一個用戶、數據庫。此處用戶名、數據庫名為 dns,密碼為 PASSWORD。請根據實際需求更改。
1 2 3 4 |
mysql -uroot -p CREATE USER 'dns'@'%' IDENTIFIED BY 'PASSWORD'; CREATE DATABASE IF NOT EXISTS `dns`; GRANT ALL PRIVILEGES ON `dns`.* TO 'dns'@'%'; |
2.2. 導入數據庫表結構,並設定外鍵。以下是官方文檔 [2] 提供的表結構,閣下可以根據需求進行修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
USE dns; CREATE TABLE domains ( id INT AUTO_INCREMENT, name VARCHAR(255) NOT NULL, master VARCHAR(128) DEFAULT NULL, last_check INT DEFAULT NULL, type VARCHAR(6) NOT NULL, notified_serial INT UNSIGNED DEFAULT NULL, account VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET 'latin1'; CREATE UNIQUE INDEX name_index ON domains(name); CREATE TABLE records ( id BIGINT AUTO_INCREMENT, domain_id INT DEFAULT NULL, name VARCHAR(255) DEFAULT NULL, type VARCHAR(10) DEFAULT NULL, content VARCHAR(64000) DEFAULT NULL, ttl INT DEFAULT NULL, prio INT DEFAULT NULL, disabled TINYINT(1) DEFAULT 0, ordername VARCHAR(255) BINARY DEFAULT NULL, auth TINYINT(1) DEFAULT 1, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET 'latin1'; CREATE INDEX nametype_index ON records(name,type); CREATE INDEX domain_id ON records(domain_id); CREATE INDEX ordername ON records (ordername); CREATE TABLE supermasters ( ip VARCHAR(64) NOT NULL, nameserver VARCHAR(255) NOT NULL, account VARCHAR(40) CHARACTER SET 'utf8' NOT NULL, PRIMARY KEY (ip, nameserver) ) Engine=InnoDB CHARACTER SET 'latin1'; CREATE TABLE comments ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(10) NOT NULL, modified_at INT NOT NULL, account VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL, comment TEXT CHARACTER SET 'utf8' NOT NULL, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET 'latin1'; CREATE INDEX comments_name_type_idx ON comments (name, type); CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); CREATE TABLE domainmetadata ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, kind VARCHAR(32), content TEXT, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET 'latin1'; CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind); CREATE TABLE cryptokeys ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, flags INT NOT NULL, active BOOL, content TEXT, PRIMARY KEY(id) ) Engine=InnoDB CHARACTER SET 'latin1'; CREATE INDEX domainidindex ON cryptokeys(domain_id); CREATE TABLE tsigkeys ( id INT AUTO_INCREMENT, name VARCHAR(255), algorithm VARCHAR(50), secret VARCHAR(255), PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET 'latin1'; CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); ALTER TABLE records ADD CONSTRAINT `records_domain_id_ibfk` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE comments ADD CONSTRAINT `comments_domain_id_ibfk` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE domainmetadata ADD CONSTRAINT `domainmetadata_domain_id_ibfk` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE cryptokeys ADD CONSTRAINT `cryptokeys_domain_id_ibfk` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; |
2.3. 插入測試域名
插入域名,設定 SOA 記錄,NS 記錄,以及主機 www 的 A 記錄為 1.1.1.1
1 2 3 4 5 6 7 8 9 |
INSERT INTO domains (name, type) values ('example.com', 'NATIVE'); INSERT INTO records (domain_id, name, content, type,ttl) VALUES (1,'example.com','ns1.example.com admin.example.com 1 7200 900 1209600 10800','SOA',86400); INSERT INTO records (domain_id, name, content, type,ttl) VALUES (1,'example.com','ns1.example.com','NS',86400); INSERT INTO records (domain_id, name, content, type,ttl) VALUES (1,'example.com','ns2.example.com','NS',86400); INSERT INTO records (domain_id, name, content, type,ttl) VALUES (1,'www.example.com','1.1.1.1','A',120); |
此處需要注意格式。domain_id 的值為 domains 表新增的域名 id。name 的值需為完整域名,結尾不需加 "."
同樣,SOA 記錄的 ns 和郵箱結尾不需要加點,郵箱的 "@" 需要替代為 "."。NS 記錄的內容結尾也不需要加。 [5]
3. 設定 PowerDNS
本節新增 PowerDNS 配置文件,配置 MySQL 及 GeoIP 後端。請新開一個 Shell 進行以下操作。
3.1. 下載 geoip 數據庫,用來分區域解析。
雖然官方說明支持 geoip2 數據庫,但是測試過程中啓動失敗。因此此處採用 maxmind 的 geoip 數據庫,由第三方提供轉換。
1 2 3 4 |
mkdir /etc/powerdns/geoip/ cd /etc/powerdns/geoip/ wget https://dl.miyuru.lk/geoip/maxmind/country/maxmind4.dat.gz gunzip maxmind4.dat.gz |
3.2. 清空並編輯 /etc/powerdns/pdns.conf 文件。
開啓 lua 記錄,開啓 MySQL 及 GeoIP 後端並設定。此處 MySQL 訊息需與 2.1 的設定一致。此處 geoip 數據庫路徑需與 3.1 的文件路徑相同。
1 2 3 4 5 6 7 8 9 10 |
enable-lua-records=yes setuid=pdns setgid=pdns launch=gmysql,geoip gmysql-host=127.0.0.1 gmysql-user=dns gmysql-dbname=dns gmysql-password=PASSWORD gmysql-dnssec=yes geoip-database-files=/etc/powerdns/geoip/maxmind4.dat |
4. 啓動 PowerDNS 進行測試
4.1. 在前臺啓動 PowerDNS,方便 debug
1 |
/usr/sbin/pdns_server --daemon=no --guardian=no --loglevel=9 |
出現類似以下的 log 即代表開啓成功。
1 2 3 4 5 6 7 8 9 10 11 12 |
Jul 01 00:10:39 Loading '/usr/lib/x86_64-linux-gnu/pdns/libgmysqlbackend.so' Jul 01 00:10:39 [gmysqlbackend] This is the gmysql backend version 4.2.0-rc2.25.master.gc0c7b9e1e (Jun 14 2019 09:36:14) reporting Jul 01 00:10:39 Loading '/usr/lib/x86_64-linux-gnu/pdns/libgeoipbackend.so' Jul 01 00:10:39 [geoipbackend] This is the geoip backend version 4.2.0-rc2.25.master.gc0c7b9e1e (Jun 14 2019 09:37:34) reporting Jul 01 00:10:39 This is a standalone pdns Jul 01 00:10:39 Listening on controlsocket in '/var/run/pdns.controlsocket' Jul 01 00:10:39 UDP server bound to 0.0.0.0:53 Jul 01 00:10:39 TCP server bound to 0.0.0.0:53 Jul 01 00:10:39 PowerDNS Authoritative Server 4.2.0-rc2.25.master.gc0c7b9e1e (C) 2001-2019 PowerDNS.COM BV Jul 01 00:10:39 Creating backend connection for TCP Jul 01 00:10:39 gmysql Connection successful. Connected to database 'dns' on '127.0.0.1'. Jul 01 00:10:39 Done launching threads, ready to distribute questions |
4.2. 新開一個 Shell,使用 dig 進行測試。
1 2 |
apt install dnsutils dig +short www.example.com A @127.0.0.1 |
若一切正常,將查詢出 1.1.1.1。
5. 設定 LUA 記錄動態更改解析
PowerDNS 自帶 LUA 記錄 [3],可以根據 LUA 代碼動態返回解析結果。同時預設了多個 LUA 方法[4]。
- country(2位地區代碼)
如果請求 IP 屬於這個地區,返回 true。可傳遞 list
- continent(2位大洲代碼)
如果請求 IP 屬於這個大洲,返回 true。可傳遞 list
- pickrandom(IP地址 list)
隨機返回該 list 的一個 IP 地址
- ifportup(端口號, IP 地址 list)
如果該 IP 地址指定端口號開啓,則返回。可配合選擇方案
本 LUA 例子為判斷地區/大洲,然後返回相應的 A 記錄。開頭先說明返回的記錄為 A,後面跟 LUA 代碼。如果國家為 新加坡、日本,返回3.3.3.3,如果大洲為歐洲,返回 4.4.4.4,其他(默認)情況返回1.1.1.1。由於 LUA 本身不支持 switch 語法,此處使用多個 if。
1 2 3 |
"A "; if(country({'SG','JP'})) then return '3.3.3.3 'end" "if(continent( {'EU'} )) then return'4.4.4.4'end" "return'1.1.1.1'" |
5.1. 在第二步的 MySQL Shell 窗口插入這條記錄。如果覺得這樣不好睇,可以選擇在圖形化 MySQL 客戶端上操作,如 Navicat。
1 2 |
INSERT INTO records (domain_id, name, content, type, ttl) VALUES (1,'geo.example.com','A \"; if(country({\'SG\',\'JP\'}))then return\'3.3.3.3\'end if(continent( {\'EU\'} ))then return\'4.4.4.4\'end return\'1.1.1.1\'\"','LUA',120); |
5.2. 從上述地區發出 dig 請求,測試是否返回相應的記錄。
1 |
dig +short geo.example.com A @ns1的IP地址 |
6. 設定多臺 NS 服務器,並同步數據
6.1. 在 ns1 上導出數據庫。
1 |
mysqldump -uroot -p dns > dns.sql |
6.2. 傳輸此sql文件到其他服務器(ns2),在ns2上重複上述步驟 1,3。第 2 步 只需要執行創建數據庫部分,然後導入 ns1 的數據庫。
1 2 3 |
mysql -uroot -p use dns; source dns.sql |
6.3. 在 ns2 中執行步驟 4 進行測試。
測試成功後,將 ns2 的數據庫設定為 slave 模式,使用 MySQL 主從複製 同步數據 [6]。
需要增加記錄時,更改 ns1 的 MySQL 數據庫即可。
7. 在域名註冊商處增加相應膠水記錄 (glue record)
新增膠水記錄,將 ns1.example.com 指向 ns1 IP 地址,ns2.example.com 指向 ns2 IP 地址等。不同域名商可能有不同的叫法,如 register name server 等。
接下來將其他域名的 nameserver 指定到以上 ns 域名即可。
參考資料
[1] PowerDNS repo,根據不同系統選擇不同的 repo
[2] 基礎數據庫設定,可根據業務需要更改表結構
[3] LUA 記錄例子
[4] LUA 預設變量和方法
[5] PowerDNS 支持的記錄及格式
後記
在選用 PowerDNS 之前,我也測試過以下權威DNS軟件。
gdnsd. 輕巧,文件存儲。由於需要先定義數據中心,在域名多、區域解析多的情況下不太方便。不支持 DNSSEC。
bind9. 事實上的 DNS 標準。官方沒有提供 MySQL 的表結構,測試起身麻煩,先放棄。
主要是,mmdb格式只能单独在geoip.conf配置似乎,而且不能搭配mysql,只能配置zone files
你好,请问下博主配置成功mmdb格式了吗?我测试了好一会,发现mmdb无法正常工作(结合mysql)
没有喔,之前配置不成功,我就没有再尝试mmdb了。网上有转换mmdb为dat的代码,可以尝试下。
你好,请教个问题,pdns授權DNS支援MySQL geoip lua後端同時工作麼?
為什麼我按照博主的博客操作進行,但不能實現不同地區解析得到不同的ip呢?
你好,支持的,我现在就在用。
如果你配置了lua记录,能正常得到解析结果,但是没有地区解析效果,可能是geoip数据库没弄好。