十分钟架设DNS转发缓存服务器

为了应对GFW的DNS污染,我们可以进行UDP转发或者搭建自己的DNS服务器。

socat UDP转发

socat是netcat的升级版,最简单的方式就是使用TCP连接来转发UDP数据包,非标准端口的tcp连接并不会被GFW检查和干扰。

安装: yum install socat

server: socat -ly tcp4-listen:5353,reuseaddr,fork UDP:8.8.8.8:53
client: socat -ly -T 2 udp4-listen:53,reuseaddr,fork tcp:157.7.7.7:5353

这样server端和client端会建立基于5353端口的tcp连接,并将client端UDP端口53的流量通过server端转发到8.8.8.8来进行dns查询

这样就可以正常解析了,不过我们也可以通过socat配合ssh隧道来对流量进行加密。

搭建DNS cache forward服务器

搭建自己的公共DNS服务器。

安装bind yum install bind
配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
options {
listen-on port 53 { any; }; # 允许任意连接
listen-on-v6 port 53 { ::1; };
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
memstatistics-file "/var/named/data/named_mem_stats.txt";
allow-query { any; }; # 允许任意查询
allow-query-cache {any;};
recursion yes; # 允许递归查询
dnssec-enable yes;
dnssec-validation yes;
dnssec-lookaside auto;
forwarders {
8.8.8.8; # 转发查询DNS服务器
8.8.4.4;
};
forward only; # 设置转发
/* Path to ISC DLV key */
bindkeys-file "/etc/named.iscdlv.key";
managed-keys-directory "/var/named/dynamic";
};

启动dns服务 /etc/init.d/named start
开机自启 chkconfig named on

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost named]# dig www.google.com @192.168.0.100
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.el6_6.3 <<>> www.google.com @192.168.0.100
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14577
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 136 IN A 173.194.120.84
www.google.com. 136 IN A 173.194.120.82
www.google.com. 136 IN A 173.194.120.83
www.google.com. 136 IN A 173.194.120.81
www.google.com. 136 IN A 173.194.120.80
;; Query time: 64 msec
;; SERVER: 192.168.0.100# 53(192.168.0.100)
;; WHEN: Tue Jul 14 20:19:27 2015
;; MSG SIZE rcvd: 112

需要注意的是,如果上游DNS服务器不支持dnssec,比如alidns 223.5.5.5/223.6.6.6 ,则会出现这样的错误:

1
2
3
4
Jul 14 19:54:47 localhost named[25859]: error (no valid RRSIG) resolving 'com/DS/IN': 223.5.5.5# 53
Jul 14 19:54:47 localhost named[25859]: error (no valid RRSIG) resolving 'com/DS/IN': 223.6.6.6# 53
Jul 14 19:54:47 localhost named[25859]: error (no valid DS) resolving 'www.baidu.com/A/IN': 223.6.6.6# 53
Jul 14 19:54:47 localhost named[25859]: validating @0x7f28b84512d0: www.baidu.com A: bad cache hit (com/DS)

需要关闭dnssec

1
2
dnssec-enable yes;
dnssec-validation yes;

但是对关键域名的dns解析还是会被干扰,比如针对facebook的解析测试就会出现各种乱七八糟的IP地址,比如阿塞拜疆爱尔兰新西兰。
正确解析记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@conan ~]# dig www.facebook.com @127.0.0.1
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.el6_6.3 <<>> www.facebook.com @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40722
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;www.facebook.com. IN A
;; ANSWER SECTION:
www.facebook.com. 445 IN CNAME star.c10r.facebook.com.
star.c10r.facebook.com. 24 IN A 31.13.79.246
;; Query time: 0 msec
;; SERVER: 127.0.0.1# 53(127.0.0.1)
;; WHEN: Tue Jul 14 21:30:24 2015
;; MSG SIZE rcvd: 74

被干扰的解析记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost]# dig www.facebook.com @157.7.7.7
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.el6_6.3 <<>> www.facebook.com @157.7.7.7
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44038
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;www.facebook.com. IN A
;; ANSWER SECTION:
www.facebook.com. 2194 IN A 78.16.49.15
;; Query time: 31 msec
;; SERVER: 157.7.7.7# 53(157.7.7.7)
;; WHEN: Tue Jul 14 20:28:56 2015
;; MSG SIZE rcvd: 50

SSH TUNNEL

考虑使用加密的ssh隧道来保证dns请求的传输。

建立SSH本地端口转发

ssh -g -l root -L 53:127.0.0.1:53 517.7.7.7 -p 222

