前言

相信大家都接触过openwrt,从几年前仅4M的flash就能刷openwrt的tplink路由器,到现在大家玩的软路由,openwrt都在HomeLab里面扮演着“智能路由器”的角色,一个路由器能干的事情也越来越多,像是DNS、各种科学的插件、远程下载、甚至塞上docker等等。
而写这个系列探讨的事情恰恰相反,为了避免ALL IN BOOM的结局,追求最棒的性能和稳定性,在主路由器只当纯粹的路由器,很硬的硬路由的前提下,如何搭建局域网内的各种服务,实现更HiFi的网上冲浪。或者说,在企业级的环境下,如何实现更好的“优化”方案,毕竟企业级的路由器就真的是路由器。实际上,方案也经过了实际的千人以上企业级环境使用验证,很久之前就想整理出来写文章。缺点也很明显,如果你是ALL IN BOOM的软路由用户,要低碳低功耗,那这个文章系列可能不太适合你的需求。

为啥需要递归DNS

谈起DNS,相信很多折腾软路由都不陌生,大家可能为了一个“最佳的DNS”想尽了各种办法,比如收集各种公共DNS服务器,对他们进行测速,看看哪个快;又或者使用类似SmartDNS这种工具,把返回的结果进行再测速。但无论如何,你查询的DNS的路径都是这样的(以查询ipsu.03k.org为例):

1
2
电脑->ipsu.03k.org->114.114.114.114
114.114.114.114: 你好,解析结果是111.174.61.225

那这个结果又是怎么来的呢?当然是114作为一个公共DNS服务器,帮你查询了权威DNS服务器(指域名所在的DNS托管服务器,用nslookup查询提示的非权威应答就是这个意思)得到的结果,而114这种公共DNS服务器我们把他叫做“递归DNS”服务器。他的查询过程是这样的:

1
2
3
4
5
6
114 -> root server(根服务器):ORG域名在哪?
root server-> (org root server list)
114 -> org root server: 03k.org谁在托管?
org root server-> dnspod
114 -> dnspod:ipsu.03k.org的解析是?
dnspod : 111.174.61.225

如果你想知道114用了什么IP来帮你查询权威服务器,你可以运行下面的命令:

1
2
3
4
5
6
7
8
9
>nslookup -type=TXT whoami.ds.akahelp.net 114.114.114.114
服务器:  public1.114dns.com
Address:  114.114.114.114

非权威应答:
whoami.ds.akahelp.net   text =

        "ns"
        "58.217.249.132" #这个就是114连接权威DNS服务器用的IP

或者,你可以使用03k.org的服务:

1
2
3
4
5
6
7
>nslookup whoami.03k.org 114.114.114.114
服务器:  public1.114dns.com
Address:  114.114.114.114

非权威应答:
名称:    whoami.03k.org
Address:  58.217.249.132 #这个就是114连接权威DNS服务器用的IP

以上过程只是一个简单的示例,实际上如果你还有CNAME记录套娃的话,这个查询过程也就一层层套下去了,如果你有一个畅通无阻的网络,你可以安装dig命令,使用dig +trace 域名命令来执行一次原生的查询过程:

 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
root@ # dig +trace ipsu.03k.org

; <<>> DiG 9.18.11 <<>> +trace ipsu.03k.org
;; global options: +cmd
.                       3       IN      NS      k.root-servers.net.
.                       3       IN      NS      m.root-servers.net.
.                       3       IN      NS      c.root-servers.net.
.                       3       IN      NS      a.root-servers.net.
.                       3       IN      NS      f.root-servers.net.
.                       3       IN      NS      j.root-servers.net.
.                       3       IN      NS      d.root-servers.net.
.                       3       IN      NS      i.root-servers.net.
.                       3       IN      NS      g.root-servers.net.
.                       3       IN      NS      e.root-servers.net.
.                       3       IN      NS      l.root-servers.net.
.                       3       IN      NS      h.root-servers.net.
.                       3       IN      NS      b.root-servers.net.
.                       3       IN      RRSIG   NS 8 0 518400 20230418050000 20230405040000 60955 . pmsORdgM7AuC44ri0FQ9LUVtDP5itybmggpP5ycwMU+dQjuxDMWxgdT8 /6VkyL6TUiNAkO2MzM+Y6om641/fGAwwi4wfbL5/3unvxiXcbo9egdwr T7YL2Sa0WGfYxq+1RT8embnqwp28L2nfo8Ym8B/T04iRFrJOLa5KiS2T cljrtPX1+uZEmJC//JwWJ5iYq3BqJoWwEK4jnrv4W7SmGGGuuLu4r7UH vpxLvRsBfs32jqrmpwtBJBwfUjne89UYbZZ5QhfrsO49spOGhKZS4xkk Bq1jxUz28P7h2oCQgUhDr82dt8v1FuiWZxZBILdLCDjhEHFWWAVSOs6J EqJKYA==
;; Received 717 bytes from 172.23.128.1#53(172.23.128.1) in 870 ms

