Cloudflare 宣布使用 1.1.1.1 作為 DNS,並且強調隱私保護。由於 Cloudflare DNS 支持 DNS-over-TLS 和 DNS-over-HTTPS,這使得加密 DNS 成為了熱門話題。
因為操作系統往往不支持加密 DNS,所以要使用加密 DNS 必須使用一個加密 DNS 的客戶端,然后這個客戶端同時作為一個明文 DNS 服務器向操作系統提供正常的 DNS 服務。我可以選擇在每一台我使用的設備上安裝一個加密 DNS 客戶端(對於 iOS 來說則是 NetworkExtension),我也可以選擇在家里假設一個加密 DNS 客戶端然后把路由器 DNS 指向過去,之后家里所有設備的 DNS 都會跟着變。我選擇了后者,因為這樣做比較方便,也為我提供了一個折騰 Raspberry Pi 的借口——我需要把加密 DNS 客戶端部署到 Raspberry Pi 上讓它長期為家里的局域網提供 DNS 服務。
(為什么不用 OpenWRT 呢?因為我家里已經在用 Eero 來做路由器了,它可以通過 mesh Wi-Fi 來提供更好的覆蓋。如果我要多買一個 OpenWRT 路由放在 Eero 前面,那我還不如買個 Raspberry Pi 來玩玩呢。)
Raspberry Pi
我買了這個 Raspberry Pi 套裝,因為它自帶盒子和電源。電源不重要,我家已經有很多 USB 電源,但是我總不能一塊電路板隨便一放吧,所以必須買個盒子。然后我還買了張 64GB 的 microSD。因為我所有 microSD 都是 64GB 的,所以我只買 64GB 的方便有需要時隨意替換。
收到 Raspberry Pi 之后,我就按照官方 NOOBS 的指引下載和准備安裝。然而 NOOBS 復制到 SD 卡后無論如何 Raspberry Pi 都無法正常啟動,只亮紅燈沒有視頻輸出。搜索之后發現綠燈不亮就是沒有讀取 SD 卡進行啟動。我開頭懷疑是我下載的 NOOBS 有問題,於是換成 NOOBS Lite 和 Raspbian,但都是不行。我也懷疑過是不是下載的 zip 數據有問題,但 sha256 checksum 正確。
實在找不到問題了,我就開始搜索到底 Raspberry Pi 是如何進行引導的,發現它必須從 FAT 分區進行引導。Raspberry Pi 自己的官方文檔教大家使用一個叫做 SD Association’s Formatting Tool 的軟件來格式化 SD 卡,但這個軟件在面對超過 32GB 的卡時就會傻傻地使用 exFAT 來進行格式化。其實使用 Mac 內置的 Disk Utility 不就好咯,就算是超過 32GB 的 SD 卡也可以選擇格式化為 FAT。
把 SD 格為 FAT 后,所有問題都解決了。NOOBS 能夠正常啟動,接着 Raspbian 也能夠順利裝上。Raspberry Pi 安裝好之后我嘗試啟用 VNC 以便我用 Mac 遠程控制,結果那上面裝的 VNC 和 Mac 自帶的 Screen Sharing 客戶端不兼容,我只好降級到用 SSH,不過也能完成絕大多數操作了。
啟用 SSH 后 Raspbian 會提醒你改默認密碼,沒有改的話記得改掉,否則太不安全了。因為 Raspbian 連 dig
這么基本的命令都沒有,需要通過 apt-get
來安裝,所以我們需要先更新一下然后把 dig
裝上:
sudo apt-get update
sudo apt-get install dnsutils
DNS-over-HTTPS
我基本上就是按照 Cloudflare 的 DNS-over-HTTPS 指引 來做的。一開始我覺得 Raspbian 既然是 Debian 系的就下載了 Debian 的安裝包,結果發現安裝不上去。接着嘗試用 Linuxbrew 來裝 homebrew 的版本,結果裝上后發現不能執行。看到「exec format error」並且搜索后才突然明白到,Raspberry Pi 不是基於 x86/x64 架構的,而是基於 ARM 架構的。那到底 Raspberry Pi 是 32 位還是 64 位的呢?理論上 Raspberry Pi 3 B+ 是 64 位的 CPU,但在 Raspbian 上執行 uname -a
的話會顯示:
Linux raspberrypi 4.9.80-v7+ #1098 SMP Fri Mar 9 19:11:42 GMT 2018 armv7l GNU/Linux
所以其實不是 64 位的,如果要選正確的版本那必須選 32 位的 ARM。只要選擇正確的版本,Cloudflared 和 Dnscrypt-Proxy 都是可以用的。我兩個都裝了,都能在 localhost:53 上跑起來,最后選擇了 Dnscrypt-Proxy 是因為配置方便。(Dnscrypt-Proxy 有配置文件模板,改改就可以用了,不需要對着文檔寫一個新的。)
Dnscrypt-Proxy 的安裝跟着官方指引做就可以了,選擇 Linux 版本 來下載。記得下載 Linux ARM 的版本,不要用 Android 或者 ARM64 的版本。(盡管 Dnscrypt-Proxy 是可以安裝在 Pi-Hole 上面的,但我不想安裝 Pi-Hole 來過濾廣告所以選擇了非 Pi-Hole 的版本。)盡管官方指引叫你檢查一下是否有別的 DNS 服務正在使用 53 端口,但新裝的 Raspbian 應該是不會有任何服務占用 53 端口的所以這一步可以略過。
Dnscrypt-Proxy 下載和解壓好之后就可以開始配置了。假設我們已經在 Dnscrypt-Proxy 解壓好的目錄里:
cp example-dnscrypt-proxy.toml dnscrypt-proxy.toml
sudo ./dnscrypt-proxy
這時候 Dnscrypt-Proxy 應該能夠跑起來,在 Raspberry Pi 上用 dig
驗證一下就知道了:
dig +short @127.0.0.1 cloudflare.com AAAA
這個驗證必須在 Raspberry Pi 上做,因為 Dnscrypt-Proxy 的默認配置只監聽 localhost:53 端口,從另外一台機器連上來 53 端口是不行的。如果 Dnscrypt-Proxy 正常工作了,我們就可以開始改配置了。打開 dnscrypt-proxy.toml
,然后把 server_names
和 listen_addresses
改掉。(在 SSH 上面,用 nano
或 vi
都可以編輯 dnscrypt-proxy.toml
。)
首先找到 server_names
,把前面注釋這一行的 #
刪掉,然后把后面的內容改為你想要的服務。因為 Cloudflare 和 Google 都支持 DNS-over-HTTPS,而且都是可靠的大公司,所以我在這兩家之間選。因為 Google 不強調隱私,有可能記錄數據,所以我只用 Cloudflare 的,按照 Cloudflare 的文檔把這一行改為這樣子:
server_names = ['cloudflare', 'cloudflare-ipv6']
接着找到 listen_addresses
,你會發現它只監聽 IPv4 和 IPv6 的 localhost,所以其他機器不能用 Raspberry Pi 來做 DNS。這時候你要想辦法把 Raspberry Pi 的 IP 綁上去。我的做法是這樣子的:因為我家里路由器的 IP 是 192.168.0.1,然后 DHCP 范圍是 192.168.0.10–192.168.0.199,所以 192.168.0.2–192.168.0.9 是不會被動態分配出去的。我把 Raspberry Pi 的有線網 IP 寫死為 192.168.0.2,然后把它加到監聽地址端口列表上:
listen_addresses = ['127.0.0.1:53', '[::1]:53', '192.168.0.2:53']
搞掂之后,可以再啟動一下 Dnscrypt-Proxy:
sudo ./dnscrypt-proxy
然后從另外一台機器使用 dig
測試一下:
dig +short @192.168.0.2 cloudflare.com AAAA
如果沒有問題的話,就可以把 Dnscrypt-Proxy 當裝系統服務啟動了:
sudo ./dnscrypt-proxy -service install
sudo ./dnscrypt-proxy -service start
sudo systemctl enable dnscrypt-proxy
之后登錄到路由器,把路由器的 DNS 改為 192.168.0.2 就可以了,家里所有設備的 DNS 都會經過 Raspberry Pi 上的 Dnscrypt-Proxy 走 DNS-over-HTTPS 連接 Cloudflare 服務器。盡管 Dnscrypt-Proxy 的官方指引還說要把 Linux 上的 DNS 客戶端指向 localhost,但因為我暫時不在 Raspberry Pi 上做別的事情所以不在意 Raspberry Pi 本身發出的 DNS 請求是否加密。只要它作為 DNS 服務器服務好我家里的其他設備就行。
已知問題
上述做法是有一些已知問題的。首先,如果我們請求使用 SNI 的 HTTPS 服務的話,我們還是會明文傳輸域名的,就算 DNS 加密了還是會存在域名泄漏的情況。如果多個不同證書的 HTTPS 域名要在一個 IP 上共處,那必須使用 SNI 否則 SSL 握手時無法決定用哪個證書的密鑰。因此 SNI 常見於跑在雲平台上的服務,因為雲平台往往在多個服務之間共享 IP,但每一個服務來自不同的客戶有不同的證書。對於大型網站來說這不常見,因為無論一個大型網站旗下有多少域名,它都可以選擇把所有域名放在同一個證書里面。
其次,我沒有做 IPv6 的配置,只讓 Dnscrypt-Proxy 綁定了一個 IPv4 地址。這時候如果 IPv6 分配了不一樣的 DNS,那使用 IPv6 DNS 查詢時還是會走明文的。如果你所處的網絡完全不使用 IPv6,那是沒問題的。我知道 Comcast 是會分配 IPv6 地址和 IPv6 DNS 的,所以如果不在路由器上設置 IPv6 DNS(或者是不能設置)的話,那 IPv6 DNS 就有可能是 Comcast 分配下來的,也就是明文 DNS。(其他 ISP 也一樣。)
最后,如果你喜歡我的文章,歡迎通過郵件訂閱我的博客。