利用 Mosdns 和 AdGuardHome 搭建自己的 DNS
date
Aug 17, 2022
slug
use-mosdns-and-adguardhome-to-build-your-own-dns
status
Published
summary
国内的 DNS 服务商一般有两个,网络运营商和部分商业公司。而由于种种原因,这些 DNS 都存在或多或少的问题,这里就不细说了。而部分玩家为了尽可能地规避其中的某些问题,因此必不可少地需要自建 DNS 服务。
tags
DNS
type
Post
前言
国内的 DNS 服务商一般有两个,网络运营商和部分商业公司。而由于种种原因,这些 DNS 都存在或多或少的问题,这里就不细说了。而部分玩家为了尽可能地规避其中的某些问题,因此必不可少地需要自建 DNS 服务。但是,需要注意得是,我们国家的互联网域名管理办法明文规定:
提供域名解析服务,应当遵守有关法律、法规、标准,具备相应的技术、服务和网络与信息安全保障能力,落实网络与信息安全保障措施,依法记录并留存域名解析日志、维护日志和变更记录,保障解析服务质量和解析系统安全。涉及经营电信业务的,应当依法取得电信业务经营许可。
因此,接下来搭建好的的 DNS 服务只能在自己的内网运行,用于学习、测试等个人用途上,不得对外提供服务。
工具选型
目前用得比较多,而且还在活跃的 DNS 工具总共有三种:
它们都是开源产品,能够做到防污染、防跟踪、防篡改、去广告、反跟踪等等功效。但是彼此之间的实现区别还是很多的,而我在仔细对比之后还是选择了后两款。至于原因这里不方便细说,比较复杂,但是如果说对于 SmartDNS 有特殊感情也可以自行搜索相关的教程。
服务搭建
首先介绍下思路,我们用 Mosdns 作为最核心的部分 → DNS 转发器,AdguardHome 作为可视化的前端和广告规则自动拦截和屏蔽工具。
当然,有人可能会问只用 AdGuardHome 就够了,为什么还要用 Mosdns?
至于为什么不单用 AdguardHome,我这里主要出于两方面的考虑:
- 经过测试 Mosdns 的 DNS 查询转发速度相对更快,且查询条件能做到更加精细地控制,这里可以参考规则部分
- Mosdns 的域名匹配工具可以直接利用一些常用的域名文件做到更加方便和精准地分流;Adguardhome 不是不行,但是很麻烦
这里需要你有一台测试 Linux 服务器(不用多好,能正常访问就行),接下来使用该命令一键安装 Docker。如果你不知道什么是 Docker,请自行搜索了解。
curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
Mosdns
mosdns v5:2022年 12 月 21 日发布了 v5.0.0-rc.0,配置文件和 v4 有大量变化,需要参考 wiki 重新配置。因此我下面的内容也分为 v4 版本和 v5 版本以区分。
首先你需要准备好配置文件相关资源文件和
config.yaml
,下面是相关链接可以复制或下载使用- 资源文件
v4 版本:
wget https://glare.xukecheng.tech/Loyalsoldier/geoip/cn.dat -O /etc/mosdns/cn.dat && \ wget https://glare.xukecheng.tech/Loyalsoldier/v2ray-rules-dat/geosite.dat -O /etc/mosdns/geosite.dat && \ wget https://glare.xukecheng.tech/Loyalsoldier/v2ray-rules-dat/geoip.dat -O /etc/mosdns/geoip.dat && \ touch /etc/mosdns/ecs_cn_domain.txt && \ touch /etc/mosdns/ecs_tw_domain.txt # ecs_cn_domain 是强制本地解析域名,ecs_tw_domain 是强制非本地解析域名;格式可以参考 https://github.com/pmkol/easymosdns/blob/main/ecs_tw_domain.txt
v5 版本:
curl https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/direct-list.txt > /etc/mosdns/direct-list.txt && \ curl https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/apple-cn.txt > /etc/mosdns/apple-cn.txt && \ curl https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/google-cn.txt > /etc/mosdns/google-cn.txt && \ curl https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/proxy-list.txt > /etc/mosdns/proxy-list.txt && \ curl https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/gfw.txt > /etc/mosdns/gfw.txt && \ curl https://raw.githubusercontent.com/Hackl0us/GeoIP2-CN/release/CN-ip-cidr.txt > /etc/mosdns/CN-ip-cidr.txt && \ touch /etc/mosdns/force-nocn.txt && \ touch /etc/mosdns/force-cn.txt # force-cn 是强制本地解析域名,force-nocn 是强制非本地解析域名
- 配置文件 →
/etc/mosdns/config.yaml
v4 版本
# 配置文件的部分内容参考了下面这个网站 # https://apad.pro/easymosdns # log: file: "./mosdns.log" level: error data_providers: - tag: geosite file: ./geosite.dat auto_reload: true - tag: geoip file: ./geoip.dat auto_reload: true - tag: cn file: ./cn.dat auto_reload: true - tag: ecscn file: ./ecs_cn_domain.txt auto_reload: true - tag: ecstw file: ./ecs_tw_domain.txt auto_reload: true - tag: hosts file: ./hosts.txt auto_reload: true plugins: # 缓存的插件 - tag: cache type: cache args: size: 10240 # 机器上部署了 redis 再开启 # redis: "redis://127.0.0.1:6379/0" lazy_cache_ttl: 86400 cache_everything: true # Hosts的插件 - tag: hosts type: hosts args: hosts: - "provider:hosts" # 调整ECS的插件 # [auto|global|cn|tw] - tag: ecs_auto type: ecs args: auto: true mask4: 24 force_overwrite: false - tag: ecs_global type: ecs args: auto: true mask4: 22 force_overwrite: false - tag: ecs_cn type: ecs args: auto: false ipv4: "1.2.4.0" ipv6: "2001:dc7:1000::1" mask4: 24 force_overwrite: true - tag: ecs_tw type: ecs args: auto: false ipv4: "168.95.1.0" ipv6: "2001:b000:168::1" mask4: 22 force_overwrite: true # 调整TTL的插件 - tag: ttl_short type: ttl args: minimal_ttl: 60 maximum_ttl: 3600 - tag: ttl_long type: ttl args: minimal_ttl: 300 maximum_ttl: 3600 # 屏蔽TYPE65类型请求的插件 - tag: qtype65 type: query_matcher args: qtype: [65] - tag: black_hole type: blackhole args: rcode: 0 ipv4: "127.0.0.1" ipv6: "::1" # 转发至 AliDNS 的插件 - tag: forward_ali type: fast_forward args: upstream: - addr: "https://dns.alidns.com/dns-query" dial_addr: "223.5.5.5" trusted: true enable_pipeline: true - addr: "tls://dns.alidns.com" dial_addr: "223.5.5.5" trusted: true enable_pipeline: true # 转发至 DnspodDNS 的插件 - tag: forward_dnspod type: fast_forward args: upstream: - addr: "https://1.12.12.12/dns-query" trusted: true - addr: "https://120.53.53.53/dns-query" trusted: true # 转发至本地服务器的插件 - tag: forward_local type: sequence args: exec: - parallel: - - "forward_ali" # 执行序列 #1。 - - "forward_dnspod" # 执行序列 #2。 # 转发至 GoogleDNS 的插件 - tag: forward_google type: fast_forward args: upstream: - addr: "https://dns.google/dns-query" dial_addr: "2001:4860:4860::8844" trusted: true enable_pipeline: true enable_http3: true - addr: "tls://dns.google" dial_addr: "8.8.8.8" trusted: true enable_pipeline: true enable_http3: true # 转发至 CloudflareDNS 的插件 - tag: forward_cloudflare type: fast_forward args: upstream: - addr: "https://cloudflare-dns.com/dns-query" dial_addr: "2606:4700::6810:f9f9" trusted: true enable_pipeline: true enable_http3: true - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" dial_addr: "1.1.1.1" trusted: true enable_pipeline: true enable_http3: true # 转发至远程服务器的插件 - tag: forward_remote type: sequence args: exec: - parallel: # 并行 - - "forward_google" # 执行序列 #1。 - - "forward_cloudflare" # 执行序列 #2。 # 匹配本地域名的插件 - tag: query_is_local_domain type: query_matcher args: domain: - "provider:geosite:cn" - "provider:geosite:apple-cn" - "provider:geosite:steam@cn" # 匹配污染域名的插件 - tag: query_is_non_local_domain type: query_matcher args: domain: - "provider:geosite:geolocation-!cn" - tag: response_has_local_ip type: response_matcher args: ip: # 使用默认geoip.dat文件 # - "ext:./geoip.dat:cn" # 使用高性能cn.dat文件, 需要下载对应的文件 - "provider:cn:cn" # - tag: query_is_ad_domain # type: query_matcher # args: # domain: # - "ext:./geosite.dat:category-ads-all" # 匹配强制本地解析域名的插件 - tag: query_is_cn_domain type: query_matcher args: domain: - "provider:ecscn" # 匹配强制非本地解析域名的插件 - tag: query_is_tw_domain type: query_matcher args: domain: - "provider:ecstw" # 匹配RCODE2的插件 - tag: response_server_failed type: response_matcher args: rcode: [2] # 主要的运行逻辑插件 # sequence 插件中调用的插件 tag 必须在 sequence 前定义 # 否则 sequence 找不到对应插件 - tag: main_sequence type: sequence args: exec: # 域名映射IP - hosts # 缓存 - cache # 屏蔽TYPE65类型请求 - if: qtype65 exec: - black_hole - _return # 强制用本地服务器解析 - if: query_is_cn_domain exec: - ecs_auto - forward_local - _return # 强制用非本地服务器解析 - if: query_is_tw_domain exec: - ecs_tw - forward_remote - ttl_long - _return # 屏蔽广告域名 # - if: query_is_ad_domain # exec: # - _new_nxdomain_response # - _return # 已知的本地域名或CDN域名用本地服务器解析 - if: query_is_local_domain exec: - ecs_auto - forward_local - _return # 已知的污染域名用远程服务器解析 - if: query_is_non_local_domain exec: - _prefer_ipv4 - ecs_cn - forward_remote - _return # 剩下的未知域名用IP分流,分流原理请参考fallback的工作流程 # primary 从本地服务器获取应答,丢弃非本地IP或污染IP的结果 - primary: - ecs_auto - forward_local - ttl_short - if: "(! response_has_local_ip) && [_response_valid_answer]" exec: - _drop_response # secondary 从远程服务器获取应答,无法解析的域名从本地服务器获取应答 secondary: - _prefer_ipv4 - ecs_global - forward_remote - ttl_long - if: "response_server_failed" exec: - forward_local - ttl_long # 这里建议设置成 local 服务器正常延时的 2~5 倍 # 这个延时保证了 local 延时偶尔变高时,其结果不会被 remote 抢答 # 如果 local 超过这个延时还没响应,可以假设 local 出现了问题 # 这时用就采用 remote 的应答。单位: 毫秒 fast_fallback: 150 servers: - exec: main_sequence timeout: 5 listeners: - protocol: udp addr: "0.0.0.0:53" - protocol: tcp addr: "0.0.0.0:53" # - protocol: http # addr: "0.0.0.0:9443" # url_path: "/dns-query" # get_user_ip_from_header: "X-Forwarded-For" # - protocol: tls # addr: "0.0.0.0:853" # cert: "/etc/mosdns/cert/test.crt" # TLS 所需证书文件。 # key: "/etc/mosdns/cert/test.key" # TLS 所需密钥文件。 # idle_timeout: 10 # 连接复用空连接超时时间。单位: 秒。默认: 10。 api: http: "0.0.0.0:9080"
v5 版本
log: level: debug production: true # API 入口设置 api: http: "0.0.0.0:9080" # 在该地址启动 api 接口。 # 从其他配置文件载入 plugins 插件设置。 # include 的插件会比本配置文件中的插件先初始化。 include: [] plugins: - tag: "geosite-cn" type: domain_set args: files: - "./direct-list.txt" - "./apple-cn.txt" - "./google-cn.txt" - tag: "geosite-nocn" type: domain_set args: files: - "./proxy-list.txt" - "./gfw.txt" - tag: "geoip-cn" type: ip_set args: files: "./CN-ip-cidr.txt" - tag: "force-cn" type: domain_set args: files: "./force-cn.txt" - tag: "force-nocn" type: domain_set args: files: "./force-nocn.txt" - tag: "hosts" type: hosts args: files: "./hosts.txt" - tag: "cache" type: "cache" args: size: 1024 lazy_cache_ttl: 0 dump_file: ./cache.dump dump_interval: 600 # 转发至本地服务器的插件 - tag: forward_local type: forward args: concurrent: 3 upstreams: - addr: "https://dns.alidns.com/dns-query" - addr: "tls://dns.alidns.com" - addr: "https://1.12.12.12/dns-query" - addr: "https://120.53.53.53/dns-query" # 转发至远程服务器的插件 - tag: forward_remote type: forward args: concurrent: 3 upstreams: - addr: "https://cloudflare-dns.com/dns-query" - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - addr: "https://dns.google/dns-query" - addr: "tls://dns.google" - tag: "primary_forward" type: sequence args: - exec: $forward_local - exec: ttl 60-3600 - matches: - "!resp_ip $geoip-cn" - "has_resp" exec: drop_resp - tag: "secondary_forward" type: sequence args: - exec: prefer_ipv4 - exec: $forward_remote - matches: - rcode 2 exec: $forward_local - exec: ttl 300-3600 - tag: "final_forward" type: fallback args: primary: primary_forward secondary: secondary_forward threshold: 150 always_standby: true - tag: main_sequence type: sequence args: - exec: $hosts - exec: query_summary hosts - matches: has_wanted_ans exec: accept - exec: $cache - exec: query_summary cache - matches: has_wanted_ans exec: accept - exec: query_summary qtype65 - matches: - qtype 65 # exec: black_hole 127.0.0.1 ::1 0.0.0.0 exec: reject 0 - matches: - qname $geosite-cn exec: $forward_local - exec: query_summary geosite-cn - matches: has_wanted_ans exec: accept - matches: - qname $force-cn exec: $forward_local - exec: query_summary force-cn - matches: has_wanted_ans exec: accept - matches: - qname $geosite-nocn exec: $forward_remote - exec: query_summary geosite-nocn - matches: has_wanted_ans exec: accept - matches: - qname $force-nocn exec: $forward_remote - exec: query_summary force-nocn - matches: has_wanted_ans exec: accept - exec: $final_forward - tag: "udp_server" type: "udp_server" args: entry: main_sequence listen: 0.0.0.0:53 - tag: "tcp_server" type: "tcp_server" args: entry: main_sequence listen: 0.0.0.0:53
上述工作都完成后就可以使用下述的安装命令了:
v4 版本: docker run -d\ --name mosdns --net=host \ -v /etc/mosdns:/etc/mosdns \ irinesistiana/mosdns:v4.5.3 v5 版本: docker run -d\ --name mosdns --net=host \ -v /etc/mosdns:/etc/mosdns \ irinesistiana/mosdns:latest
至此,Mosdns 已经搭建完成,理论上从这一刻开始,你就可以直接使用 DNS 服务了。但是,这里存在很多问题:
- 没有可视化的、傻瓜式的界面
- 想要自动更新广告屏蔽列表该怎么办
因此接下来我们就需要利用 AdGuardHome 来解决这一问题。
AdGuardHome
下面是 AdGuardHome 的 Docker 安装命令。
docker run -d --name adguardhome\ --restart unless-stopped\ -v /my/own/workdir:/opt/adguardhome/work\ -v /my/own/confdir:/opt/adguardhome/conf\ --net=host -d adguard/adguardhome # 详细参数请参考 https://hub.docker.com/r/adguard/adguardhome
安装完成后只需要使用浏览器访问 IP + 3000 端口即可进入服务初始化界面。在部署端口时需要注意一下:
- 网页管理界面端口建议不使用默认 80,方便你使用 Nginx 或 Caddy 反代
- DNS 服务端口也建议不要用 53,因为已经被 Mosdns 占用了
接下来设置完成用户名和密码登录,就来到了主界面。