org.                    172800  IN      NS      a0.org.afilias-nst.info.
org.                    172800  IN      NS      a2.org.afilias-nst.info.
org.                    172800  IN      NS      b0.org.afilias-nst.org.
org.                    172800  IN      NS      b2.org.afilias-nst.org.
org.                    172800  IN      NS      c0.org.afilias-nst.info.
org.                    172800  IN      NS      d0.org.afilias-nst.org.
org.                    86400   IN      DS      26974 8 2 4FEDE294C53F438A158C41D39489CD78A86BEB0D8A0AEAFF14745C0D 16E1DE32
org.                    86400   IN      RRSIG   DS 8 1 86400 20230418050000 20230405040000 60955 . phJIBr06XFE1kh5vOQCbShUMRBXpS7Q88Do4kuxaSrv5TXAokEe6z2Wr GpnQCQdYdDyLAFdso5tKQeJOisjxq3J5dH//X7qaWeF+DwlGKhySmBX9 3HXF5UH07jlu6V9nUvIvmqCUovd3J15f1QHLYEWDP/6GRe04maU+JxPF 4L1lyz8EGxnWHR7B5Q7ZrsJVGZBSK0JsPVGKkzhsZPJwVbqZ2qk3JeGG 3/b67f4hjJLyNjXxX8LC1TBxIT60m/LLOfBc64XCAxCPdOKwb96rzGz/ mBrOezUu7nm7A3NAiVgmsBgbznKwesqEpj1NDn1AP0D6Q3M6RC2CKYcp e5t3nQ==
;; Received 778 bytes from 199.7.91.13#53(d.root-servers.net) in 190 ms

03k.org.                3600    IN      NS      sunfish.dnspod.net.
03k.org.                3600    IN      NS      cold.dnspod.net.
gdtpongmpok61u9lvnipqor8lra9l4t0.org. 3600 IN NSEC3 1 1 0 332539EE7F95C32A GDTREA8KMJ2RNEQEN4M2OGJ26KFSUKJ7 NS SOA RRSIG DNSKEY NSEC3PARAM
gdtpongmpok61u9lvnipqor8lra9l4t0.org. 3600 IN RRSIG NSEC3 8 2 3600 20230426155102 20230405145102 10821 org. fXJbeCpOw+f3wo34p2GnXloPvij05Fnp1Epj5Ztxw4Riyz3lHP+oUUzw NMBJCAYyqFT7w/q9x3Cv+cYSJAeqORtolbnIsu62+p9NWnRmGb/czUzn SKy7nIIyYkcpe3PtaClBOt/BvIyzvYTcQXuxEoXr9rxMUZSOWSs1DD3C VxE=
9shp8vrpv4q8aogkf458qdde5l2djlrv.org. 3600 IN NSEC3 1 1 0 332539EE7F95C32A 9SHVJDC614NF0O9ONTO1S0CV0E9769TH NS DS RRSIG
9shp8vrpv4q8aogkf458qdde5l2djlrv.org. 3600 IN RRSIG NSEC3 8 2 3600 20230422152851 20230401142851 10821 org. grjEWOUosPlrtFEO4G+uSz4+0u6tOnIil9RmEuWdMOEeYkCN3SM0SGc0 niR0e4X2xnEQg2/OLBpSUJ7A2D8+rKF/fS63erfWFnAyd8WIVMPCqSa1 K7KtQ9i3MmqiNlouqpiSV1tREgnAKrQ8reY3qqpdlcfIhSgA08xKwkbS 9rk=
;; Received 593 bytes from 199.19.57.1#53(d0.org.afilias-nst.org) in 130 ms

ipsu.03k.org.           600     IN      CNAME   su.baidu.com.cname.yunjiasu-cdn.net.
03k.org.                86400   IN      NS      sunfish.dnspod.net.
03k.org.                86400   IN      NS      cold.dnspod.net.
;; Received 151 bytes from 117.89.178.184#53(cold.dnspod.net) in 30 ms

可以看到,这个过程比起直接查询公共DNS服务器,需要走更多的“路径”,而最后一行也就是权威服务器给的应答,dig也给出每一步所需要的耗时ms,前后加起来也有1秒多了,这也是为什么会有公共DNS服务器的存在,因为如果大家都这么查询的话,上网冲浪实在是比天翼3G还慢。
那既然大家都用公共DNS的话,选择哪个DNS有什么区别吗?首先我们不考虑客户端到公共DNS服务器之间的延迟这个问题,就返回的解析结果来考虑,不同的DNS服务器可能返回不一样的结果。一个常见的场景是,很多域名使用了CDN,解析结果根据所在地理IP来返回,比如你是广东电信,返回在广东电信的服务器地址,而在北京联通就返回北京联通的地址,尽管公共DNS服务器一般都有Anycast(同一个IP有很多台服务器),DNS服务器的地理位置也可能不尽相同,IP的精度也不能面面俱到,有时候结果还不如当地运营商的公共DNS。虽然有ECS这种协议存在(DNS ECS是DNS协议的一个扩展,它允许递归DNS解析器在发送给权威DNS服务器的请求中包含终端用户IP地址数据的部分),但首先它是用在递归DNS上的,也就是部署在114这种公共服务器上的,其次你请求的域名所在的权威DNS要支持ECS协议才可以,说白了这个协议对局域网用户来说没什么用(因为你什么都做不了)。
那么有什么完美的DNS服务器呢?答案是我们可以拥有属于自己的递归DNS服务器,说白了就是把114这样的DNS服务器装你家里。这样你的每个请求都非常的原生地到达了权威DNS服务器,获取的结果可谓是准确中的准确。你再也不需要对DNS服务器进行收集和测速,也不需要对解析结果进行测速(实际上,你已经获取了原生的准确解析结果了,测它没有什么意义,多个DNS解析结果本来就是为了能DNS轮询负载均衡故障转移,比如在一个大局域网大家都用同一个IP连接视频播放地址可能就不是一个好主意),实在是强迫症治愈良药。
除了以上原因,一个不太常见的问题就是,你觉得公共DNS服务器值得信任吗?在不考虑DNS解析结果被运营商劫持的情况下,你怎么能保证它返回的结果值得信任?毕竟你不是直接问的权威DNS服务器,自然结果也是凭良心了(虽然有DNSSEC这种东西存在,但它并不能解决DNS劫持和污染,你除了知道它结果可能是错的之外什么都做不了)。即使DNS结果值得信任,但你的查询记录可能会被公共DNS服务器记录日志,四舍五入你上了什么网站,什么时间上的,IP地址是什么,这些都可以入了公共DNS的大数据系统,也就是可能会造成一定的隐私问题。尽管有的公共DNS澄清自己是干净无记录的,但你怎么能保证他们能遵守诺言呢?是的,你不能保证。一个更加离谱的可能是,如果公共DNS服务器被攻击,那你的查询结果可能被引导到恶意网站,就算这种可能性比较低,公共DNS服务器也有故障的时候,你可能又开始考虑:我能保证谁的公共DNS服务器100%稳定性?是的,你不能保证。但当你拥有一台属于自己的递归DNS服务器的时候,稳定性和隐私问题将由你自己掌控。