该命令将监听本地tcp 53端口,并将流量发送给远程服务器157.7.7.7的53端口,由于数据包到达远程服务器时ip来源是127.0.0.1,故为了安全起见,bind配置中的监听地址any最好换成localhost,只允许来自本机的连接。

将UDP连接转发到TCP连接

1
2
mkfifo /tmp/dnsfifo
nc -l -u -p 53 < /tmp/dnsfifo | nc 127.0.0.1 53 > /tmp/dnsfifo

这条语句通过管道,实现UDP端口53到TCP端口53的转发。不过我在尝试的过程中,nc一直报错,使用tee来重定向也不行,可能是nc版本的问题。

UDP转发到自己的DNS服务器

为了使用自己的DNS服务器来缓存解析结果,可以这样启动socat

server: nohup socat -ly tcp4-listen:5353,reuseaddr,fork UDP:127.0.0.1:53 &>/dev/null &
client: nohup socat -ly -T 2 udp4-listen:53,reuseaddr,fork tcp:157.7.7.7:5353 &>/dev/null &

在server端需要配置好bind服务器。-ly参数表示将日志记录到syslog。

客户端解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@tencent ~]# dig www.youtube.com @1.1.1.1
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.el6_6.3 <<>> www.youtube.com @1.1.1.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11545
;; flags: qr rd ra; QUERY: 1, ANSWER: 12, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;www.youtube.com. IN A
;; ANSWER SECTION:
www.youtube.com. 21589 IN CNAME youtube-ui.l.google.com.
youtube-ui.l.google.com. 289 IN A 173.194.126.195
youtube-ui.l.google.com. 289 IN A 173.194.126.200
youtube-ui.l.google.com. 289 IN A 173.194.126.201
youtube-ui.l.google.com. 289 IN A 173.194.126.199
youtube-ui.l.google.com. 289 IN A 173.194.126.192
youtube-ui.l.google.com. 289 IN A 173.194.126.198
youtube-ui.l.google.com. 289 IN A 173.194.126.194
youtube-ui.l.google.com. 289 IN A 173.194.126.206
youtube-ui.l.google.com. 289 IN A 173.194.126.197
youtube-ui.l.google.com. 289 IN A 173.194.126.193
youtube-ui.l.google.com. 289 IN A 173.194.126.196
;; Query time: 268 msec
;; SERVER: 1.1.1.1# 53(1.1.1.1)
;; WHEN: Wed Jul 15 10:30:30 2015
;; MSG SIZE rcvd: 243

其中1.1.1.1为socat client端。

为了验证缓存生效,我们可以再去server端解析www.youtube.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@conan ~]# dig www.youtube.com @127.0.0.1
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.el6_6.3 <<>> www.youtube.com @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1870
;; flags: qr rd ra; QUERY: 1, ANSWER: 12, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;www.youtube.com. IN A
;; ANSWER SECTION:
www.youtube.com. 21562 IN CNAME youtube-ui.l.google.com.
youtube-ui.l.google.com. 262 IN A 173.194.126.201
youtube-ui.l.google.com. 262 IN A 173.194.126.199
youtube-ui.l.google.com. 262 IN A 173.194.126.192
youtube-ui.l.google.com. 262 IN A 173.194.126.198
youtube-ui.l.google.com. 262 IN A 173.194.126.194
youtube-ui.l.google.com. 262 IN A 173.194.126.206
youtube-ui.l.google.com. 262 IN A 173.194.126.197
youtube-ui.l.google.com. 262 IN A 173.194.126.193
youtube-ui.l.google.com. 262 IN A 173.194.126.196
youtube-ui.l.google.com. 262 IN A 173.194.126.195
youtube-ui.l.google.com. 262 IN A 173.194.126.200
;; Query time: 0 msec
;; SERVER: 127.0.0.1# 53(127.0.0.1)
;; WHEN: Wed Jul 15 11:30:59 2015
;; MSG SIZE rcvd: 243

可以观察到请求时间为0 msec,证明已经对该解析结果进行缓存。
不过这样的dns缓存在socat的server端,即东京,如果可以缓存在client端,即国内服务器,这样解析结果会更快。
所以可以把上述流程反过来。
先用socat做UDP转发
server: socat -ly tcp4-listen:5353,reuseaddr,fork UDP:8.8.8.8:53
client: socat -ly -T 2 udp4-listen:5252,reuseaddr,fork tcp:157.7.7.7:5353
再在client上架设bind服务器,将请求全都转发到localhost的5252端口。bind配置完全和上述相同,不过forwarder如下:

1
2
3
forwarders {
127.0.0.1 port 5252;
};

