DEV Community

Metalage303
Metalage303

Posted on

翻墙 - DNS污染的原理以及应对策略

DNS以及DNS污染的成因(dnscrypt-proxy + dnsmasq方案)

我们都知道,互联网上的主机之间依靠数字IP地址进行通讯,但人类对这种毫无意义的纯数字的IP很不敏感,因为太难记了。于是就有了 “域名” 这个概念,它由具有一定意义的英文字母组成,比如qq.com。现在问题来了,人类能读懂的域名机器不懂,机器懂的数字IP人类又记不住,那就需要一个媒介将两者的关系绑定起来,这个媒介就叫做DNS服务器。

当我们在浏览器输入qq.com的时候,电脑并不会立刻和腾讯服务器连接上,而是先询问DNS服务器(请注意,所有的DNS服务器自身一定是以数字IP示人),请告诉我qq.com的IP地址是多少,DNS服务器回答:125.39.52.26,有了这个IP,我们才能正常浏览qq.com。

以上是大部分“正常国家”的互联网访问模式,但别忘了我们是有中国特色的社会主义国家,注定有些地方会比较有“特色”,这就是所谓的GFW. 而域名污染则是GFW其中的一个重要功能之一,那什么又是域名污染呢?当我们在浏览器输入google.com的时候,这个字符串首先会传递到你的宽带服务商为你指定的默认DNS服务器里,然后查询google.com对应的IP,GFW在这一步做了手脚,它会返回一个虚假的google.com的IP地址,导致你的浏览器无法正常与google服务器连接。

也许你曾经听说,DNS服务器好像是可以自定义设置的,那咱们不用宽带商提供给我的默认DNS服务器,而是自己设一台可信的DNS服务器不就得了,比如google公司本身也提供DNS服务,其DNS服务器地址是8.8.8.8,那我在Windows的网卡设置里将DNS服务器设为8.8.8.8不就万事大吉了?

没错,曾经有一段时间这个想法是可行的,但是很快8.8.8.8这个IP就被GFW封锁了。再后来,GFW想出了一个更卑鄙的方法,那就是不直接封杀这个IP,而是弄一台假的8.8.8.8!当你ping 8.8.8.8的时候,它确实是通的,让你产生一种错觉,既然它是通的,那么由它解析回来的结果也是可信赖的吧。如果你真的这么想,那说明你还是图样图森破了,这些假的域名服务器会为你返回各种各样稀奇古怪的IP地址,并且无一例外,全是假的。

那咱们的方案又是如何做到防止域名污染的呢?

3、dnscrypt-proxy 与 dnsmasq
首先是摒弃宽带服务器给你安排的默认DNS服务器,用自己搭建的DNS服务器取而代之,本文选择dnscrypt-proxy + dnsmasq 作为在本机搭建DNS服务器的方案。

dnscrypt-proxy是什么?首先它是一个搭建DNS服务器的程序,但它也并非仅仅是一个程序这么简单,它还是一个为了防止域名污染公益项目。由OpenDNS主导(思科公司的子公司),它在全球部署了许多可靠的DNS服务器,彼此间通过加密通道传输信息。

利用dnscrypt做域名解析好处有二:首先,作为一个不掺杂任何政治目的的公益项目,从dnscrypt解析回来的IP可以认为是真实可靠的,而不是杜撰出来的虚假IP。其次,它的数据传输相对安全,外人并不知道我在浏览哪个网站。

dnsmasq

那么dnsmasq又是什么呢?它也是一个搭建DNS服务器的程序,它不仅可以用来搭建DNS服务器,也可以用来搭建DHCP服务器。那么你可能会问,既然dnscrypt已经能提供足够强大的DNS服务了,那我们就直接用它做DNS服务器就行了,为什么还需要多来一个dnsmasq呢?表面上看起来后者似乎有些多余。

答案是访问速度问题,因为我们在访问国内网站的时候,没必要劳师动众地把dnscrypt祭出来,它的服务器毕竟在国外,域名解析速度比较慢。在访问国内网站的时候,我们随便找一个国内的公众DNS服务器就行了,著名的114,阿里,百度,清华,这些都能提供DNS服务,虽然他们返回的国外域名的IP地址肯定是有问题的,但是对于国内域名,一般来说还算靠谱。关键是在国内访问他们的速度比dnscrypt快得多。

于是,我们在本机就搭建了两个DNS服务器,其目的是为了实现:

所有域名,首先都发送到dnsmasq通道,如果是国内域名,dnsmasq调用上层国内DNS服务器(如114,阿里),返回国内域名所对应的IP,达到高速解析的效果。