接下来进入 DNS 设置页,将上游 DNS 服务器设置为 127.0.0.1,此时代表 AdGuardHome 将向 Mosdns 转发 DNS 查询。

同时建议在 DNS 服务配置启用 EDNS 客户端子网。

不建议开启缓存,缓存可以交给 Mosdns,这里 AdGuardHome 的作用就是转发、记录和去广告。

接下来还有两个地方需要设置:加密设置和设置 DNS 拦截列表。
加密设置在这里,主要能够支持 DoH 和 DoT。这里也要说明一下不建议直接使用 IP 形式的 DNS(使用我们自行搭建的服务时):

而 DNS 拦截列表就是可以去广告和自动更新规则的地方,规则请自行搜索:

PS:有个配置建议,可以打开 AdGuardHome.yaml 在 dns 下面增加 trusted_proxies,主要是能够获取到上游的真实请求地址,方便 ECS 的运行。
dns: trusted_proxies: - 0.0.0.0/0
更多关于 AdGuardHome 的教程可以参考:
如何访问
这里建议使用 Nginx 或者 Caddy 反代 AdGuardHome,本地使用 DoH 地址进行测试,我自己使用得是 Caddy,可以参考一下:
{ email you@email.com } https://you.domain { reverse_proxy https://172.17.0.1:<AdGuardHome 加密设置中的 HTTPS 端口> { transport http { tls_insecure_skip_verify } } }
最终生成的 DoH 地址就是 https://you.domain/dns-query,大家可以把这一地址填写到对应的工具中即可。
另外,关于搭建的服务器有一些建议:
- 到测试环境的延迟尽量低,否则容易出现在 DNS 解析阶段就浪费大量时间的情况
- 有一定的 SLA 保证
- 由于需要用到 HTTPS 来访问 DNS,所以如果你没有备案的域名可能无法在国内服务器上进行测试,建议可以选择回国线路相对较好的香港服务器
个人搭建并体验过的服务器厂商有
上面这两家的部分香港服务器线路都不错,建议可以试一下。另外,腾讯云的轻量服务器(https://cloud.tencent.com/)也可以考虑的。
PS:括号内的不带 AFF。
参考资料: