就我所知,目前可以应对DNS劫持的方法有以下这么些:
- 使用加密的通道进行DNS查询。
- 使用TCP协议发送DNS请求。
- 使用监听于非标准端口的DNS服务器。
- 设法挡掉伪造的DNS应答。
方案一的实现方式有VPN、Tor等等,前者需要额外的开销,后者速度和稳定性不佳,暂不考虑。
使用TCP协议发送DNS请求
由于GFW只污染了使用UDP协议发往服务器53端口的请求,所以改用TCP协议发送请求就可以规避污染。只是具体到OpenWRT,其默认采用的dnsmasq无法强制使用TCP协议向上游服务器转发请求,所以非要用这个法子,就得再装一个unbound。而unbound的资源占用对路由器来说颇为不低,所以这个方案对OpenWRT来说实用性并不强。
opkg install unbound后,修改/etc/unbound/unbound.conf:
server: port: 5353 do-ip4: yes do-ip6: no do-udp: yes do-tcp: yes tcp-upstream: yes forward-zone: name: "." forward-addr: 8.8.8.8 forward-addr: 8.8.4.4
然后修改/etc/config/dhcp:
config 'dnsmasq' #option 'resolvfile' '/tmp/resolv.conf.auto' option 'noresolv' '1' list 'server' '127.0.0.1#5353'
注释掉resolvfile并打开noresolv选项,是为了让dnsmasq不使用resolvfile中的DNS服务器进行查询,下同。
使用监听于非标准端口的DNS服务器
如上所说,GFW只污染了使用UDP协议发往服务器53端口的请求,所以若是某个服务器由非标准端口提供DNS服务,同样可以规避DNS污染。
使用非标准端口的DNS服务器不多,Google DNS就不行。有个德国隐私基金会倒是提供了一组服务器,不过速度也太慢了点。于是没得选了,只剩下OpenDNS。
修改/etc/config/dhcp:
config 'dnsmasq' #option 'resolvfile' '/tmp/resolv.conf.auto' option 'noresolv' '1' list 'server' '208.67.222.222#5353' list 'server' '208.67.220.220#5353'
针对OpenDNS查询不存在的域名显示广告的问题,可以用dnsmasq的bogus-nxdomain来解决,将下面这一句加入dnsmasq的配置文件即可。(如果遇到国内流氓运营商的劫持问题,也可以用这个法子来试试。)
bogus-nxdomain=67.215.65.132
设法挡掉伪造的DNS应答
用tcpdump或Wireshark抓包可以看到,GFW会在正确的DNS应答之前加塞几条错误应答。如果我们可以分辨哪些是GFW伪造的应答并将其忽略,同样可以达到规避DNS污染的目的。
GFW伪造的应答分两种,一种返回一个错误的IP,另外一种不包含任何查询结果。针对后一种情况,AntiDNSPoisoning提供了如下规则:
iptables -I INPUT -p udp --sport 53 -m u32 --u32 "4 & 0x1FFF = 0 && 0 >> 22 & 0x3C @ 8 & 0x8000 = 0x8000 && 0 >> 22 & 0x3C @ 14 = 0" -j DROP iptables -I FORWARD -p udp --sport 53 -m u32 --u32 "4 & 0x1FFF = 0 && 0 >> 22 & 0x3C @ 8 & 0x8000 = 0x8000 && 0 >> 22 & 0x3C @ 14 = 0" -j DROP
由于需要安装iptables-mod-u32和kmod-ipt-u32,我的OpenWRT需要重新编译,所以暂时没法实际测试。我依照原文的说法,写了下面两条规则丢掉Answer、Authority和Additional均为0的应答:
iptables -I INPUT -p udp --sport 53 -m string --algo bm --hex-string "|81 80 00 01 00 00 00 00 00 00|" --from 30 --to 40 -j DROP iptables -I FORWARD -p udp --sport 53 -m string --algo bm --hex-string "|81 80 00 01 00 00 00 00 00 00|" --from 30 --to 40 -j DROP
至于返回错误IP的应答则有点复杂。AntiDNSPoisoning的思路是通过向伪DNS服务器查询被污染域名,获得错误IP的列表,然后再将返回这些IP的应答都丢掉。这个思路看起来可行,但问题在于如何获得完整的错误IP列表。
以下是我这里找到的错误IP:
- 159.106.121.75
- 37.61.54.158
- 59.24.3.173
- 203.98.7.65
- 243.185.187.39
- 78.16.49.15
- 46.82.174.68
- 159.24.3.173
- 93.46.8.89
- 243.185.187.30
- 8.7.198.45
假如说这个列表完备,且在相对较长的一段时间内都没有变动的话,则AntiDNSPoisoning提出的方案可行。不过这两个前提条件是否成立,那就需要比较长的时间来验证了。另外,iptables的string模块是否会带来比较严重的延迟也是一个问题。毕竟按照AntiDNSPoisoning方案的思路,每个DNS查询响应都要比对十几条规则,或许为dnsmasq打个补丁来做这件事要更好一点。
除了返回的IP外,TTL值也可以用来分辨GFW伪造的DNS查询响应。比如从本机ping 8.8.8.8,得到TTL值51,则可以构造出以下规则:
iptables -I INPUT -p udp -s 8.8.8.8 --sport 53 -m ttl --ttl-lt 52 -j DROP iptables -I INPUT -p udp -s 8.8.8.8 --sport 53 -m ttl --ttl-gt 52 -j DROP iptables -I FORWARD -p udp -s 8.8.8.8 --sport 53 -m ttl --ttl-lt 51 -j DROP iptables -I FORWARD -p udp -s 8.8.8.8 --sport 53 -m ttl --ttl-gt 51 -j DROP
本机的TTL为51,则路由器上少跳一次所以是52。在网络环境一定的情况下,TTL一般不会变化,而GFW伪造的应答与正确应答的TTL刚好一样的可能性也很低,所以这个法子也有一定的实用价值。
小结
从不需要安装更多的程序和规则数量两方面考虑,目前我用的是5353端口的OpenDNS。
2 条评论 :
用ttl过滤的方式实际上局限性比较大,可能随着网络的变化ttl也在变。
是啊,所以虽然偶尔抽风,不过我自己目前用的是opendns
发表评论