如果dnsmasq发现是国外域名,则放弃解析,转交给dnscrypt,通过加密通道访问其部署在全球的可靠的DNS服务器,返回没有污染的、真实的IP。

4、智能识别国内/国外域名
新的问题又来了:dnsmasq 怎么知道哪些域名是国内域名,哪些又是国外域名呢?

这就要说到gfwlist这个项目了,它将那些被GFW列入黑名单的国外域名收集起来,导成一个文件供大家下载。

但 dnsmasq 是不能直接读懂这份文件,所以咱们还需要 dnsmasq-gfwlist 这个Python写的程序,它的作用就是将原始的gfwlist文件转换为dnsmasq能读懂的格式。

好了,现在dnsmasq终于能知道哪些域名是国内域名,哪些域名是国外域名了,基本实现了智能域名解析的目的。

域名解析这一块基本算是完成了,接下来还要解决流量问题。首先,机场给我们的流量是有限的,其次,不是所有流量都需要梯子,否则将严重影响访问速度。下一步我们要实现的是:国外网站走代理服务器,国内网站直连就好。

5、使用IPSET 对不同的流量进行定向路由

这时候ipset登场了,这东东又是干什么的?

ipset是linux系统下路由管理工具iptables的扩展,它允许创建一次匹配整个“地址”集的防火墙规则。这个官方解释挺拗口的,不容易理解,不急,先看dnsmasq的gfwlist文件,里面由几千个这样的内容组成:

server=/instagram.com/127.0.0.1#30053 这一行是关于DNS的,表示将instagram.com这个域名解析任务转交给本机30053端口去完成(30053就是dnscrypt)

ipset=/instagram.com/gfwlist
这一行与DNS解析无关,而是将instagram.com这个域名解析后的地址放到ipset的gfwlist这张表里去

第一步,我们需要用ipset命令创建了一张表(你也可以理解为一个集合),名字叫gfwlist,请注意,这个表与上面说的那个"gfwlist文件" 名字是一样的,但性质完全不同。"gfwlist文件"存放的是国外域名,或者说GFW黑名单,而这里的"gfwlist表"存放的是这些域名所对应的IP地址。

那么,这个IP地址集合又有啥用呢?

我们知道,在Linux系统中,网络流量是可以被分流的(路由),通常使用iptables命令去完成,通过这个命令,我们可以实现定向路由:

凡不在gfwlist中的IP,走直连通道

凡被收入gfwlist中的IP,走翻墙通道

总结
最后再总结一下完整流程:

用户输入一个域名

dnsmasq介入,开始对域名进行解析

dnsmasq通过gfwlist文件做两件事:

不在gfwlist中的域名,自己解析,在gfwlist的域名,交给dnscrypt解析
在gfwlist中的域名,将解析后的IP地址,保存在ipset的gfwlist表(集合)中
iptables 对数据进行分流(定向路由),凡在gfwlist集合中的流量,走翻墙通道,否则走直连通道。

基本架构:
环境说明:本文运行环境是Centos 8.2, 官网:https://www.centos.org/

可以在官网下载ISO映像,将映像刻录到U盘启动,在实体机或虚拟机安装皆可。

本节需要配置两个DNS服务:

dnsmasq 为主DNS服务器,负责国内域名解析,通过阿里DNS获得IP,一旦发现国外域名,转交给dnscrypt-proxy处理

dnscrypt-proxy 为辅DNS服务器,负责加密传输,获取真实的境外IP

首先安装相关依赖包、工具:

dnf install epel-release -y
dnf install git -y
dnf install -y gcc gettext autoconf libtool automake make pcre-devel asciidoc xmlto udns-devel c-ares-devel libev-devel libsodium-devel mbedtls-devel net-tools wget bind-utils nano
Enter fullscreen mode Exit fullscreen mode

2、配置 dnscrypt-proxy:
使用dnf安装dnscrypt-proxy:

dnf install -y dnscrypt-proxy
修改dnscrypt-proxy主配置文件:

nano /etc/dnscrypt-proxy/dnscrypt-proxy.toml
注意这行, 确保中括号里面不要有任何内容即可:

listen_addresses = []
[存盘退出]
修改dnscrypt-proxy的socket配置文件:

nano /usr/lib/systemd/system/dnscrypt-proxy.socket
将Socket这一节整体替换为下面这样:

[Socket]
ListenStream=
istenDatagram=
ListenStream=127.0.0.1:30053
ListenStream=[::1]:30053
ListenDatagram=127.0.0.1:30053
ListenDatagram=[::1]:30053
[存盘退出]
文件都修改完毕后,执行以下命令让他们开机自启:

systemctl start dnscrypt-proxy.socket

