Skip to main content

Command Palette

Search for a command to run...

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

Published
3 min read

DNS以及DNS污染的成因

我们都知道,互联网上的主机之间依靠数字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地址,并且无一例外,全是假的。

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


dnscrypt-proxy 与 dnsmasq

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

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

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

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

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

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

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

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


智能识别国内/国外域名

新的问题又来了:dnsmasq 怎么知道哪些域名是国内域名,哪些又是国外域名呢?

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

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

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

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


使用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

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=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 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

[存盘退出]

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

测试

在测试之前首先安装测试工具Dig: dnf install bind-utils

输入以下命令测试: dig -x yourname.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 yourname.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/

配置 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地址。 恭喜,本文的所有目标你都实现了!

More from this blog

OpenVPN 极简版教程

OpenVPN是一款优秀用于创建虚拟私人网络的软件,但是由于其涉及了服务器证书、TLS密钥、防火墙等一堆衍生概念,因此设置显得比较复杂。本文化繁为简,仅以 “能连通” 这个最低要求,完整地展示了一遍OpenVPN的安装调试过程。万事开头难,在实现了连通的基础上再来探索更多的功能是一种比较好的学习路径。 一、概述 在安装openvpn的时候之前,大概说下它的结构,整个安装流程涉及以下4个部分: Server / Client 服务器端程序 Easyrsa 证书生成程序 Server端配置文...

Jun 19, 20244 min read
OpenVPN 极简版教程

Nftables 基础

1、关于Linux防火墙 在Linux系统里,防火墙并非只负责网络安全,凡是跟数据包相关的事务都由它负责,比如说局域网内共享上网,尽管听上去和网络安全无关,但也是通过防火墙命令来实现的。 我相信大家经常会见到一些诸如Netfilter、Iptables、Firewalld、nftables 这样的命令,他们都属于防火墙相关命令,那么这几个命令之间究竟是什么关系呢? Netfilter 、 Iptables/Nftables 、 Firewalld 四者关系: Netfilter:运行在Lin...

Jun 19, 20245 min read
Nftables 基础

随笔 - 今日现状的一些思考

引言 自2012年习近平大开倒车以后,短短几年间舆论环境的恶化真的是就让所有人噤若寒蝉。 在胡温执政时期,尽管铁拳无时不刻在暗处提醒着你它的存在,但在明面上,整个民间环境还是偏向自由的。 现在的情况跟胡温时代有着本质的不同,从前自由派是一种尽管并不全面但仍算得上是主流的社会共识,很大程度上民间是认同这种声音的,反对的声音只源自政府,充其量是一场民间与政府的交锋。但是现在极左粉红却一家独大,将所有不同的声音都淹没了,整个舆论环境变成一潭让人绝望的死水。这俨然已经不是民间 Vs 政府这么简单了。粉红...

Mar 9, 20221 min read

vue / golang & linux(centos)

6 posts