场景分析 家用DNS的场景免不了天天折腾,比不上企业服务器24x7不关机。 很多在用PaoPaoDNS+PaoPaoGateway的网友经常问关于DNS故障转移的问题。让我们来先看一个常用的简单FakeIP拓扑:
主路由 FakeIP网关 客户端 DNS服务 FakeIP池 11.0.0.1=qq.com ...... ........
FakeIP池... 被分流的域名 静态路由11.0.0.0/8 下一跳是192.168.1.200
静态路由11.0.0.0/8 下一跳是192.168.1.200 192.16... 192.16... 192.16... qq.com qq.com... DNS查询 11.0.0... 域名命中分流列表 返回解析11.0.0.1 192.16... 向FakeIP网关查询 返回解析11.0.0.1 请求 Viewer does not support full SVG 1.1 DNS查询过程可以简化如下:
PaoPaoDNS 10.10.10.8 PaoPaoGateway 10.10.10.3 CUSTOM_FORWARD FakeIP CIDR PC 10.10.10.100 DNS查询/请求 在这个场景里面,DHCP下发客户端的DNS是PaoPaoDNS,“DNS故障"可以有几种情况:
PaoPaoGateway炸了,不返回FakeIP。但这在常用的拓扑下,通常只影响国外域名查询。 PaoPaoDNS的递归组件炸了,但PaoPaoDNS内部可以回落到其他查询结果 PaoPaoDNS所在的宿主机炸了,这回就真的是没网了。<-最需要故障转移高可用的一集 如果给客户端下发备用DNS有用吗?没有用,多个DNS下发到客户端的实际行为不确定,大多数时候都是随机查询并不是故障转移。既然PaoPaoDNS所在的宿主机炸了,那么自然也要跳出宿主机去解决问题——比如在一些稳定不关机的嵌入式设备,或者可执行二进制的硬路由器(经典openwrt硬路由)上跑一个简单的DNS转发器,他的任务很简单,当PaoPaoDNS可用的时候使用PaoPaoDNS,当PaoPaoDNS不可用的时候回落到运营商或者其他公共DNS————由于给客户端下发的DNS是路由器的DNS,因此你就算把PaoPaoDNS的宿主机砸了也不会断网。理想的拓扑简化如下:
PaoPaoDNS 10.10.10.8 PaoPaoGateway 10.10.10.3 FakeIP CIDR ISP DNS 223.5.5.5 Router 10.10.10.1 PC 10.10.10.100 Fallback 实现平滑的DNS故障转移 很多开源DNS服务端项目,比如openwrt里面自带的dnsmasq就有DNS故障转移的功能————比如按顺序查询(strict-order参数)。但实际用起来一点也不平滑,表现为:
当DNS故障的时候,查询结果明显巨大的延迟,上网就感觉到卡。因为触发故障转移的条件往往非常苛刻,比如直到遇到SERVFAIL甚至5秒的查询超时后才切换备用查询服务器。也就是你搭建的DNS炸了之后,每次查询都要忍受巨大的延迟————有些应用程序甚至等不了这么久就马上报错。 无有效结果的响应不会被视作DNS查询失败。这个很好理解,就像你打开网页返回了404被认为是正常的。比如当PaoPaoGateway炸了的时候,CUSTOM_FORWARD仍会返回失败的消息。 当搭建的DNS服务器恢复正常了不能及时切回来。这个不仅是DNS服务端缓存,也是相对于客户端而言的,就FakeIP场景而言,假设在故障的时候没有返回FakeIP而是其他IP,DNS或者其他服务恢复之后,之前的IP结果在客户端就会有通常最多10分钟左右的缓存,这就造成换回来也很不平滑。 那么要做到平滑的DNS故障转移也很简单:先查询主DNS,在一个较小的查询阈值之内如果没有匹配查询请求的DNS结果,那么就转移到备用DNS上查询,如果有查询结果,把DNS应答的ttl设置为1,让缓存快速过期,待主DNS恢复后,平滑切换回来。实际上用mosdns写配置也不复杂,但为了更便捷的配置和专注于DNS故障转移的用途,方便给下次遇到同样需求的问题直接甩一个程序链接,基于PaoPaoDNS的修改版本的mosdns的基础上写了一个故障转移专用的DNS转发器————mini-ppdns。
mini-ppdns https://github.com/kkkgo/mini-ppdns 专注于 DNS 故障转移的迷你DNS转发器。mini-ppdns 是从PaoPaoDNS项目精简修改而来的纯粹转发器,致力于提供极致轻量化且高效的平滑 DNS 故障转移体验。 Hook功能通过定时执行外部命令来主动检测主DNS的可用性,可以用脚本扩展覆盖更多主备切换场景。
快速启动 假设你的本地自建DNS是10.10.10.8,你的运营商DNS或者需要故障转移的DNS是223.5.5.5,
那么最简单的命令行启动:
1
mini-ppdns -dns 10.10.10.8 -fall 223.5.5.5
可以指定DNS端口和多个上游:
1
mini-ppdns -dns 10.10.10.8:53,10.10.10.9:53 -fall 223.5.5.5:53,119.29.29.29:53
参数详解 -dns:本地自建主 DNS 上游(必填),支持多个地址以逗号分隔,如 10.10.10.8,10.10.10.9。-fall:备用/运营商 DNS 上游(必填),支持多个地址以逗号分隔。-listen:可以指定监听地址和端口,默认是监听所有可监听的私有地址(跳过公网地址)。mini-ppdns -dns 10.10.10.8 -fall 223.5.5.5 -listen 127.0.0.1:53-aaaa:可以指定 AAAA 记录的处理模式(默认为 no,屏蔽 AAAA 查询):no(默认):屏蔽所有 AAAA 查询,直接返回空结果。yes:允许 AAAA 查询,走正常的主 DNS→备用 DNS 故障转移逻辑。noerror:允许 AAAA 查询,若主 DNS 返回 NOERROR(即使 Answer 为空),直接采信该结果,不再尝试备用 DNS。-force_fall:可以指定某些客户端 IP 段总是走备用 DNS。mini-ppdns -dns 10.10.10.8 -fall 223.5.5.5 -force_fall=192.168.1.10,192.168.2.0/24-qtime:指定故障转移的延迟阈值(单位 ms,默认 250)。-lite:是否开启精简响应模式(默认为 yes,仅保留请求的主记录,去掉无关记录)。-debug:输出详细的调试日志。-d:在后台运行。-version:打印版本信息并退出。-config:可以指定加载配置文件,可以配置 mini-ppdns.ini 如下: 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
# 本地搭建的主DNS
[dns]
10.10.10.8:53
10.10.10.9:53
# 故障转移备用/运营商DNS
[fall]
223.5.5.5:53
119.29.29.29:53
# 监听地址端口
# 若未指定listen,或者指定为通配地址 `0.0.0.0:<port>` 或 `[::]:<port>`,
# 会自动展开为本机所有私有/环回地址(跳过公网),避免将 DNS 暴露到公网接口
[listen]
127.0.0.1:53
192.168.1.1:53
# 可以指定某些IP段总是走运营商/故障转移的DNS
[force_fall]
# 支持以下三种写法:单个 IP、CIDR 端、以及特定的 IP Range
# FakeIP场景可以利用这个功能,间接实现某些设备不走代理
192.168.1.10
192.168.2.0/24
192.168.3.2-192.168.3.100
# 在IP前面加^号可以取反,比如^192.168.1.10表示除了192.168.1.10以外的IP段都走运营商/故障转移的DNS
# 所有取反的规则是AND的逻辑,也就是说,只有所有取反的规则都满足,才走运营商/故障转移的DNS
# 非取反的规则是OR的逻辑,也就是说,只要有一个非取反的规则满足,就走运营商/故障转移的DNS
# 当同时存在非取反与取反规则时,非取反规则优先判定;取反规则仅在不匹配任何非取反规则时生效。
^192.168.1.123-192.168.1.125
^192.168.1.126
^192.168.10.0/24
# FakeIP场景可以利用取反功能,间接实现只有某些设备走代理
[adv]
# 转移延迟阈值(毫秒)
qtime= 250
# 是否开启 IPv6 aaaa记录查询解析(no/yes/noerror)
# no:屏蔽aaaa查询直接返回空(默认)
# yes:允许aaaa查询,走正常故障转移逻辑
# noerror:当主DNS返回NOERROR时直接采信(即使answer为空),仅主DNS出错才走备用DNS
aaaa= no
# 是否开启精简响应模式,去掉无关记录(yes/no)
lite= yes
# 信任主DNS返回的指定rcode,直接采信不再请求备用DNS(默认为空,不信任)
# 例如:trust_rcode=0,3 表示信任NOERROR(0)和NXDOMAIN(3)
# 适用于某些信任"屏蔽记录"的场景,比如主DNS返回了空记录的noerror,但实际上备用DNS可以正常解析出记录。
# trust_rcode=0,3
# bogus-priv功能(默认启用,等效于OpenWRT的 option boguspriv '1') 设置为0可以关闭此功能
# boguspriv=1
# 手动指定DHCP lease文件路径,用于本地PTR记录解析(支持逗号分隔多个文件)
# 初始化时所有文件都不存在则不启用PTR解析,该功能在openwrt等路由器系统上会自动查找可用配置,无需指定
# lease_file=
# 手动指定hosts文件路径,用于本地记录解析(支持逗号分隔多个文件)
# 支持正向(A/AAAA)和反向(PTR)查询,格式同 /etc/hosts
# 不配置时自动尝试 /etc/hosts,支持热重载(每5秒检测文件变化)
# hosts_file=
[hosts]
# 在配置文件中直接写入hosts记录,格式同 /etc/hosts
# 同时支持正向(A/AAAA)和反向(PTR)查询
# [hosts]条目优先级高于hosts_file文件中的同名条目
# paopao.dns 总是用主DNS解析,除非hosts已经有定义
#10.10.10.53 paopao.dns
#1.2.3.4 example.com
# Hook功能通过定时执行外部命令检测主DNS状态,故障时自动切换至备用DNS,恢复后自动切回主DNS
[hook]
# 执行的命令,比如检测socks5代理是否可以访问网络
exec= "curl -o /dev/null -s -w %{http_code} --proxy socks5h://10.10.10.3:1080 http://www.google.com/generate_204"
# 执行命令的退出状态码(exit status),比如0表示成功,不指定的时候则不检查状态码
exit_code= 0
# 执行命令的输出中是否包含某个关键字,不指定的时候则不检查输出
keyword= "204"
# 检测间隔,每sleep_time秒检测一次,默认值为60
sleep_time= 60
# 重试间隔,检测失败后等待retry_time秒后重试,默认值为5
retry_time= 5
# 当连续count次检测失败后,定义主DNS为故障,默认值为10
count= 10
# 当因为hook功能切换到备用DNS后,执行的命令(如发送通知)
# 注意:触发时会自动清空所有现存的系统DNS缓存,避免受主DNS的过时记录影响。
# switch_fall_exec命令会自动延迟 retry_time / 2 配置的时间后才执行,以等待备用DNS切换生效
# 从而确保执行switch_fall_exec脚本时系统已可以使用备用DNS正常解析(如上报时所用的通知域名)
switch_fall_exec= "curl -sk -o /dev/null --data 'Main DNS is DOWN!' --retry 3 https://ntfy.sh/mydns_status"
# 当因为hook功能切换回主DNS后,执行的命令(如发送通知)
switch_main_exec= "curl -sk -o /dev/null --data 'Main DNS is UP!' --retry 3 https://ntfy.sh/mydns_status"
在openwrt路由器上部署 在openwrt上部署说起来简单也复杂,因为很多openwrt里面有各种神神秘秘的插件互相干扰。其中不少会劫持DNS,此处部署过程仅包含一些常见的坑和注意事项。当然部分过程也适用于其他linux系统。
去release 下载适合你的硬件架构的二进制文件。如果不清楚自己的硬件是什么架构,可以在终端输入uname -m。其中release名字带UPX的是为了给一些储存空间紧张的设备用的,如果你的设备空间充足下正常版本即可。 把mini-ppdns上传到你的设备,为了方便你可以上传到/usr/sbin/mini-ppdns,加执行权限chmod +x /usr/sbin/mini-ppdns。将你的配置文件储存在/etc/mini-ppdns.ini,然后执行mini-ppdns -config /etc/mini-ppdns.ini看看是否输出正常(比如提示某个端口已经被监听)。 添加自启动脚本。在openwrt上最简单的是编辑 /etc/rc.local,在 exit 0 之前添加你的启动命令,带上 -d 参数,程序会自动到后台,不会阻塞启动。当你修改了局域网的IP段或者配置,你需要重新启动mini-ppdns。 1
2
/usr/sbin/mini-ppdns -config /etc/mini-ppdns.ini -d
exit 0
或者写一个守护脚本加计划任务或者修改服务,此处提供了一个参考脚本:
https://github.com/kkkgo/mini-ppdns/blob/main/mini-ppdns.sh 使用方法,把脚本上传到/usr/sbin重命名为/usr/sbin/mini-ppdns加执行权限,crontab -e编辑计划任务:* * * * * /usr/sbin/mini-ppdns.sh即可。脚本启动之前检测是否已经存在mini-ppdns进程,如果存在就直接退出,因此可以直接让计划任务每分钟执行来作为守护。执行mini-ppdns.sh restart可以重载配置。
普通linux到这里已经弄完了,但一些linux安装过程中会自带DNS服务器导致占用监听端口,比如Ubuntu,可以禁用自带的DNS解析器: 1
2
3
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
#禁用后记得手动编辑/etc/resolv.conf手动写入DNS服务器
当然,openwrt自带dnsmasq,我们需要把他停用。
编辑 /etc/dnsmasq.conf 或在 OpenWrt 管理界面 (网络 -> DHCP/DNS -> 高级设置) 中:
1
2
# 将 DNS 端口设置为 0,彻底禁用 dnsmasq 的 DNS 解析功能(仅保留 DHCP 功能)
port=0
某些dnsmasq的禁用DNS解析会导致DHCP不下发DNS。所以我们还需要手动下发路由器的DNS。 点击网络-接口-LAN-DHCP服务器-高级设置,在DHCP选项里面,手动设置DHCP的附加选项。下发DNS的选项是6,比如你的路由器IP是10.10.10.1,那么填入6,10.10.10.1。当然,在FakeIP场景下,你也可以顺便填入option 121 。 某些修改的openwrt版本会有DNS重定向的劫持选项需要手动关闭。【参考1】 【参考2】 。 在IPv6环境下,需要关闭路由器的DNS的IPv6 DNS下发。 在docker上部署 尽管在这个场景下使用docker部署不太常见,但仍有很多没有开放终端的设备只能跑docker。 以下是非常简单的docker compose的示例配置,可以根据自己的实际环境调整,或者复制给AI转换成你实际的容器环境Cli。 把mini-ppdns.ini和对应你设备架构的mini-ppdns二进制放在docker-compose.yml同一目录。
1
2
3
4
5
6
7
8
9
10
11
services :
mini-ppdns :
image : public.ecr.aws/sliamb/tool
container_name : mini-ppdns
network_mode : host
working_dir : /app
entrypoint : ["/app/mini-ppdns" ]
command : ["-config" , "mini-ppdns.ini" ]
volumes :
- ./:/app:ro
restart : unless-stopped
此处定义网络模式是 host(直接使用宿主机网络),因此不需要映射端口,可根据自己需要调整。
文章作者
Sliamb
上次更新
2026-03-01
许可协议
CC BY-SA 4.0 / 转载文章请保留链接。
文章链接
https://blog.03k.org/post/mini-ppdns.html
参与讨论
点击下方按钮参与讨论。或者点击下方按钮添加QQ群。