systemctl enable dnscrypt-proxy.socket
systemctl start dnscrypt-proxy.service
systemctl enable dnscrypt-proxy.service
3、配置 dnsmasq:
使用dnf安装dnsmasq

dnf install epel-release -y
dnf install -y dnsmasq
systemctl start dnsmasq
systemctl enable dnsmasq
修改dnsmasq配置文件:

nano /etc/dnsmasq.conf
listen-address=127.0.0.1,192.168.1.1 (监控本机,以及内网IP地址)
expand-hosts (删掉这行前面的#号,即: 取消注释让其生效)
domain=rockage.lan (设置局域网域名)
server=223.5.5.5 (因为dnsmasq对应的是国内域名,因此我们采用阿里DNS)
server=223.6.6.6
address=/rockage.lan/192.168.1.1 (将域名与IP绑定)
dhcp-range=192.168.1.50,192.168.1.100,12h (为局域网的机器自动分配从.50到.100的IP地址)
[存盘退出]
dnsmasq 配置文件语法检查, 如果显示Error表示上述修改内容有误, 请仔细检查:

dnsmasq --test
设置dnsmasq和本机dns的关联

/etc/resolv.conf 文件是本机DNS的设置文件, 由本地守护程序(NetworkManager)维护,因此用户对这个文件所做的任何更改, 在系统重启之后都将被覆盖还原。解决这个问题的方法是给它加一把锁, 防止系统进程对它进行自动修改

首先解锁:

chattr -i /etc/resolv.conf
解锁之后就可以修改了:

nano /etc/resolv.conf
删除这个文件的所有nameserver部分, 或者在前面加#号注释, 只留一行:

nameserver 127.0.0.1 (将nameserver指向本机)
[存盘退出]
编辑完成后需要加锁, 以防被系统进程自动修改:

chattr +i /etc/resolv.conf
查看一下加锁状态: (PS: 下次如需修改此文件,需先解锁再编辑 )

lsattr /etc/resolv.conf
接下来, 打通dnsmasq和本机预定义host的关联: 本机的 /etc/hosts 文件预先存储了一些IP和域名的对应关系,需要将他们指向dnsmasq

nano /etc/hosts
在文件尾部加一行:
127.0.0.1 dnsmasq
[存盘退出]

  • 尽管不是必须, 但鉴于修改内容较多,建议重启一次机器:

bash
reboot
测试
在测试之前首先安装测试工具Dig:

dnf install bind-utils
输入以下命令测试:

dig -x rockage.lan
如果回显:[SERVER: 127.0.0.1#53(127.0.0.1)] 表示OK

在局域网上随便找台机器,

IP设为192.168.1.X (X可以取值2-255), 
网关设为: 192.168.1.1
DNS 设为: 192.168.1.1
然后输入命令: ping rockage.lan 
如果回显:   **来自 192.168.1.1 的回复: 字节=32 时间<1ms TTL=64**,表示OK
现在针对国内的DNS服务就已经设置完毕了,但是如果ping google.com之类的域名,那么结果肯定还是假的.

本节参考文章: https://www.tecmint.com/setup-a-dns-dhcp-server-using-dnsmasq-on-centos-rhel/

4、配置 gfwlist
gfwlist 是一个公益项目, 它将被GFW所屏蔽的网站做了一个汇总, 并以文件方式提供给大家下载。

项目地址:https://github.com/gfwlist/gfwlist ,有兴趣的可以去围观点赞。

dnsmasq-gfwlist.py 是一个Python 程序,它将原始的gfwlist文件转换为dnsmasq能读懂的格式。

首先下载 dnsmasq-gfwlist.py:

wget https://gist.githubusercontent.com/lanceliao/85cd3fcf1303dba2498c/raw/7391429f8fdc5e3f4c82ba98b98767922b8bb473/dnsmasq-gfwlist.py
然后对文件进行一点修改:

nano dnsmasq-gfwlist.py
需要修改的内容不多,只需要将:

mydnsport = '1053' 
改为 
mydnsport = '30053' 
[存盘退出]
PS: 端口号30053是我们之前设置好的dnscrypt-proxy的端口。另外,因为这个文件是Python 2.7写的,注意不能用python 3.8来运行,如果此时你的系统还没有安装Python 2.7, 可以用dnf命令先安装:

dnf install -y python27
接着,开始编译执行dnsmasq-gfwlist.py:

python2.7 dnsmasq-gfwlist.py
注意:这一步需要在梯子已经建好的情况下,因为程序会自动去下载gfwlist文件,下载这个文件需要翻墙。

程序运行完毕,转换后的文件将自动保存在/etc/dnsmasq.d 里,先查看一下文件内容:

cat /etc/dnsmasq.d/gfwlist.conf
如果看到一大堆类似这样的内容:

server=/instagram.com/127.0.0.1#30053      
ipset=/instagram.com/gfwlist
表示文件没有问题,创建成功了。

接着,还需要将它导入dnsmasq,编辑dnsmasq设置文件:

# nano /etc/dnsmasq.conf 
查找 "conf-dir=" 
默认的应该是这样:
conf-dir=/etc/dnsmasq.d,.rpmnew,.rpmsave,.rpmorig
或者这样:
conf-dir=/etc/dnsmasq.d 
只要保证conf-dir没有被注释,另外保证/etc/dnsmasq.d这个路径包含在设置清单内即可。
[存盘退出]
退出后,再做一次语法检查确保配置文件无误:

dnsmasq --test
最后重启dnsmasq并检查启动是否成功:

systemctl restart dnsmasq 
systemctl status dnsmasq
如果出现: dnsmasq: failed to create IPset control socket: Permission denied 这个错误,这个多半是由于selinux没有关闭造成的,现在关闭selinux:

nano /etc/selinux/config
将: SELINUX=enforcing
改为:SELINUX=disabled
[存盘退出]
修改selinux需要重启机器:

reboot
重启后,输入以下命令确认selinux已关闭:

sestatus
如果出现: failed to create listening socket for port 53: Address already in use

首先使用这个命令,查看一下到底是哪个程序占用了53端口:

systemctl list-sockets
如无意外,我猜大概率是跟 dnscrypt-proxy.socket 打架了,解决方法:

请严格按照我上面的dnscrypt-proxy安装方法去做,诚然,主运行端口留空这一点可能会引起大家的疑惑,但dnscrypt-proxy官方文档写得很清楚,socket方式就是这样设置的。

主要检查两个地方:

/etc/dnscrypt-proxy/dnscrypt-proxy.toml 文件中:listen_addresses = [] (这个中括号一定要为空)

/usr/lib/systemd/system/dnscrypt-proxy.socket 文件中:Socket 一节原封不动复制粘贴我的配置

如果显示:Unable to retrieve source [public-resolvers],这个问题倒不大,因为服务器在境外有时候会出现读取不了的情况,只需要重新restart一下dnscrypt-proxy服务,再用status查看一下,一般来说多刷几次就正常了。

检查完毕后,用 reboot 命令重启,重启后输入:

systemctl status dnscrypt-proxy
systemctl status dnsmasq 
如果两者都显示绿字而没有任何红字,表示设置成功了! 如果实在问题多多,就用这个命令仔细看看启动log,再具体分析:

journalctl -r -u dnscrypt-proxy.service 
5、设置 ipset
首先是建一个名为 "gfwlist" 的表:

ipset create gfwlist hash:net
注意这个gfwlist和上面我们说的gfwlist文件不是一回事, gfwlist 源文件通过转换后能够被dnsmasq识别,那么,通过dnsmasq 筛选出来的国外域名会转交给 dnscrypt-proxy 做加密解析,最终,这些解析后的IP存放在哪呢?就存放在现在咱们用ipset做的这个名叫gfwlist的表里了。

表建好之后,现在查看一下:

ipset list gfwlist
意料之中,目前表是空的,Members后面什么都没有。

现在输入:

nslookup qq.com
再输入:

ipset list gfwlist
啥变化也没有嘛,为何?因为这是一个国内网站,并没有被gfwlist收录

接着输入:

nslookup facebook.com
再输入:

ipset list gfwlist
神奇的事情发生了!内容有变化,Members后面跟了一个IP,这就对了,因为这是一个被gfwlist收录的域名,所以被ipset过滤了进来!你还可以多试几个网站,比如google.com、twitter.com、youtube.com 等等你懂的系列网站,你会发现Members列表会越来越长。

最终需要做到:

用nslookup命令查询一个国内域名,gfwlist表中的Members不增加;
用nslookup命令查询一个被GFW墙了的域名,gfwlist表中的Members增加其对应的IP地址。
恭喜,本文的所有目标你都实现了!
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
zhaojc profile image
zhaojc

真的假的,这么说我可以不用买vpn了?

Collapse
 
lxy523107 profile image
Lxy523107

[Socket]
ListenStream=
istenDatagram=
ListenStream=127.0.0.1:30053
ListenStream=[::1]:30053
ListenDatagram=127.0.0.1:30053
ListenDatagram=[::1]:30053 设置在哪里?我找了好几个版本都没有找到dnscrypt.socket

Collapse
 
xcjiang profile image
ted

8.8.8.8的dot还是能用的, 853端口没有被屏蔽