5

搭建 Pi-Hole 为网上冲浪保驾护航

 2 years ago
source link: https://wzyboy.im/post/1372.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

曾经我觉得有 uBlock Origin 之类的浏览器插件,不需要在网关上做广告和追踪器的过滤。但随着手机使用量的增加,我逐渐意识到在网关上做集中化管理还是有好处的。正好 Raspberry Pi 4 已经闲置了两个月,那就来试试 Pi-Hole 吧。

本文介绍如何使用 Pi-Hole 过滤广告、追踪器和恶意软件的域名,并使用 DoH / DoT 对 DNS 请求加密。最后,Android 设备可以通过 Private DNS 功能,即使不在家,也可以享受 Pi-Hole 的好处。

一、Pi-Hole 简介

在使用浏览器网上冲浪的过程中,DNS 的作用是把域名翻译成 IP 地址,以供浏览器连接。网页上有各种广告和追踪器,这些东西往往是专营此业的第三方服务提供的(如 Google AdSenseGoogle Analytics),与用户浏览的网站并不属于同一域名。那么,将这些域名在 DNS 层面上屏蔽掉,就达到了过滤广告和追踪器的效果。上了年纪的中国网民一定很熟悉十几年前流行的使用 hosts 文件屏蔽广告域名,或是绕过 GFW 的 DNS 污染的套路,其原理是类似的。只是随着个人电脑和手机等移动设备的增多,在多设备之间维护一份屏蔽列表是一件非常费劲(如果不是不可能)的事情。这种时候,在网关上部署一个集中化管理的屏蔽列表便是一种省力的方式。Pi-Hole 便是这样一套广告、追踪器与恶意软件域名过滤及管理的方案。

二、Pi-Hole 的安装与部署

正如其名字所暗示的,Pi-Hole 最早是为 Raspberry Pi 设计的。Pi-Hole 虽然较轻量,但是对于一般的家庭路由器来说还是太重了,因此跑在 Raspberry Pi 上是个很不错的选择。当然,Pi-Hole 的负载能力与硬件有关,如果你有诸如 Microserver Gen8 / Gen10 / Gen10+ 之类的家用服务器的话,Pi-Hole 在其上可以发挥更大的效能。

很遗憾地,Pi-Hole 没有提供通过包管理器的安装方式,而是通过「一键脚本」这种我个人很不喜欢的方式实现安装。官方推荐的方式是用 curl | bash 这种极富争议的代码执行方式:

curl -sSL https://install.pi-hole.net | bash

更谨慎的方式则是下载并检视其安装脚本,然后再安装:

wget -O basic-install.sh https://install.pi-hole.net
sudo bash basic-install.sh

安装过程中,脚本会通过 TUI 询问一些问题。安装完成后,屏幕上会打印随机生成的网页后台的密码。Pi-Hole 会为自己配置一个静态 IP 地址。本文中以 192.168.2.191 为例。

Pi-Hole 推荐用户立刻将其设置为局域网 DHCP 服务(通常由路由器提供)所分配的 DNS 地址,但你也许想要先配置一下 Pi-Hole,并确保其可用,再将其设置为局域网内的 DNS 地址。

三、重要的 Pi-Hole 配置

域名屏蔽方式

默认配置下,Pi-Hole 对被屏蔽的域名的 A 记录返回 0.0.0.0。这一行为在某些情况下并不是最优的。对于 Windows 来说,0.0.0.0 的确是一个不可达的地址,而对 GNU/Linux 和 macOS 来说,这个地址等同于 127.0.0.1。如果你使用 GNU/Linux 或 macOS,而本机又恰好运行了一个 HTTP 服务器监听了 80/tcp 和 443/tcp 端口,那么在你使用 Pi-Hole 之后,你本机的 HTTP 服务器会接到大量试图访问广告和追踪器域名的无效请求,并且你在浏览器里可能还会遇到莫名其妙的证书错误。比如当你访问 https://analytics.google.com/ 的时候,由于 analytics.google.com 这个域名默认被 Pi-Hole 屏蔽从而解析到 0.0.0.0,你的浏览器会拿到你本机 HTTP 服务器所用的 TLS 证书。

如果不希望使用返回 0.0.0.0 作为屏蔽方式,可以更改 /etc/pihole/pihole-FTL.conf 文件,改用别的屏蔽方式:

