众所周知,DNS 污染是较常遭遇的攻击手段之一。

ChinaDNS

ChinaDNS 为代表,目前自动解决这个问题的思路是多 DNS 对比:同时查询较快的本地 DNS(通常为 ISP 的 DNS)和较慢的可信 DNS(通常为经过加密传输的 Google 或 OpenDNS 等),对比返回的结果,若有差异,说明本地 DNS 返回的结果大概率被污染。
以此为基础,配合国内网站和国内 CDN 的白名单(直接使用本地 DNS)、被封锁网站的黑名单(直接使用可信 DNS)基本上达到了兼顾功能(反污染)和效率(国内跳过)的效果。
这个思路的缺点也很明显:

  • 如果使用白名单,截至今天(2018.01.26),白名单 accelerated-domains.china.conf 已经包含了 39881 条记录,这对很多路由器上的 dnsmasq 造成了不小的压力。而实际上,在这么一份大而全、更新飞快的列表中,大部分人使用的只是很小一部分。
  • 如果不使用白名单,仅使用黑名单,对于非重度网络使用者,其实是一个可以接受的方案。
  • 如果不使用白名单也不使用黑名单,结果更糟糕。这种情况下一般会启用类似 GEO IP: CN => DIRECT; FINAL => PROXY 的自动规则,由于目前绝大部分大中型网站均在多个区域部署 CDN 进行加速,对这些网站查询可信 DNS 有非常大的概率会返回一个对本地来说负优化的结果(为远端 VPS 或者代理优化,解析到一个对本地较慢的 CDN 节点),甚至网站会根据 GEO IP 的结果强制跳转(如淘宝海外站)。
  • 路由器版本的 ChinaDNS 有时会出一些莫名其妙的问题导致不能正常解析,非常……影响心情。

改进思路

为了弥补这些缺点,我们提出以下需求:

  • 只查询一个本地 DNS,或者,一个快的远端 可信 DNS(考虑到大部分地区到 114.114.114.114119.29.29.29 的延迟,可以认为 30-45 左右的延迟是可以接受的)。
  • 不使用庞大的白名单和黑名单。
  • 确保解析到的结果为本地优化。

针对后两点,技术上其实已经有了解决方案,那就是 RFC 7871 (Client Subnet in DNS Queries, aka edns-client-subnet, ECS),RFC 文档见此,还可参考 Google 的帮助
ECS 允许 DNS 解析的请求放附带一个网络地址,要求 DNS 服务器做出针对这个地址优化的解析响应。
但是,ECS 目前的实施还是非常不接地气的。国内大厂多有成熟的智能解析方案,国外大厂更由于隐私等诸多问题对此动力不足。即便是目前对 ECS 支持的最好的 Google Public DNS,发过去的请求包也只有一半可以得到正确的 ECS 响应。
因此 Google 提供了一种迂回的解决方案:DNS-over-HTTPS文档)。不使用不能稳定得到 ECS 响应的 DNS 协议,通过 HTTPS 协议可以稳定获取 ECS 响应。
我们可以从这个方案中得到一个新思路,将 DNS 请求转化为 HTTPS 请求,再将收到的响应转化为 DNS 响应返回(事实上会小幅度增加解析耗时)。

部署

实现这个思路有两种部署方案:

本地部署

下载 google-https-dns (Go 语言,支持包括 ARM 在内的多种 CPU),参照作者的说明安装在本地路由器或其他设备上为局域网提供服务,通过前置的代理(支持 socks 或影梭)访问 Google 的 DNS over HTTPS。

远端部署

推荐以容器的形式部署。
远端部署的优势是可以共享自建 DNS。
这种方式将 google-https-dns 作为后端提供服务,同时在前端放置一个支持 ECS 缓存的 DNS 代理(推荐使用 Unbound)以获得更高性能。
部署方式如下,也可以参考 这份 Gist

  1. 创建 google-https-dns 的容器:docker run -d --name dns-google --log-opt max-size=1m --restart=unless-stopped tarot13/google-https-dns
  2. 准备 Unbound 的配置文件 unbound.conf(见后文)和 Root Hints root.hints(可以从 ftp://FTP.INTERNIC.NET/domain/named.cache 下载)
  3. 创建 Unbound 的容器:docker run -d --name dns-unbound -v $HOME/unbound:/etc/unbound -p 53:53/tcp -p 53:53/udp --link=dns-google:dns-google --log-opt max-size=1m --restart=unless-stopped tarot13/unbound

供参考的 unbound.conf 配置:
需要注意的是其中的两项:

  1. ECS 缓存(subnetcache)必须在模块配置中启用:module-config: "subnetcache iterator"
  2. 最好指定允许发送 ECS 信息的上游 DNS 网段(即 google-https-dns 的地址):send-client-subnet: 172.16.0.0/12
server:
  username: "root"
  interface: 0.0.0.0
  verbosity: 1
  do-daemonize: no
  access-control: 0.0.0.0/0 allow
  root-hints: "/etc/unbound/root.hints" # Root Hints: ftp://FTP.INTERNIC.NET/domain/named.cache
  auto-trust-anchor-file: "/etc/unbound/root.key" # Auto generated
  do-ip4: yes
  do-ip6: no
  do-udp: yes
  do-tcp: yes
  hide-identity: yes
  hide-version: yes
  harden-glue: yes
  use-caps-for-id: yes
  cache-max-ttl: 3600
  prefetch: yes
  num-threads: 4
  msg-cache-size: 64m
  rrset-cache-size: 128m
  module-config: "subnetcache iterator"
  unwanted-reply-threshold: 10000000
  do-not-query-localhost: no
  send-client-subnet: 172.16.0.0/12
  minimal-responses: yes

  forward-zone:
    name: "."
    forward-host: dns-google

使用

对于本地部署,局域网内的其他设备可以直接使用。
对于远端部署,可以选择通过非 53 端口转发,或者通过任意方法加密传输。

测试记录:

  • dig with subnet (web), mainland IPs:
; <<>> DiG 9.10.6 <<>> @127.0.0.1 www.taobao.com +subnet=114.114.114.114
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9065
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
; CLIENT-SUBNET: 114.114.114.114/32/24
;; QUESTION SECTION:
;www.taobao.com.                        IN      A

;; ANSWER SECTION:
www.taobao.com.         416     IN      CNAME   www.taobao.com.danuoyi.tbcache.com.
www.taobao.com.danuoyi.tbcache.com. 162 IN A    58.215.145.110
www.taobao.com.danuoyi.tbcache.com. 162 IN A    58.218.215.155
www.taobao.com.danuoyi.tbcache.com. 162 IN A    180.96.11.188
www.taobao.com.danuoyi.tbcache.com. 162 IN A    222.186.49.177

;; Query time: 76 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Jan 03 17:29:12 China Standard Time 2018
;; MSG SIZE  rcvd: 317
  • dig without subnet, HK IP:
; <<>> DiG 9.10.6 <<>> @127.0.0.1 www.taobao.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39972
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.taobao.com.                        IN      A

;; ANSWER SECTION:
www.taobao.com.         392     IN      CNAME   www.taobao.com.danuoyi.tbcache.com.
www.taobao.com.danuoyi.tbcache.com. 179 IN A    205.204.104.227

;; Query time: 92 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Jan 03 17:29:18 China Standard Time 2018
;; MSG SIZE  rcvd: 144

如果有什么问题,欢迎在微博或者上和我交流。╮( ̄▽ ̄")╭