这样国内服务器(client端)监听UDP 53端口,将dns请求转发到本地的5252端口,再通过socat建立的TCP 5352连接将请求转发到国外服务器(server端)的remote dns server(8.8.8.8),并将结果缓存在本地。
使用pc测试一下
第一次解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
C:\Users\conan\Desktop\BIND9>dig www.google.com @223.1.2.3
; <<>> DiG 9.10.2-P2 <<>> www.google.com @223.1.2.3
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11761
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 299 IN A 173.194.126.176
www.google.com. 299 IN A 173.194.126.177
www.google.com. 299 IN A 173.194.126.180
www.google.com. 299 IN A 173.194.126.179
www.google.com. 299 IN A 173.194.126.178
;; Query time: 218 msec
;; SERVER: 223.1.2.3# 53(223.1.2.3)
;; WHEN: Wed Jul 15 15:52:33 ?D1ú±ê×?ê±?? 2015
;; MSG SIZE rcvd: 123

耗费218ms
第二次解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
C:\Users\conan\Desktop\BIND9>dig www.google.com @223.1.2.3
; <<>> DiG 9.10.2-P2 <<>> www.google.com @223.1.2.3
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8423
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 298 IN A 173.194.126.177
www.google.com. 298 IN A 173.194.126.180
www.google.com. 298 IN A 173.194.126.179
www.google.com. 298 IN A 173.194.126.178
www.google.com. 298 IN A 173.194.126.176
;; Query time: 15 msec
;; SERVER: 223.1.2.3# 53(223.1.2.3)
;; WHEN: Wed Jul 15 15:52:34 ?D1ú±ê×?ê±?? 2015
;; MSG SIZE rcvd: 123

耗费15ms
至此工作正常,也可以使用queryperf进行压力测试。

dnsmasq按域名解析

这样的DNS服务器存在问题,或者说现在的DNS协议不那么完善。如果我们使用这样的境外DNS服务器去解析www.qq.com那么返回的域名将指向该网站的海外节点,导致我们访问这样的网站很慢,这本来是通过CDN的方式加速我们的访问,现在却成了制约因素。因为DNS服务器在向上请求时携带的是自己的IP地址,所以google发布了一个EDNS协议一个升级版功能edns-client-subnet,携带的请求IP为客户端的IP地址就可以避免这样的问题。

为了解决这样的问题,我们可以指定一份域名列表,指定域名按照指定DNS服务器去解析,这样就可以避免上述问题。bind配置太繁琐,我们可以选择dnsmasq这款轻量级的dns/dhcp软件。

配置/etc/dnsmasq.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
no-resolv
no-poll
server=223.5.5.5
server=223.6.6.6
log-queries
log-dhcp
log-facility=/var/log/dnsmasq
# conf-dir=/etc/dnsmasq.d
# Google and Youtube
server=/.google.com/127.0.0.1# 5353
server=/.google.com.hk/127.0.0.1# 5353
server=/.gstatic.com/127.0.0.1# 5353
server=/.ggpht.com/127.0.0.1# 5353
server=/.googleusercontent.com/127.0.0.1# 5353
server=/.appspot.com/127.0.0.1# 5353
server=/.googlecode.com/127.0.0.1# 5353
server=/.googleapis.com/127.0.0.1# 5353
server=/.gmail.com/127.0.0.1# 5353
server=/.google-analytics.com/127.0.0.1# 5353
server=/.youtube.com/127.0.0.1# 5353
server=/.googlevideo.com/127.0.0.1# 5353
server=/.youtube-nocookie.com/127.0.0.1# 5353
server=/.ytimg.com/127.0.0.1# 5353
server=/.blogspot.com/127.0.0.1# 5353
server=/.blogger.com/127.0.0.1# 5353
# FaceBook
server=/.facebook.com/127.0.0.1# 5353
server=/.thefacebook.com/127.0.0.1# 5353
server=/.facebook.net/127.0.0.1# 5353
server=/.fbcdn.net/127.0.0.1# 5353
server=/.akamaihd.net/127.0.0.1# 5353
# Twitter
server=/.twitter.com/127.0.0.1# 5353
server=/.t.co/127.0.0.1# 5353
server=/.bitly.com/127.0.0.1# 5353
server=/.twimg.com/127.0.0.1# 5353
server=/.tinypic.com/127.0.0.1# 5353
server=/.yfrog.com/127.0.0.1# 5353

指定的域名解析请求转发给本地5353端口,再通过socat建立的UDP转发通道来完成解析。

github上面有人专门维护了一份域名列表

如果您觉得这篇文章对您有帮助,不妨支持我一下!