如何搭建递归DNS服务器

这里推荐使用unbound作为递归DNS服务器程序,理由如下:

  • unbound是一个老牌安全的开源递归DNS服务器,主要由NLnet Labs,VeriSign Inc.,Nominet开发。它可以直接查询权威的DNS服务器,而不需要依赖其他上游DNS服务器,稳定性经过长久验证。
  • unbound支持redis缓存(redis版本要编译安装),性能表现优异,曾经对比过多款递归DNS服务程序,从压力测试结果来看unbound是非常不错的选择,如果说哪个DNS服务的缓存性能和算法值得信赖,unbound肯定是其中之一(well,没错我说的就是GitHub上各种奇奇怪怪的智能DNS服务的缓存会出现各种奇奇怪怪的bug)。
  • 具有完美可控的缓存和预读取功能,DNS记录可以通过redis持久化和接近ttl过期的时候进行预读取,即使过期也能通过返回一个低值ttl的旧记录后秒刷新,实现无感知的“网页秒开”,当然,你内存越大能存的记录越多体验越好啦(不过一般来说就算是重度企业使用能存下2G也很厉害了)。
  • unbound支持完整的DNS协议实现,能保证各种结果的准确性和其他可能用不上的高级自定义。

具体的搭建可以参考官方文档:
https://unbound.docs.nlnetlabs.nl/en/latest/
以及优化指南:
https://www.nlnetlabs.nl/documentation/unbound/howto-optimise/
看到这里你是不是开始头疼了,要编译unbound,还要搞清楚一堆参数配置,还要配置redis,如果你在China大陆,你还得考虑境外DNS防污染的问题,实在头疼。
Well,这就是接下来要说的重点,为了能一键部署,我做了个docker镜像,一次满足你所有需求~!

PaoPao DNS docker

泡泡DNS是一个能一键部署递归DNS的docker镜像,它使用了unbound作为递归服务器程序,使用redis作为底层缓存,此外针对China大陆,还有智能根据CN分流加密查询的功能,也可以自定义分流列表,可以自动更新IP库,分流使用了mosdns程序,加密查询使用dnscrypt程序,针对IPv4/IPv6双栈用户也有优化处理。
为啥叫泡泡DNS,因为部署DNS就像吹泡泡一样简单(其实我写docker的时候刚好在看《泡泡》)。
PaoPaoDNS
(AI生成的LOGO)
pull size
Github 项目地址https://github.com/kkkgo/PaoPaoDNS
(っ◞‸◟c)都看到这里了,点个Star
更新日志 以下文档说明以Github最新文档为准

docker镜像: sliamb/paopaodns
泡泡DNS适合的使用场景:

  • 场景一:仅作为一个纯粹准确的递归DNS服务器,作为你其他DNS服务程序的上游,替代114.114.114.114,8.8.8.8.8等公共DNS上游
  • 场景二:作为一个局域网内具备CN智能分流、解决污染问题和IPv6双栈优化的DNS服务器,或者你的局域网已经从IP层面解决了“科学”的问题,需要一个能智能分流的DNS服务器。

使用方法和参数说明

简单来说,那么你可以运行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#拉取最新的docker镜像
docker pull sliamb/paopaodns:latest
#假设你的数据要放在/home/mydata
docker run -d \
--name paopaodns \
-v /home/mydata:/data \
-e CNAUTO=yes \
--restart unless-stopped \
-p 53:53/udp \
sliamb/paopaodns

如果你需要容器运行在同一个局域网段而不是单独映射端口,除了一些NAS有现成的界面点点点,原生docker你可以考虑使用macvlan如下的配置(假设你的网络是192.168.1.0/24):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 启用eth0网卡混杂模式
ip link set eth0 promisc on
# 创建macvlan网络
docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth0 macvlan_eth0
#拉取最新的docker镜像
docker pull sliamb/paopaodns:latest
# 运行容器并指定IP
docker run -d \
--name paopaodns \
-v /home/mydata:/data \
-e CNAUTO=yes \
--restart unless-stopped \
--network macvlan_eth0 --ip 192.168.1.8 \
sliamb/paopaodns

如果你的网络端口没有冲突,也可以考虑使用docker host网络模式以获得最佳性能。
如条件允许建议使用docker compose部署
如果你的网络环境访问Docker Hub镜像有困难,可以尝试使用public.ecr.aws镜像:

  • 示例: docker pull public.ecr.aws/sliamb/paopaodns
  • 示例: docker run -d public.ecr.aws/sliamb/paopaodns

验证你的递归DNS正常运行(假设你的容器IP是192.168.1.8),可以执行以下命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>nslookup -type=TXT whoami.ds.akahelp.net 192.168.1.8
服务器:  PaoPaoDNS,blog.03k.org
Address:  192.168.1.8

非权威应答:
whoami.ds.akahelp.net   text =

        "ns"
        "116.31.123.234"  #连接权威DNS服务器的IP=你的宽带IP
Linux可使用dig命令:  
dig whoami.ds.akahelp.net @192.168.1.8 txt -p53