# 对被屏蔽域名返回 NXDOMAIN(该域名不存在)
BLOCKINGMODE=NXDOMAIN
# 对被屏蔽域名返回 NODATA(该域名存在,但是没有 A / AAAA 记录)
BLOCKINGMODE=NODATA

当然,如果你本机并没有运行监听 80/tcp 和 443/tcp 的服务,那么默认的屏蔽方式也是适合你的——浏览器只是会得到一个 connection refused 而已。

如果你没有在安装过程中关闭日志的话,那么 Pi-Hole 默认是会记录所有 DNS 请求的。这也是我推荐新晋 Pi-Hole 用户的设置,因为这有助于发现局域网内各种奇怪的请求。比如若不是 Pi-Hole,我是绝对不会想到 Velop 在 24 小时内请求 www.belkin.com 这个域名超过 2500 次的

待使用一段时间后,你可以调低 Pi-Hole 的日志数据库(SQLite)写入频次,以减少对 Raspberry Pi 的存储卡的损耗:

# 每小时写入一次,而不是默认的每分钟
DBINTERVAL=60

四、使用 dnscrypt-proxy 加密从 Pi-Hole 发出的请求

在互联网的田园时代,一切都是明文的。后来虽然有了 HTTPS,但是明文的 HTTP 依然是主流。从大约十年前开始,在 PRISM 的余波之下,以 2010 年 Gmail 默认以 HTTPS 加载为标志性事件,各大互联网公司开始推崇 HTTPS by Default 的理念。十年后的今天,绝大部分主流网站都已支持 HTTPS 并以此为默认,但大部分的用户仍在使用未加密的 DNS 协议。是时候改变了。

早在 2012 年,出于翻墙的原因,我就用 dnscrypt-proxy 来实现抗污染 DNS。当时的 DNSCrypt 协议后来被 DNS over HTTPS (DoH) 所取代,上游服务器也从 OpenDNS 换成了 Cloudflare。如今想要实现加密 DNS,我第一时间想到的仍然是它。

现在的 dnscrypt-proxy 已经改用 Golang 重写,这使得它能更容易地支持 x86_64 以外的平台。在 Raspberry Pi 上安装 dnscrypt-proxy 之后,可使用如下最小配置运行起来:

# /etc/dnscrypt-proxy/dnscrypt-proxy.toml

listen_addresses = ['0.0.0.0:44353']
server_names = ['cloudflare']
cache = false

[sources]
  [sources.'public-resolvers']
  url = 'https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md'
  cache_file = '/var/cache/dnscrypt-proxy/public-resolvers.md'
  minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
  refresh_delay = 72
  prefix = ''

注意:有些发行版(如 Raspbian / Debian)默认使用 .socket 方式激活 dnscrypt-proxy.service,你可能需要对此做一些处理才能让 dnscrypt-proxy 监听 53/udp 以外的端口:

$ sudo systemctl mask dnscrypt-proxy.socket
$ sudo systemctl disable dnscrypt-proxy-resolvconf.service
# 去掉对 .socket 的依赖
$ sudo systemctl edit --full dnscrypt-proxy.service

启动 dnscrypt-proxy 之后,可观察到类似这样的日志:

[NOTICE] Source [/var/cache/dnscrypt-proxy/public-resolvers.md] loaded
[NOTICE] dnscrypt-proxy 2.0.19
[NOTICE] Now listening to 0.0.0.0:44353 [UDP]
[NOTICE] Now listening to 0.0.0.0:44353 [TCP]
[NOTICE] [cloudflare] OK (DoH) - rtt: 19ms
[NOTICE] Server with the lowest initial latency: cloudflare (rtt: 19ms)

这代表现在 44353/udp 收到的普通 DNS 请求将会通过 DoH 加密转发给 Cloudlfare 进行解析。

使用 dig @192.168.2.191 -p44353 google.com 确认 dnscrypt-proxy 工作正常之后,即可在 Pi-Hole 的后台将上游 DNS 改成 127.0.0.1#44353

pi-hole-upstream-dnscrypt-proxy

至此,Pi-Hole 对外的 DNS 请求都是加密的了,你的 ISP 不再能看到你局域网内设备的 DNS 请求。

以下命令可用于确认 Pi-Hole 是否工作正常:

# 正常返回
$ dig @192.168.2.191 google.com

