搭建一个可用的DNS

我发现即便是用ShadowsocksNX全局打开的话,在console下面也不能正常访问一些网站。我想到了有可能这是DNS被污染的原因。


想要验证DNS被污染了其实也非常简单,最好有个WireShark. 先将DNS修改成为8.8.8.8, 然后请求一个被墙的地址比如data.castbox.fm. `dig data.castbox.fm`

dns-poisoning-wireshark.png

可以看到首先返回了两个A分别是66.*** 和 199.***,这两个IP如果查询地理位置的话发现都是facebook.com的IP,而第三个CNAME才是正确的内容.


解决DNS被污染的问题,我突然想到了可以使用ss. 因为之前看ss的代码里面记得有个 `asyncdns.py` 这个文件,专门用于DNS解析的。所以就不加思索地直接试了一把(比如ss在本地62221上) `dig @127.0.0.1 -p 62221 data.castbox.fm`(@是可以指定dns服务器,-p可以使用dns端口)。但是ss有下面这样的错误日志

2017-12-12 22:01:22 WARNING  udprelay.py:349 drop a message since frag is not 0
2017-12-12 22:01:27 WARNING  udprelay.py:349 drop a message since frag is not 0
2017-12-12 22:02:47 WARNING  udprelay.py:349 drop a message since frag is not 0

我大概明白了,ss暴露的62221是支持socks5的tcp/udp端口,不能直接应用于dns. 必须在前面用个组件比如 `dns2socks` 或者是 `ss-tunnel`。


dns2socks这个软件是C编写的,我希望有个python编写的(这样就不要编译并且可以很容易地在其他地方部署了),所以找到了 `ss-tunnel`. 在我的印象中(阅读ss代码的时候),ss是没有ss-tunnel这个功能的。网上搜索了一下也是在 `shadowsocks-libev` 这个项目下面才有,而这个项目也是C + libev来写的。不过很有趣的是,在shadowsocks的python实现上,有位同学提交了一个python的ss-tunnel实现 https://github.com/shadowsocks/shadowsocks/pull/759 并且已经被合并到了master上面。

使用 `ss-tunnel` 这个需要同时升级client/server才行,并且中间不能走kcptunnel(kcp使用的是tcp协议,而DNS是udp协议)。当然可以在dig里面指定使用tcp来进行解析 `+tcp`。解析正确了,ss-tunnel运行在65353端口上面。接下来的工作就是结合dnsmasq来做DNS转发了。

➜  ~ dig @127.0.0.1 -p 65353 data.castbox.fm

; <<>> DiG 9.9.7-P3 <<>> @127.0.0.1 -p 65353 data.castbox.fm
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13971
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;data.castbox.fm.		IN	A

;; ANSWER SECTION:
data.castbox.fm.	299	IN	CNAME	casbox-elb-1612116034.ap-northeast-1.elb.amazonaws.com.
casbox-elb-1612116034.ap-northeast-1.elb.amazonaws.com.	4 IN A 52.197.248.46
casbox-elb-1612116034.ap-northeast-1.elb.amazonaws.com.	4 IN A 52.69.132.23

;; Query time: 81 msec
;; SERVER: 127.0.0.1#65353(127.0.0.1)
;; WHEN: Wed Dec 13 21:30:04 CST 2017
;; MSG SIZE  rcvd: 144

下面在用 `+tcp` 来试试·。ss默认地是同时开启tcp/udprelay功能的。

➜  ~ dig @127.0.0.1 -p 65353 data.castbox.fm +tcp

; <<>> DiG 9.9.7-P3 <<>> @127.0.0.1 -p 65353 data.castbox.fm +tcp
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2132
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;data.castbox.fm.		IN	A

;; ANSWER SECTION:
data.castbox.fm.	299	IN	CNAME	casbox-elb-1612116034.ap-northeast-1.elb.amazonaws.com.
casbox-elb-1612116034.ap-northeast-1.elb.amazonaws.com.	59 IN A	52.69.132.23
casbox-elb-1612116034.ap-northeast-1.elb.amazonaws.com.	59 IN A	52.197.248.46

;; Query time: 186 msec
;; SERVER: 127.0.0.1#65353(127.0.0.1)
;; WHEN: Wed Dec 13 21:34:22 CST 2017
;; MSG SIZE  rcvd: 144

在找到dnsmasq之前,在网上看到了很多dns的软件比如pdnsd, unbound这些。dnsmasq的确不是特别符合我的预期,因为它不支持按照tcp去转发dns请求(这样就没有办法用kcptun做relay了),不过因为我只是打算实验以下,所以就没有继续研究pdnsd, unbound这些软件了。如果要做一个reliable dns的话,支持tcp请求应该是必须的。

按照这个 https://blog.netsh.org/posts/mac-os-x-dnsmasq_1762.netsh.html 来配置dnsmasq, 然后找到了一个项目 https://github.com/cokebar/gfwlist2dnsmasq 可以从gfwlist里面拉取到当前封锁域名然后生成dnsmasq格式。dnsmasq有个缺点就是只能使用UDP请求来解析,看到有一些其他软件比如pdnsd或者是unbound可以使用tcp,不过感觉配置起来有点繁琐。

网上有很多配置,这里也放一下我的配置,供参考。

➜  ~ cat /usr/local/etc/dnsmasq.conf
resolv-file=/usr/local/etc/dnsmasq.resolv.conf
strict-order
no-hosts
cache-size=32768
listen-address=127.0.0.1
conf-dir=/usr/local/etc/dnsmasq.d

➜  ~ cat /usr/local/etc/dnsmasq.resolv.conf
nameserver 8.8.8.8
nameserver 8.8.4.4

➜  ~ head /usr/local/etc/dnsmasq.d/dnsmasq_gfwlist.conf
# dnsmasq rules generated by gfwlist
# Last Updated on 2017-12-13 15:48:46
#
server=/030buy.com/127.0.0.1#65353
server=/0rz.tw/127.0.0.1#65353
server=/1-apple.com.tw/127.0.0.1#65353
server=/1000giri.net/127.0.0.1#65353
server=/100ke.org/127.0.0.1#65353
server=/10conditionsoflove.com/127.0.0.1#65353
server=/10musume.com/127.0.0.1#65353

在mac上面为了方便重启,在shell profile里面增加了两个指令

reload_dnsmasq() {
    sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
    sudo launchctl stop homebrew.mxcl.dnsmasq
    sudo launchctl start homebrew.mxcl.dnsmasq
    sudo killall -HUP mDNSResponder
}
unload_dnsmasq() {
    sudo launchctl stop homebrew.mxcl.dnsmasq
    sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
}


如果希望全局可以自动分流的话,不仅仅DNS需要自动分流,其他流量(应用流量)也需要分流,这个就需要比如ss-redir/iptables这类软件来支持,配置起来内容就比较多了。如果仅仅是单纯地想得到一个正确的,纯净的dns的话,那么ss + ss-tunnel + dnsmasq就可以完成了。