或者,你可以使用03k.org的服务:

1
2
3
4
5
6
7
>nslookup whoami.03k.org 192.168.1.8
服务器:  PaoPaoDNS,blog.03k.org
Address:  192.168.1.8

非权威应答:
名称:    whoami.03k.org
Address:  116.31.123.234 #连接权威DNS服务器的IP=你的宽带IP

如果返回的IP和你宽带的出口IP一致的话,说明你的递归DNS服务正常运作了。

搭建完请简单验证所有DNS组件是否工作正常:

1
2
3
4
# 在容器内置执行 test.sh
docker exec paopaodns test.sh
# 如果执行后输出 ALL TEST PASS,则所有组件都工作正常。
# 如果显示 FAIL,可以执行 debug.sh 进一步分析原因。

同时你可以查阅更新日志的最新版本公告时间,检查输出的镜像版本时间是否大于等于当前最新版本。

需要注意的是,如果你的网络有“自动分流IP”的功能,请把容器的IP加入不分流的名单,因为权威DNS需要准确的IP去判断,IP分流会影响权威DNS的判断。此外,一些软路由存在劫持DNS请求的情况,解决办法参见这个issue
[DNS hijack]DNS劫持算是经常问的高频问题了,请参考
docker的运行家用建议内存2G,企业环境建议16G,再大了意义不太大(实际内存占用差不多达到4G已经很厉害了,当然,内存越大性能越好,命中率体验更棒),容器启动的时候会根据可用内存调整配置文件参数,占用内存不会超过上限。根据Github的使用者反馈,有最低512M内存的ARM路由器流畅使用的案例。实际上,在最低参数启动下的缓存也是能满足家庭使用需求的。
直接拉取镜像运行即可,你需要挂载容器的/data目录,该目录会存放所有配置文件、DNS缓存文件和更新的数据,容器重新启动的时候也会直接读取。如果你是在隔离的网络中运行的容器,你需要映射53端口出来,需要注意的是你的宿主也可能运行着DNS服务,跟53端口冲突,这个需要你自己解决(比如禁用自带的DNS服务)。当然,最佳的运行网络环境是跟你的客户端在同一个网段设置一个静态IP,原生docker的话可以使用macvlan网络实现,或者某些NAS提供了docker的桥接到物理网卡的点点点选择(如果你要写docker compose的话,威联通的NAS的桥接网络驱动类型是qnet)。
环境变量参数如下:

环境变量默认值可用值
CNAUTOyesyes,no
DNSPORT53端口值
DNS_SERVERNAMEPaoPaoDNS,blog.03k.org不含空格的英文字符串
SERVER_IP空,非必须。IP地址,如10.10.10.8
SOCKS5空,非必须。如:10.10.10.8:7890
TZAsia/Shanghaitzdata时区值
UPDATEweeklyno,daily,weekly,monthly
IPV6nono,yes,only6,yes_only6,raw
CNFALLyesno,yes
EXPIRED_FLUSHyesno,yes
CUSTOM_FORWARD空,可选功能IP:PORT,如10.10.10.3:53
CUSTOM_FORWARD_TTL01-604800
AUTO_FORWARDnono,yes
AUTO_FORWARD_CHECKyesno,yes
USE_MARK_DATAyesno,yes
RULES_TTL01-604800
USE_HOSTSnono,yes
HTTP_FILEnono,yes
SAFEMODEnono,yes
ADDINFOnono,yes
SHUFFLEnono,yes,lite,trnc
QUERY_TIME2000mstime.Duration