# 返回 0.0.0.0 或 NXDOMAIN 或 NODATA(根据不同的屏蔽方式)
# doubleclick.net 是 Google 广告的域名
$ dig @192.168.2.191 doubleclick.net

若一切正常,则可在路由器中将 DHCP 分配的 DNS 地址改成 Pi-Hole 的地址。待局域网内的设备重连,或是 DHCP 租期过期之后,即可 Pi-Hole 中观察到被放行和被屏蔽的域名请求。且通过抓包可观察到,Pi-Hole 对外的 DNS 请求是加密的而非明文的。

五、使用 CoreDNS 加密发往 Pi-Hole 的请求 / 不在家时也能使用 Pi-Hole

自 Android 9 "Pie" 开始,系统里内建了一个 Private DNS 功能。开启之后,Android 设备可以在任意网络(蜂窝网络和 Wi-Fi)下使用加密的 DNS 协议进行域名解析。根据我的测试,除了 IMS (Wi-Fi Calling) 和 RCS 等底层通讯服务之外,几乎所有的 DNS 请求都是加密的,而不再使用手机运营商或是 Wi-Fi 热点所提供的普通 DNS 服务器。

这是 Android 首次可以免 root、免第三方应用的情况下,直接在系统设置里设置一个真正「全局」的 DNS——而且还是加密的。我们可以使用这个功能,让 Android 设备在不连接家里 Wi-Fi 的时候,也可以使用家里的 Pi-Hole。

我一直以为 Private DNS 是用的 DoH (DNS over HTTPS),而 @yegle 提醒我这其实用的是另一个加密的 DNS 协议 DoT (DNS over TLS)。dnscrypt-proxy 只支持 DoH(接收和转发),但不支持 DoT。于是我找到了后起之秀 CoreDNS 用来接收 DoT 请求。

CoreDNS 同样使用 Golang 编写,提供了各平台的预编译单文件可执行程序systemd 文件,因此即使你的发行版没有提供 CoreDNS 的打包,使用 Ansible 快速写一个部署脚本也不费事。

以下是我使用的最小配置(真的很小):

# /etc/coredns/Corefile

tls://.:853 {
  tls /etc/coredns/cert.crt /etc/coredns/cert.key
    forward . 127.0.0.1:53
}

以上配置会让 CoreDNS 监听 853/tcp 的 DoT 请求,并将其转发给 53/udp,也就是 Pi-Hole 的端口。

将自己的域名指向家里的公网 IP 地址,然后在路由器上配置 DNAT 将 853/tcp 转发给 Raspberry Pi,最后将 Android 设备的 Private DNS 设置为自己的域名。设置方法可以参考 Cloudflare Blog 的图文教程,只需要将 Private DNS 的域名改成自己的域名即可。

至此,即使 Android 设备在使用蜂窝网络或是咖啡馆的 Wi-Fi 热点,其 DNS 请求也是加密发给家里的 Pi-Hole 的,时刻享受着其提供的广告、追踪器和恶意软件域名过滤功能。

经过以上的折腾,不在家时 Android 的 DNS 请求是这样流转的:

Android ---> CoreDNS ---> Pi-Hole ---> dnscrypt-proxy ---> Cloudflare DNS
        (853/tcp, DoT) (53/udp, DNS)  (44353/udp, DNS)     (443/tcp, DoH)

而局域网内的设备则是直接发往 Pi-Hole。无论哪一条路,在公网上的部分(到达 Cloudflare 之前)都是加密的,对 ISP 不可见。

CoreDNS 和 Cloudflare DNS 都是既支持 DoH 也支持 DoT 的,所以上图中的 dnscrypt-proxy 完全可以拿 CoreDNS 替换,这就留给读者当作练习了。

任何暴露在公网的服务皆有被攻击的风险,CoreDNS 也不例外。如果你有被害妄想症的话,你可以通过其 acl 插件进行一定的限制。

出于隐私问题的顾虑,Cloudflare DNS 并不支持 EDNS,这意味如果你在离你较远的位置搭建了本文所述的方案的话(比如你在欧洲,而在一台美国 VPS 上安装 Pi-Hole),那么你得到的 DNS 解析可能不是地理位置最优的。因此如果有条件的话,推荐还是在家庭宽带环境下搭建本文所述的方案,或者至少在离你(网络上)较近的服务器上搭建。

本文地址: https://wzyboy.im/post/1372.html 。转载请注明出处。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK