步骤/目录： 1.背景介绍 2.adguardhome （1）安装 （2）配置 3.pihole （1）安装 （2）配置 附录： （1）macvlan （2）macvlan下的网段 （3）macvlan的其它操作 （4）pihole配置详解本文首发于个人博客https://lisper517.top/index.php/archives/476/，转载请注明出处。本文的目的是介绍自建DNS、过滤广告的两款软件。本文写作日期为2024年1月29日，在Ubuntu 22.04LTS系统演示。1.背景介绍在用电脑、手机平板浏览网页时，经常能看到网页上各种广告。这些广告最常见的形式是一些图片或视频，而图片和视频资源一般是通过网址加载的。如果在路由器中屏蔽这些广告网址，就能实现去广告。路由器默认设置下，会自动选择本地运营商的DNS，而运营商有时候会劫持DNS，情节轻时在返回的内容中加上广告，严重时则返回错误的内容。在这种情况下也有必要自建DNS。2.adguardhomeadguardhome最初应该是国人做的一款去广告DNS。（1）安装根据其 docker网址 的引导，在Ubuntu上如下操作：mkdir -p /docker/adguardhome/data/adguardhome mkdir -p /docker/adguardhome/conf/adguardhome cd /docker/adguardhome vim docker-compose.yml写入如下内容：version: '3.8' services: adguardhome: image: 'adguard/adguardhome' container_name: adguardhome ports: - '53:53' #DNS端口 - '3000:3000' #网页UI，初始配置端口 - '8500:80' #网页UI，后续配置端口 volumes: - ./data/adguardhome:/opt/adguardhome/work - ./conf/adguardhome:/opt/adguardhome/conf restart: unless-stopped以上设置仅将adguardhome作为DNS服务器使用，其实它还可用用来做DHCP服务器（动态分配内网ip）、HTTPS/DNS-over-HTTPS服务器（DNS的过程用HTTPS加密）、DNS-over-TLS服务器（用TLS加密DNS）、DNS-over-QUIC服务器（QUIC加密DNS）、DNSCrypt服务器（不太清楚，应该也是加密），详见原文，需要注意的是HTTPS、TLS、QUIC好像都需要公网ip+域名+HTTPS证书（所以需要在云服务器部署adguardhome），而DNSCrypt虽不需要证书、却需要客户端（电脑，手机平板等使用adguardhome进行DNS的设备）配置软件。另外需要注意，3000端口是adguardhome初始配置的端口，后续WebUI则会在80端口，已被占用的话可以改到其它端口（此处改到8500端口）。最后打开端口，运行即可（其实docker会自动打开，笔者用ufw是为了方便查看）：ufw allow 53 comment "DNS-adguardhome" ufw allow 3000 comment "WebUI-adguardhome-initial" ufw allow 8500 comment "WebUI-adguardhome" docker-compose config docker-compose up如果提示53端口被占用，可以把53改到其它端口，前提是你的路由器支持DNS服务器使用非53端口；或者使用笔者更推荐的macvlan，见附录。（2）配置访问 本地ip:3000 端口，设置登录用户名密码，其它一路默认，完成初始化配置；然后到8500端口进行进一步配置。在adguardhome配置前，或者配置完成后，到你的路由器管理页面，将ipv4的DNS改到adguardhome的ipv4地址。以下配置参考了 这篇文章 。a.DNS服务器在 设置 - DNS设置 - 上游 DNS 服务器 中，填入cloudflare和google的DNS：1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001 8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844后备 DNS 服务器 使用adguardhome提供的DNS：94.140.14.14 94.140.15.15 2a10:50c0::ad1:ff 2a10:50c0::ad2:ff笔者实验时用不了ipv6的DNS，这是因为docker没有给adguardhome容器分配ipv6地址，自然也用不了ipv6。开启的方法见 参考文章一 （讲解较乱）或者 参考文章二 （使用macvlan，见附录）。b.DNS 黑名单在 过滤器 - DNS 黑名单 里，添加DNS过滤规则。有人整理出来某些广告域名，添加到黑名单后adguard就会拦截这些请求。可以添加自带的，也可以自己找一些黑名单，比如 配置参考文章 中提供的：halflife https://raw.githubusercontent.com/o0HalfLife0o/list/master/ad.txt anti-AD https://anti-ad.net/easylist.txt neoHosts https://cdn.jsdelivr.net/gh/neoFelhz/neohosts@gh-pages/full/hosts.txt 大圣净化 - 针对国内视频网站 https://raw.githubusercontent.com/jdlingyu/ad-wars/master/hosts adgk手机去广告规则 https://raw.githubusercontent.com/banbendalao/ADgk/master/ADgk.txt 广告终结者 http://sub.adtchrome.com/adt-chinalist-easylist.txt Adbyby https://raw.githubusercontent.com/adbyby/xwhyc-rules/master/lazy.txt EasyList China+EasyList https://easylist-downloads.adblockplus.org/easylistchina+easylist.txt EasyPrivacy https://easylist-downloads.adblockplus.org/easyprivacy.txt不用太多，只添加一两个即可，太多了容易误杀，也拖慢DNS速度。c.DNS 白名单为了防止错杀，还可以添加白名单，白名单的优先级高于黑名单。比如google analytics就容易被错杀。anti-ad白名单 https://raw.githubusercontent.com/privacy-protection-tools/dead-horse/master/anti-ad-white-list.txt filter_whitelist https://raw.githubusercontent.com/hl2guide/Filterlist-for-AdGuard/master/filter_whitelist.txt LWJ's white list https://raw.githubusercontent.com/liwenjie119/adg-rules/master/white.txt DNS允许白名单 https://raw.githubusercontent.com/ChengJi-e/AFDNS/master/QD.txt白名单也不需要太多。如果正常的网页加载不出来，到adguardhome的后台查看是否误杀，手动加白名单。d.DNS 重写如果想通过域名访问一些局域网ip，可以在这里配置。一些路由器如AX9000也可以在路由器里配置（小米路由器需要在同名app配置，找到hosts设置）。e.自定义过滤规则一些网站的广告需要其它设置才能去除，比如油管的视频广告（笔者未验证）：/googleads.$~script,domain=~googleads.github.io /pagead/lvz? ||google.com/pagead/ ||static.doubleclick.net^$domain=youtube.com ||youtube.com/get_midroll_ ||safebrowsing.googleapis-cn.com ||doubleclick.net自定义规则需要有一定的网络基础才能玩的转，至少要会用chrome的开发者模式，或者也可以搜索别人的规则试试。3.piholepihole最初是给树莓派做的广告拦截，但是也能用于其它电脑。pihole没有中文界面，需要的可自行找汉化版，但是笔者还是更推荐英文原版。（1）安装根据 docker-pihole 的介绍，在 Ubuntu 22.04 LTS 上操作（同样使用macvlan的方法）。创建docker-net（之前创建过的话这里就不用建了）：docker network create -d macvlan \ --subnet=192.168.31.0/24 \ --gateway=192.168.31.1 \ --ipv6 \ --subnet=fe80:7eb5:2afd::/64 \ --gateway=fe80:7eb5:2afd::1 \ -o parent=eth0 \ macvlan_net然后是熟悉的步骤：mkdir -p /docker/pihole/etc-pihole mkdir -p /docker/pihole/etc-dnsmasq.d cd /docker/pihole vim docker-compose.yml写入如下内容：version: "3" services: pihole: container_name: pihole image: pihole/pihole:latest environment: TZ: 'Asia/Shanghai' WEBPASSWORD: '123456' #网页密码 volumes: - './etc-pihole:/etc/pihole' - './etc-dnsmasq.d:/etc/dnsmasq.d' restart: unless-stopped networks: macvlan_net: ipv4_address: 192.168.31.31 ipv6_address: fe80:7eb5:2afd::31 networks: macvlan_net: external: true运行：docker-compose config docker-compose up然后到 [fe80:7eb5:2afd::31]/admin 或者 192.168.31.31/admin 登录，密码为yml中设置的密码。后面如果把pihole作为DNS，则也能从 http://pi.hole/admin 登录。如果忘记或者想更改登录密码，可以 docker exec -it pihole pihole -a -p （或者进入宿主机后 pihole -a -p ）。（2）配置pihole没有中文界面，配置起来稍微难一点。pihole配置中的高级选项会在附录中解释。另外，目前的pihole管理白名单不是很方便，大概是21年某次更新之前的网页UI还有黑白名单的设计，现在则是用groups代替了。在 Adlists 里，可以添加别人的屏蔽列表。添加完后， docker exec -it pihole pihole -g 来更新列表；或者到 Tools - Update Gravity 里更新。黑名单在这里添加，白名单见后；在 Settings - DNS 里，把Google、OpenDNS、Cloudflare的IPv4、IPv6的DNS服务器都勾选上。旁边也可以自己填；下方有一个 Rate-limiting ，默认应该是某个客户端在60s内请求1000次DNS时就封禁该客户端。玩多线程爬虫的话会有影响；或者用路由器作为pihole的客户端也容易被封。都设置为0可关闭该功能。其它配置见附录。附录：（1）macvlanmacvlan是一种网络虚拟化技术，可以用同一张物理网卡获得不同的局域网ip。在docker中可以调用macvlan，将容器作为一个单独设备、分配局域网ip，使得局域网内的机器（除了宿主机）几乎都能通过局域网ip来访问容器。比如将adguardhome绑定到 192.168.1.30 ，那么 192.168.1.30:80 、 192.168.1.30:3000 就分别变成了adguardhome的配置页面、初次配置页面。由于还要使用ipv6访问adguardhome，也需要给它一个v6地址。回到adguardhome容器启动前，如下设置（参考之前提到的 参考文章二 和之前未提到的 参考文章三 ，后者讲的比较详细）：docker network create -d macvlan \ --subnet=192.168.1.0/24 \ --gateway=192.168.1.1 \ --ipv6 \ --subnet=fe80:7eb5:2afd::/64 \ --gateway=fe80:7eb5:2afd::1 \ -o parent=eth0 \ macvlan_net-d macvlan 即 --driver macvlan ，指定docker-net的驱动。最后的 macvlan_net 是docker-net名；ipv4网关为 192.168.1.1 ，网段为 192.168.1.0/24 。由于笔者使用小米路由器，网关和网段都要改到 192.168.31 下；ipv6网关为 fe80:7eb5:2afd::1 ， 网段为 fe80:7eb5:2afd::/64 。这个 fe80:7eb5:2afd 局域网v6地址可以按 fe80:xxxx:xxxx 的格式随便写；指定物理网卡为 eth0 对应的网卡。如果网卡不存在，在linux上用 ifconfig 查看物理网卡，局域网ip对应的那张，类似 enp2s0f1 、 en0 、 ovs_eth0 这种。另外需要注意，macvlan是独立于docker的，docker需要调用macvlan进行网络虚拟化，所以你需要先安装macvlan包。一般linux发行版是自带的，少数情况则需要手动下载，比如用 树莓派4B + Ubuntu21.10系统 时需要 apt install linux-modules-extra-raspi + reboot （见 reddit提问 ）；或者有些openwrt系统下，需要 opkg update && opkg install kmod-macvlan 。创建完docker-net后，将yml文件更改为：version: "3" services: adguardhome: image: adguard/adguardhome container_name: adguardhome volumes: - ./data/adguardhome:/opt/adguardhome/work - ./conf/adguardhome:/opt/adguardhome/conf restart: unless-stopped networks: macvlan_net: ipv4_address: 192.168.1.30 #用小米路由器则改成192.168.31.30 ipv6_address: fe80:7eb5:2afd::30 networks: macvlan_net: external: true这里没有端口映射的设置。通过以上步骤，已经给容器组中的adguardhome容器分配了v4、v6地址，访问 192.168.1.30 或者 [fe80:7eb5:2afd::30] 的3000、80端口都能设置adguardhome，也可以正常访问ipv6的DNS。设置完adguardhome后，在路由器管理页把v4和v6的DNS分别改到 192.168.1.30 和 [fe80:7eb5:2afd::30] 即可。必须尝试通过 [fe80:7eb5:2afd::30]:3000 或者 [fe80:7eb5:2afd::30]:80 访问adguardhome。如果不行，可以看下面的方法。（2）macvlan下的网段无法从路由器或者局域网的其它机器内访问 [fe80:7eb5:2afd::30] ，可能是因为网段不对。在windows的cmd中 ipconfig 看 默认网关 ，或者linux下 ip -6 route show 最后一行 default via ... 确认一下ipv6的网关。如果网关以其它开头，比如不是 fe80 而是 f123 ，那么就把上文教程中所有的 fe80 换成 f123 即可。当然，一般网关都是 fe80 开头的，因为 fe80 默认是内网v6，类似于 192.168 在v4中的地位。如果不能通过以上方法确认v6网关，你也可以进入容器：docker exec -it adguardhome sh ifconfig其部分输出内容为：eth0 Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX inet addr:192.168.1.30 Bcast:192.168.1.255 Mask:255.255.255.0 inet6 addr: fe80::xx:xxxx:xxxx:xxxx/64 Scope:Link inet6 addr: fe80:7eb5:2afd::30/64 Scope:Global inet6 addr: 240e:xxx:xxx:xx:xx:xxxx:xxxx:xxxx/64 Scope:Global ...这里出现了3个v6地址，它们的顺序可能调换。第一个 fe80 开头的是随机分配的局域网v6，即使无法通过 fe80:7eb5:2afd::30 访问adguardhome，也一定能通过这个v6访问；第二个 fe80:7eb5:2afd::30 是yml中设置的，是静态公网v6（实际上是内网）；第三个 240e 开头的，是真正的公网v6地址，但是ping不到（应该是出于安全考虑）。如果你的头两个v6地址开头不同，比如说： inet6 addr: f123::xx:xxxx:xxxx:xxxx/64 Scope:Link inet6 addr: fe80:7eb5:2afd::30/64 Scope:Global同样把所有的 fe80 换成 f123 即可。在几乎所有其它的docker-macvlan教程中，创建的网段都非 fe80 开头，比如 fd0d:7eb5:2afd 。此时容器虽然能访问外部的v6地址，但v6网段不同，导致局域网的其它机器只能访问到容器的v4地址，却访问不到容器的v6地址。（3）macvlan的其它操作使用macvlan的容器，无法直接 docker restart ，会提示ip地址已被占用。你可以重启docker service docker restart 或者重启宿主机，以释放容器的ipv4、ipv6地址。192.168.1.0/24 下虽然有256个ip地址，但也有可能用完。好在macvlan还能对物理网卡进一步分层，见 参考文章四 。分层后需要注意不同macvlan网络之间的通信问题。最后，在参考文章四作者的下一篇文章中提到了overlay，也可以达到给容器以局域网ip的效果，见 参考文章五 。（4）pihole配置详解这里都是笔者个人的理解，部分参考了 pihole的官方文档 。由于文档都是英文的，笔者没时间细看，难免可能有疏漏错误，请参考原文。a.Groups与Clients从主页面左侧边栏的 Groups 可进入Groups management（屏蔽规则的分组）。根据 官方文档 ，组可用于管理域名黑名单、白名单、客户端。但是注意目前版本pihole的白名单域名管理起来不是很方便（网页中只能一个个显示，没有白名单列表）。可以对屏蔽规则分组，也可以对客户端分组（侧边栏的 Groups 下方的 Clients ）。比如 电脑1 需要访问所有网站， 电脑2 则需要屏蔽掉不健康的网站，就可以对此类网站拉一个组，在 电脑2（客户端2） 的屏蔽规则中单独加上该组，守护少年儿童的心理健康。每个新的屏蔽规则和客户端都会加到各自的0号组（default）中。要对客户端分组，首先需要有不同的客户端，这就要求不能在路由器里统一把DNS设置到pihole，但是给每台电子设备分别改DNS又太麻烦了。笔者建议成人用一个路由器，少年儿童用另一个路由器，这样简单一些。创建组名时如果有空格，则需要用英文双引号将组名括起来。组的操作：对某个客户端取消所有屏蔽： 在 Clients - Group assignment 里，把该客户端适用的所有组移除。 对屏蔽规则（黑名单）分组： 在 Groups 里创建屏蔽规则组后，在 Adlists - Group assignment 里，对屏蔽规则分组。新增的所有规则都会加到0组（default），可以手动调整。 添加单独的域名（黑名单或白名单）到组： 在 Domains 里新增一个域名（或者正则）规则，添加到屏蔽规则的组里。目前的pihole不能添加白名单的列表，只能在这里一个个添加，因此笔者建议这里只添加白名单域名。另外，pihole中白名单的优先级也高于黑名单（ 官方文档 ）。对客户端和屏蔽规则都分组后，就能很方便地管理。比如刚才的例子，有域名黑名单A，域名黑名单B（成人能访问，儿童不能访问），域名白名单C，成人客户端1，儿童客户端2，那么进行以下操作：在 Groups 里，添加 adult_rule 和 kid_rule 两个屏蔽规则组； 在 Clients 里，添加两个客户端，分别为成人和儿童使用的客户端，分别使用 adult_rule 和 kid_rule 两个屏蔽规则组（去掉default组）； 在 Domains 里，一个个添加白名单C中的域名，注意选择 Add to Whitelist，并把所有白名单域名的 Group assignment 中勾选两个屏蔽规则组（default组随便）； 在 Adlist 里，通过URL，添加黑名单A、B，将A添加到 adult_rule 和 kid_rule ，B则只添加到 kid_rule （default组随便）；b.Domains与Adlists在 Domains 里可以手动单独添加域名到白名单或黑名单，还能用正则表达式。正式用RE之前最好用pihole检查一下，见 官方文档 （需要进入容器操作）。在 Adlists 里可以用别人总结好的屏蔽列表（anti-ad之类的），用法同adguardhome。添加完屏蔽表后， docker exec -it pihole pihole -g 来更新列表；或者到 Tools - Update Gravity 里更新。c.Disable Blocking可以临时或者永久取消所有的屏蔽，用于测试。d.Local DNS自定义解析，一般用于把某个域名绑定到局域网ip上。e.名词解释笔者对于 Settings 里的一些高级功能进行解释，可能会有错误的地方。ECS (Extended Client Subnet) ：应该是DNS的弹性负载。Content Delivery Networks (CDNs) ：基于ip提供地理信息。Never forward non-FQDN A and AAAA queries ：如果不勾选的话，有时候Local DNS可能往上游服务器查找；如果开启下面的 Conditional Forwarding 又不勾选本功能，有时会导致DNS死循环。Never forward reverse lookups for private IP ranges ：本地ip如果没有对应的本地域名，不会向上游服务器查找。这两个 Never forward 选项也许能增加隐私性，但勾选时最好同时把pihole设为DHCP服务器。Use DNSSEC ：即Domain Name System Security Extensions，验证DNS解析是否正确。如果验证失败或者上游服务器不支持DNSSEC，可能会出问题。另外该选项会显著增加log文件大小。Rate-limiting ：默认情况下，一个客户端在60s内发出1000次DNS请求后就封禁该客户端。玩多线程爬虫可能有影响，把两个值都改成0可以关闭此功能。Conditional forwarding ：如果不把pihole作为DHCP服务器，则pihole无法决定局域网内的设备名（注意这个不同于Local DNS），开启这个功能后，填入局域网段、DHCP服务器（一般是路由器）、选填本地域名，pihole会向你的路由器查询局域网内的设备名。选填的本地域名，意思是对该域名的DNS不会离开本地，并且这个本地域名要和路由器的设置一致。开启 Conditional forwarding 同时关闭 Never forward non-FQDN A and AAAA queries ，那么即使是本地域名，pihole也会交给上游的路由器处理。（所以要想用pihole解析本地域名，要么就都不开，要么就把 Conditional forwarding 和 Never forward non-FQDN A and AAAA queries 都打开）