用途说明:

  • CNAUTO:是否开启CN大陆智能分流,如果位于境外可配置为no。当CNAUTO=no时,除递归以外的功能(包括规则/列表等)将不会工作。
  • DNSPORT:设置DNS服务器端口,仅在CNAUTO=no时生效
  • DNS_SERVERNAME:DNS的服务器名称,你使用windows的nslookup的时候会看到它。注意,该选项仅在容器的网卡IP和外部网卡IP一致的时候生效(比如macvlan)。
  • SERVER_IP:指定DNS服务器的外部IP。假设你的DNS容器是宿主10.10.10.4映射出来的端口而不是独立的IP,设置该项为10.10.10.4可以让你看到正确的DNS_SERVERNAME。同时会设定域名paopao.dns指向该IP地址10.10.10.4,可配合其他服务使用。
  • SOCKS5:为分流非CN IP的域名优先使用SOCKS5查询(如10.10.10.8:7890,强制使用socks5查询则加上@,比如@10.10.10.8:7890),但没有也能查,非必须项。仅在CNAUTO=yes时生效。SOCKS5初始化会有大概3分钟的延迟连接测试过程,期间的解析结果并非最优延迟。
  • TZ: 设置系统的运行时区,仅影响输出日志不影响程序运行
  • UPDATE: 检查更新根域数据和GEOIP数据的频率,no不检查,其中GEOIP更新仅在CNAUTO=yes时生效。注意:daily,weekly,monthly分别为alpine默认定义的每天凌晨2点、每周6凌晨3点、每月1号凌晨5点。更新数据后会瞬间完成重载。
  • IPV6: 仅在CNAUTO=yes时生效,是否返回IPv6的解析结果,默认为no,如果没有IPv6环境,选择no可以节省内存。设置为yes返回IPv6的查询(为分流优化,非大陆双栈域名仅返回A记录)。如果设置为only6,则只对IPv6 only的域名返回IPv6结果。如果设置为yes_only6,则对大陆域名返回IPv6的解析结果(相当于yes),对非大陆域名只对IPv6 only的域名返回IPv6结果(相当于only6)。如果设置为raw,则不对IPv6结果做任何处理,直接返回原始记录。
  • CNFALL: 仅在CNAUTO=yes时生效,在遇到本地递归网络质量较差的时候,递归查询是否回退到转发查询,默认为yes。配置为no可以保证更实时准确的解析,但要求网络质量稳定(尽量减少nat的层数),推荐部署在具备公网IP的一级路由下的时候设置为no; 配置为yes可以兼顾解析质量和网络质量的平衡,保证长期总体的准确解析的同时兼顾短时间内网络超时的回退处理。
  • EXPIRED_FLUSH: 该选项为yes,且在CNAUTOCNFALLyes时生效。该选项默认值为yes。当开启该选项时,将会主动监测递归结果中出现的乐观缓存,在乐观缓存返回后数秒后检查是否成功递归刷新了新的解析结果,如果递归失败(由两次ttl记录差值对比),将会主动回收清除该缓存。开启该选项可以有效避免乐观缓存因网络连接性不稳定而一直滞留过期记录的问题,提高DNS解析结果的实时性。
  • CUSTOM_FORWARD: 仅在CNAUTO=yes时生效,指定一个自定义的IP:端口的DNS服务器,将force_forward_list.txt内的域名列表转发到到CUSTOM_FORWARDDNS服务器。该功能可以配合第三方旁网关的fakeip域名嗅探sniffing等特性完成简单的域名分流效果。
  • CUSTOM_FORWARD_TTL:该项设置的值大于0的时候生效,设定CUSTOM_FORWARD的ttl的最小值。
  • AUTO_FORWARD:仅在CNAUTO=yes时生效,配合CUSTOM_FORWARD功能使用,默认值为no,当设置为yes的时候,解析非CN大陆IP的域名将会直接转发到CUSTOM_FORWARD
  • AUTO_FORWARD_CHECK:在AUTO_FORWARD=yes时,转发前是否检查域名是否有效,避免产生无效查询。默认值为yes,设置为no则不检查。
  • USE_MARK_DATA:该项默认值为yes,当设置为yes的时候,将会自动更新下载预先标记处理的全球百万域名库,在判断大陆分流的时候优先使用该数据,该功能仅标记数据,后续如何处理取决你的设置(比如默认分流或者自动转发)。域名数据库来源于paopao-pref项目定期更新。该功能:
    • 优点:可以优化DNS泄漏问题、提供更快速精准高效的分流
    • 缺点:会占用更多内存
  • RULES_TTL:该项设置的值大于0的时候生效,将/data/force_ttl_rules.txt里面指定的域名转发到指定的DNS服务器,并修改其TTL值为RULES_TTL。该功能仅对A记录和AAAA记录生效,其他记录请参考进阶自定义示例一节。该功能可以适用于多种场景,比如想实现在异地的网络访问回家的DDNS域名的结果更实时一点,你可以把RULES_TTL设置为一个较低的值,然后把你的DDNS域名指定转发到对应的权威DNS服务器(也就是whois信息的NS服务器对应的IP地址,注意不要CNAME嵌套)即可。force_ttl_rules的规则格式为域名@服务器:端口,以下都是合法的格式:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# whois info 03k.org:
# Name Servers:
# cold.dnspod.net(129.211.176.224)
# sunfish.dnspod.net(112.80.181.45)

cncheck.03k.org@129.211.176.224
cncheck.03k.org@129.211.176.224:53
cncheck.03k.org@129.211.176.224,112.80.181.45
cncheck.03k.org@129.211.176.224:53,112.80.181.45:53
cncheck.03k.org@129.211.176.224,112.80.181.45:53

# 注意,在该示例中,cncheck.03k.org和其子域名比如www.cncheck.03k.org都会被转发。

此外,RULES_TTL功能也可以直接指定某个域名的A记录或者AAAA记录,或者“CNAME”到另一个域名。格式使用域名@@记录或者域名@@@记录,以下都是合法的格式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 重定向www.qq.com
www.qq.com@@1.2.3.4
www.qq.com@@5.6.7.8 #可以指定多项记录
www.qq.com@@2404:6800:4008:c06:1100:1101

# CNAME www.qq.com 到qq.03k.org
www.qq.com@@qq.03k.org

# 注意,使用@@为子域名匹配,上述示例会匹配*.www.qq.com和www.qq.com


# 如果需要精确匹配,可以使用@@@:
www.qq.com@@@1.2.3.4
www.qq.com@@@2404:6800:4008:c06:1100:1101
www.qq.com@@@qq.03k.org

