2012年5月13日星期日

OpenWRT DNS

就我所知,目前可以应对DNS劫持的方法有以下这么些:

  1. 使用加密的通道进行DNS查询。
  2. 使用TCP协议发送DNS请求。
  3. 使用监听于非标准端口的DNS服务器。
  4. 设法挡掉伪造的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 条评论 :

dxd5201 说...

用ttl过滤的方式实际上局限性比较大,可能随着网络的变化ttl也在变。

oCameLo 说...

是啊,所以虽然偶尔抽风,不过我自己目前用的是opendns