# 使用通配符匹配(同样适用于CNAME):
# 注意,这不是正则匹配,参考[更新日志](https://github.com/kkkgo/PaoPaoDNS/discussions/187)
k8s.*.qq.com@@1.2.3.4 # k8s.xxx.qq.com和k8s.aaa.xxx.xxx.com都会被匹配
dl[0-8].qq.com@@1.2.3.4 # dl8.qq.com会被匹配,dl9.qq.com不会被匹配,dl88.qq.com不会被匹配
ftp[a-c].qq.com@@1.2.3.4 # ftpc.qq.com会被匹配,ftpd.qq.com不会被匹配
dl[0-8][2-4][x-z].qq.com@@1.2.3.4 # dl84z.qq.com会被匹配,dl11x.qq.com不会被匹配,dl23t.qq.com不会被匹配
  • CN_TRACKER:仅在CNAUTO=yes时生效,默认值为yes,当设置为yes的时候,强制trackerslist.txt里面tracker的域名走dnscrypt解析。更新数据的时候会自动下载最新的trakcerlist。该功能在一些场景比较有用,比如AUTO_FORWARD配合fakeip的时候可以避免使用fakeip连接tracker。
  • USE_HOSTS: 当设置为yes的时候,在启动时读取容器/etc/hosts文件。可以配合docker的-add-hosts或者docker compose的extra_hosts使用。仅在CNAUTO=yes时生效。
  • HTTP_FILE: 当设置为yes的时候,会启动一个7889端口的http静态文件服务器映射/data目录。你可以利用此功能与其他服务程序共享文件配置。
  • SAFEMODE: 安全模式,仅作调试使用,内存环境存在问题无法正常启动的时候尝试启用。
  • ADDINFO: 默认为no,设置为yes时,在DNS查询结果中增加ADDITIONAL SECTION的调试信息,如结果来源、查询延迟、失败原因等,使用dig命令就可以实时追踪域名结果来源,详情参考更新日志( https://github.com/kkkgo/PaoPaoDNS/discussions/61 )。该功能仅对CNAUTO=yes生效。
  • SHUFFLE 默认为no,设置为yes时,对解析的结果进行洗牌实现Round-robin DNS(注:SHUFFLE功能是对每次查询都进行洗牌输出。即使设置为no,在DNS的ttl过期后重新提供的DNS记录本身是经过unbound洗牌过的)。当设置为lite,返回精简的仅与请求类型匹配的回应,参考更新日志( https://github.com/kkkgo/PaoPaoDNS/discussions/108 );当设置为trnc,在lite选项的基础之上,如果返回的记录大于3个,则每次洗牌完成后仅在ttl有效期内输出3个随机记录,参考更新日志( https://github.com/kkkgo/PaoPaoDNS/discussions/109 )
  • QUERY_TIME:限制DNS转发最大时间,仅作调试使用,随意更改此值会导致你查不到DNS结果。
可映射端口端口用途
53提供DNS服务的端口,在CNAUTO=no时数据直接来自unbound,CNAUTO=yes时数据来自mosdns
5301在CNAUTO=yes时,递归unbound的端口,可用于dig调试
5302在CNAUTO=yes时,原生dnscrypt服务端口,可用于dig调试
5303在CNAUTO=yes时并设置了SOCKS5时,走SOCKS5的dnscrypt服务端口,可用于dig调试
5304在CNAUTO=yes时,dnscrypt的底层unbound实例缓存,可用于dig调试或者fakeip网关的上游
7889HTTP_FILE=yes时,http静态文件服务器端口

挂载共享文件夹/data目录文件说明:存放redis数据、IP库、各种配置文件,在该目录中修改配置文件会覆盖脚本参数,如果你不清楚配置项的作用,请不要删除任何注释。如果修改任何配置出现了异常,把配置文件删除,重启容器即可生成默认文件。
注:群晖等挂载权限问题参考

  • redis.conf:redis服务器配置模板文件,修改它将会覆盖redis运行参数。除了调试用途,一般强烈建议不修改它。容器版本更新将会覆盖该文件。
  • redis_dns_v2.rdb:redis的缓存文件,容器重启后靠它读取DNS缓存。刚开始使用的时候因为递归DNS有一个积累的过程,一开始查询会比较慢(设置了CNFALL=no的话,如果CNFALL=yes查询速度不会低于公共DNS),等到这个文件体积起来了就很流畅了。容器版本更新不会覆盖该文件。
    注意:redis_dns_v2.rdb文件生成需要累积达到redis的最持久化要求,取决于redis.conf的配置,默认最低2小时后才会进行一次持久化操作。如果你升级容器的镜像,可以删除其他所有配置文件而保留这个rdb文件。
  • unbound.conf:Unbound递归DNS的配置模板文件,除了调试用途,一般不要修改它。容器版本更新将会覆盖该文件。
  • unbound_custom.conf:Unbound的自定义配置文件,里面内置了一些高级自定义的示例。容器版本更新不会覆盖该文件。
    以下文件仅在开启CNAUTO功能时出现:
  • dnscrypt-resolvers文件夹:储存dnscrypt服务器信息和签名,自动动态更新。容器版本更新将会覆盖该文件。
  • Country-only-cn-private.mmdb:CN IP数据库,自动更新将会覆盖此文件。容器版本更新将会覆盖该文件。
  • global_mark.datUSE_MARK_DATA功能的数据库,自动更新将会覆盖此文件。容器版本更新将会覆盖该文件。
  • dnscrypt.toml:dnscrypt配置模板文件,修改它将会覆盖dnscrypt运行参数。除了调试用途,一般不修改它。容器版本更新将会覆盖该文件。
  • force_forward_list.txt: 仅在配置CUSTOM_FORWARD有效值时生效,强制转发到CUSTOM_FORWARDDNS服务器的域名列表,容器版本更新不会覆盖该文件。一行一条,语法规则如下:
    domain:开头域匹配: domain:03k.org会匹配自身03k.org,以及其子域名www.03k.org, blog.03k.org等。
    full:开头,完整匹配,full:03k.org 只会匹配自身。完整匹配优先级更高。
    regexp:开头,正则匹配,如regexp:.+\.03k\.org$Go标准正则
    keyword:开头匹配域名关键字,如以keyword: 03k.org会匹配到www.03k.org.cn
    尽量避免使用regexp/keyword会消耗更多资源。域名表达式省略前缀则为domain:。同一文本内匹配优先级:full > domain > regexp > keyword
  • force_dnscrypt_list.txt:强制使用dnscrypt加密查询结果的域名列表,匹配规则同上。容器版本更新不会覆盖该文件。
  • force_recurse_list.txt:强制使用本地递归服务器查询的域名列表,一般不会用到该list,强制递归的域名不会被生效CNFALL功能,匹配规则同上。容器版本更新不会覆盖该文件。
  • force_ttl_rules.txt: 参见RULES_TTL功能。修改将实时重载生效。容器版本更新不会覆盖该文件。
  • 修改force_forward_list.txtforce_dnscrypt_list.txtforce_recurse_list.txtforce_ttl_rules.txt将会实时重载生效。
  • 文本匹配优先级(custom_mod功能seq: top)>force_forward_list > force_dnscrypt_list > force_recurse_list > force_ttl_rules>(custom_mod功能seq: list)>其他自动分流逻辑
  • 注意事项:由于跨平台系统差异,不建议使用Windows自带记事本编辑。如果list出现了问题无法读取或者无法生效,可以直接删除list文件,重启容器会自动重建默认的list。如果你想解析的域名位于境外,并且没有境内CDN,而你又想获取原始记录(与force_forward_list.txt或者使用AUTO_FORWARD功能获取到的解析记录区分开),那么你应该把域名加进force_dnscrypt_list.txt而不是force_recurse_list.txt,因为基于个人网络环境差异,递归服务器位于境外的域名存在递归失败的可能。force_recurse_list.txt的应用场景一般应仅限于特殊域名递归调试,大部分场景都不适用于force_recurse_list.txt 此外,你可以根据文本匹配优先级灵活设置同一个域名子域名走不同的list。(参考 )。
  • trackerslist.txt:bt trakcer列表文件,开启CN_TRACKER功能会出现,会增量自动更新,更新数据来源 ,你也可以添加自己的trakcer到这个文件(或者向该项目提交),更新的时候会自动合并。修改将实时重载生效。容器版本更新不会覆盖该文件。
  • custom_cn_mark.txt: 在USE_MARK_DATA功能设置为yes的情况下,可以在/data/custom_cn_mark.txt中额外定义标记为CN的域名。填写格式与其他 force_*_list.txt一致。参考 https://github.com/kkkgo/PaoPaoDNS/discussions/122 。有限的使用场景:当域名被USE_MARK_DATA或者被IP库认定为非CN域名但你希望把他当成CN域名处理的时候。 参考更新日志
  • force_ttl_rules.txt: 参见RULES_TTL功能。修改将实时重载生效。容器版本更新不会覆盖该文件。
  • mosdns.yaml:mosdns的配置模板文件,修改它将会覆盖mosdns运行参数。除了调试用途,一般强烈建议不修改它。容器版本更新将会覆盖该文件。
  • custom_env.ini可以自定义环境变量,会覆盖在容器在启动时的环境变量。在容器启动后修改该文件将会导致MosDNS重载,但在容器启动后修改的环境变量不会影响已经启动的其他组件。配置的格式为key="value"(注意英文双引号),错误格式的环境变量将会被忽略加载。容器版本更新不会覆盖该文件。
  • custom_mod.yaml可以自定义一些高级功能,参见下面的custom_mod.yaml文件说明。错误的配置可能导致服务运行异常。需要重启容器应用配置。容器版本更新不会覆盖该文件。
    custom_mod.yaml配置说明
 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
# yaml配置格式请注意空格缩进和冒号,错误的配置将不会被加载。
# Zones可以配置指定域名转发。可以配置多组。
# 与`RULES_TLL`等功能不同,Zones配置的域名转发优先级默认最高,并且可以转发所有记录类型。
Zones:
 - zone: company.local
   dns: udp://10.10.10.3:53,udp://10.10.10.4:53
   ttl: 0
   seq: top
   socks5: no
# - zone: 此处填转发的域名。也可以是子域名,或者后缀。
#   dns: 可以逗号分隔指定多个DNS服务器、udp/tcp协议、端口。
#        指定超过3个DNS服务器将随机选择3个。
#   ttl: 指定该域名的最大ttl值。当设置非0的时候生效。
#        设置为0为不修改原来的ttl。
#   seq: top  #缺省选项,优先级最高,直接进行转发所有类型记录
#        top6 #与top一样但应用全局的IPv6设置
#        list #优先级最低,在匹配所有list后匹配
#   socks5: 可以配置为yes或者no,是否使用socks5代理来查询。
#           仅支持代理tcp协议的dns服务器。
 - zone: .corp
   dns: udp://10.10.10.3:53,udp://10.10.10.4:53
   ttl: 60
   seq: top6
   socks5: no
 - zone: ddns.example.com
   dns: tcp://172.64.32.176:53,tcp://108.162.192.176:53
   ttl: 3
   seq: list
   socks5: yes
# zone可以一次性写入多个域名,也可以使用list的规则写法,也可以直接引用外部文件(必须以反斜杠`/`的绝对路径开头),以空格隔开,例如:
 - zone: a.com domain:b.com full:c.com regexp:dl[0-9]+\.qq\.com$ keyword:google /data/mylist.txt
   dns: udp://10.10.10.3:53,udp://10.10.10.4:53
   ttl: 0
   seq: top
   socks5: no
# Swaps可以指定某个IP/CIDR段的解析结果替换为指定变量的结果。
# 以最终解析结果为准匹配。与Zones格式类似可以配置多组。
Swaps:
 - env_key: test_ip
   cidr_file: "/data/test_cidr.txt"
# env_key:配置指定变量的解析结果。可以配合custom_env.ini使用。
# cidr_file: 配置指定IP/CIDR段的文本文件。格式为每行一个IP/CIDR段。
# Swaps的env_key可以对应多个cidr_file,一个cidr_file仅可以匹配一个env_key,详情参考[更新日志](https://github.com/kkkgo/PaoPaoDNS/discussions/187)
# 注意:如果env_key或者cidr_file配置出错,容器日志会报错并忽略替换。
# 注:`Swaps`应用场景参考:[替换指定IP段的解析结果为指定IP](https://github.com/kkkgo/PaoPaoDNS/discussions/57 )   

Hosts:
 - env_key: test_ip
   zone: a.com domain:b.com full:c.com regexp:dl[0-9]+\.qq\.com$ keyword:google /data/mylist.txt
# Hosts模块,可以自定义域名的解析直接映射为指定变量的结果。域名写法与Zones模块一样,支持引入外部文件。  
# Hosts模块将位于最高匹配优先级。

Tips :

  • env_key配合custom_env.ini使用可以实现变量改变的时候重新加载。
  • custom_mod功能引入的外部文件仅在容器启动的时候加载,如果不存在会跳过规则。custom_mod引入的外部文件不会被额外监测,发生变化的时候不会重新加载。如果需要重新加载所有外部文件,可以使用reload.sh命令,示例:docker exec paopaodns reload.sh

工作逻辑细节

阅读提示
该工作逻辑为旧版本镜像编写,其中的内容可能过时,仅供大致参考,请以Github文档参数说明为准

如果你仅使用递归Unbound DNS的功能(CNAUTO=no),那么工作逻辑比较简单:

  • 容器启动初始化,根据内存和CPU性能生成配置文件
  • 启动unbound作为递归DNS服务器
  • 使用redis作为缓存,缓存数据位于/data/redis_dns_v2.rdb
  • 根据设置的UPDATE更新频率更新根域名服务器文件(事实上,这个文件不需要频繁更新,因为根服务器往往只会在初始查询的时候用到,根服务器文件的更新频率也很低,并且更新脚本会自动验证存在正确MD5且不一致才会更新)

在开启了智能分流的情况下(CNAUTO=yes),工作逻辑稍微复杂一些:

  • 容器初始化在生成配置的同时将同时生成初始GEOIP数据和dnscrypt服务器信息(如果不存在的话),以及相关其他程序的配置模板文件。
  • 启动dnscrypt服务,dnscrypt是一个解决DNS污染的程序,该服务将会自动拉取可用的dnscrypt服务器信息并自动测试延迟和可用性、负载均衡和故障转移。如果你配置了SOCKS5变量,将会额外再启动一个使用了SOCKS5代理的dnscrypt实例,这里把它称为dnscrypt-socks。一般来说,即使不配置SOCKS5也能正常使用,但如果你的代理跟目标线路一致,这有可能帮助你获取更理想的解析结果。
  • 启动两个unbound实例,第一个unbound实例为查询递归DNS用途,这里把它称为unbound-local。第二个unbound实例为缓存dnscrypt用途,这里把它称为unbound-dnscrypt,它的上游是dnscrypt,如果配置了SOCKS5变量,它的上游将会优先dnscrypt-socks。redis缓存将会同时接受这两个unbound实例的请求。
  • 启动mosdns进行分流处理(不缓存任何请求,缓存由redis完成),处理过程如下:
    • 拒绝qtype65请求(避免apple设备类型被动使用https DNS)
    • 读取force_nocn_list.txt的域名列表,直接转发到unbound-dnscrypt
    • 读取force_cn_list.txt的域名列表,直接转发到unbound-local
    • 先尝试请求unbound-local,如果返回的结果IP位于China大陆,则接受并返回到客户端。
    • 如果返回的结果不位于China大陆,则把结果抛弃并转发到unbound-dnscrypt
    • 如果请求的域名使用了unbound-dnscrypt,并且同时具备IPv4和IPv6的解析,那么将会仅返回IPv4记录,过滤IPv6记录,如果仅有IPv6记录,将不会过滤,这个流程是考虑双栈用户的分流问题,因为应用端会优先使用IPv6地址而导致你的静态路由失效,但又不放弃那些仅有IPv6的网站。

进阶自定义示例

  1. 在企业内可能需要的一个功能,就是需要和AD域整合,转发指定域名到AD域服务器的方法: 打开/data/custom_mod.yaml编辑:
1
2
3
4
5
#Active Directory Forward Example
# 在这个示例中,你公司的AD域名为company.local,有几个AD域DNS服务器。
Zones:
 - zone: company.local
   dns: 10.111.222.11,10.111.222.12,10.111.222.13
  1. 添加除了A/AAAA记录以外类型的本地记录解析,可以通过编辑unbound_custom.conf实现,具体语法可以参考unbound官方文档,例如添加微软KMS服务器SRV记录 打开/data/unbound_custom.conf编辑:
1
2
3
4
5
6
7
#Example of setting up SRV records for KMS server VLMCS.
#假设你的内网后缀是.lan,KMS服务器地址是192.168.1.2或者kms.ad.local

server:
    local-zone: "_vlmcs._tcp.lan." static
    local-data: "_vlmcs._tcp.lan. IN SRV 0 0 1688 kms.ad.local."
    local-data: "_vlmcs._tcp.lan. IN SRV 0 0 1688 192.168.1.2."

如果有其他高级的自定义需求,欢迎在discussions里面参与讨论。

附赠:PaoPao-Pref

这是一个让DNS服务器预读取缓存或者压力测试的简单工具,配合PaoPaoDNS使用可以快速生成redis_dns_v2.rdb缓存。从指定的文本读取域名列表并调用nslookup命令查询记录,docker镜像默认自带了全球前100万热门域名。
详情:https://github.com/kkkgo/PaoPao-Pref

相关项目:PaoPaoGateWay

PaoPao GateWay是一个体积小巧、稳定强大的FakeIP网关,支持Full Cone NAT ,支持多种方式下发配置,支持多种出站方式,包括自定义socks5、自定义yaml节点、订阅模式和自由出站,支持节点测速自动选择、节点排除等功能,并附带web面板可供查看日志连接信息等。PaoPao GateWay配合PaoPaoDNS的CUSTOM_FORWARD功能就可以完成简单精巧的分流。
详情:https://github.com/kkkgo/PaoPaoGateWay

附录:使用到的程序

unbound:

redis: https://hub.docker.com/_/redis
dnscrypt:

mosdns:

GEOIP: