DNS劫持:为什么你应该手动编辑YML?

常见的科学上网姿势是购买一个机场,机场会给你一个订阅链接,将这个链接填入客户端(例如Clash)后,客户端会通过订阅链接下载一个配置文件到本地。在新手阶段,大家更关注的是节点(Proxies)和规则(Rules),这两者共同决定了不同目标网站的流量路径。

直到遭遇DNS劫持。

常见的节点类型包括ss、vmess、http,它们都支持流量加密和伪装,但节点地址都是以域名的形式表达的,那么就涉及到域名解析(DNS)。为了表述方便,将全过程简化如下:

客户端A----DNS1----|墙GFW|----节点M----DNS2----目标服务器X

一般来说,M是“看上去”身份合法的,GFW会允许A正常解析M的IP地址并连接到M。而一旦到了M,剩下的问题就不大了,M负责联系X,并将加密、混淆后的数据传给A。

但某些情况下,GFW会“破获”某些机场使用的节点M们,或者某段时间检测到某个M流量突增,一看传输内容都是混淆后的不知所云的数据,再一检测又发现它并不是一个正常的网站,就有可能将它列入“嫌疑人名单”。那么为了阻止A连接M,通常采取的措施之一就是DNS劫持。

在“客户端A----DNS1----|墙GFW|----节点M----DNS2----目标服务器X”链条中,前两个是在GFW的“势力范围”。很明显,DNS劫持将在“DNS1”这一步来实现。

例如,某个节点的域名是xeqdfik3.abc.top,它正确的解析地址应该是:67.229.123.123(位于洛杉矶)。当GFW怀疑它时,就会强行在“DNS1”这一步,将xeqdfik3.abc.top的解析地址劫持为:221.228.32.13(位于国内某省某ISP数据中心),那么A就连不上真正的M,整个数据链当然就被破坏了。不仅如此,该数据中心还会不断收到你的请求“给我转接网站X!我的认证密钥是********!”这就太尴尬了。

所以,我们需要仔细研究一下配置文件中的DNS相关设置,对它进行一番必要的改造。这个配置文件,就是通过订阅链接下载下来的文件,对于Clash来说通常后缀名是YML。

1、dns段

dns段以“dns:”开头,通常位于配置文件的靠前位置。需要重点关注其中的“default-nameserver”与“nameserver”配置项。其中nameserver是Clash日常使用的主要设置。

如果仔细看过多家机场的配置文件,就会发现一个惊人的事实:default-nameserver与nameserver中,大量充斥着这样一些IP:

223.5.5.5(阿里)、223.6.6.6(阿里)、119.28.28.28(腾讯)、119.29.29.29(腾讯)、120.53.53.53(腾讯)、180.184.1.1(字节跳动)……

出于“保护”你的需要,它们都会将一些“怀疑对象域名”的DNS解析劫持到错误的地址,让你无法访问,或干脆跳到反诈网站首页,甚至可以让你直接跳转到新浪。你的亲密战友“M”很可能不幸被列入其中。

那么,是否将nameserver换成可靠的DNS,例如:8.8.8.8(Google)、1.1.1.1(Cloudflare),就一定能找到M了呢?未必。使用可靠的DNS当然会返回正确的解析地址,但这个正确的结果能不能递到你手上就难说了。因为默认情况下,整个DNS查询过程对于ISP来说仍然是透明的,GFW/ISP知道你是谁,也知道你在什么时候向8.8.8.8提出了一个域名M的解析请求,这仍然会暴露整个“不法行径”。同时,由于整个交易地点是GFW/ISP的主场,它可以强行改变结果,仿佛戴上了一枚“现实戒指”。打个容易理解的比方:

你在街头想买点毒品,根据配置文件,你找的卖方(DNS)是223.5.5.5,它和你完成了交易,但卖给你的是假货,你回家发现这粉一点儿劲都没有————这就是你使用默认配置文件时发生的事情。

第二次你聪明了,你修改了配置文件,找到了正确的卖方(DNS)8.8.8.8,它和你完成了交易,它拿出来的也是真货,但全程有一个警察站在旁边,他“啪”地一下打掉了8.8.8.8给你的真货(解析结果数据包),然后反手塞给你一包普通面粉。————这就是透明代理下发生的事情。

所以,我们需要用到DoH(DNS over Https)。将nameserver的表达形式从IP改成一个https链接,如下所示:

  nameserver:
    - https://dns.google/dns-query    # Google
    - https://1.1.1.1/dns-query       # Cloudflare

这就把默认情况下通过53端口与DNS的明文通信,变成了通过443端口与DNS的Https加密通信。虽然整个交易仍然在GFW/ISP的主场进行,但现场多了一只牢不可破的保险箱,你把解析请求放入保险箱,而DNS从保险箱里读取请求,并把解析结果也放进保险箱,你最后从保险箱中拿到结果————整个过程是加密的。从理论上讲,你们交易的可能是“毒品”,也有可能是完全无害的两只苹果,这就让GFW/ISP的干预失去了“法理基础”。

好了,经过千辛万苦,客户端A终于知道了M的真实地址,下面可以愉快地通信了。

别急,在clash的安卓端,在dns:段还有一个单独的设置:clash-for-android:

clash-for-android:
  append-system-dns: false

顾名思义,它的意思是,在DNS解析的时候是不是要加上系统(指的是安卓系统)自身的DNS啊?

如果是国内品牌的安卓手机,那当然是信不过的;那如果是类似三星这样的国外品牌手机呢?其实也一样,因为在默认情况下,安卓系统会从ISP那里获取DNS,通常是你所在当地的、延迟低的DNS,以保障网络的通畅。所以,只要你用的是国内的ISP,那都是不值得托付的啊!所以这一项必须是false。

2、hosts段

对于机场提供的M们来说,你不需要了解它们指向的具体IP,因为很可能为了应对诡谲多变的网络环境,这些IP是会变化的,通过前文的措施进行可靠的DNS解析就可以了。

那么还有一种节点,它是自建的,一般情况下,它的IP是固定的————这时候就可以采用更简单粗暴的方法:直接指定hosts。

在配置文件中,hosts:段与dns:段平行,都属于最上一级。格式如下:

hosts:
  "m01.abc.top": 152.22.10.10
  "m02.abc.top": 69.13.125.105

这样一来,啥DNS都不需要了,Clash将直接把域名替换成IP地址。

3、解决了DNS劫持并不是万能的

需要补充说明的是,前文都是围绕着如何把M的域名转换为真实的IP地址以建立连接,但对域名的DNS污染或劫持,只是GFW可能采用的部分手段,并非全部手段。

GFW有能力直接屏蔽IP,这件事如果发生,那么M除了更换IP之外别无它法。幸好,GFW并不会滥用这个终极杀招,因为IP资源是有限的,某个IP也未必永远提供着“不良内容”,它有可能会被转手,也可能因为网络调整而变化。

为了防止这件事发生,客户端A在和M接上头之后,如何进行隐蔽的通讯,减少M“毒贩”身份的暴露,也是有讲究的。

4、tls加密

常用的节点类型中,ss默认使用的是密码加密,也可以通过插件,再加上一层https,实现tls加密。而vmess和http原生就支持tls加密。

但必须注意的是,我们需要在配置文件中指定这一点,才能强制它验证https证书和使用tls加密,以进一步确保数据的安全:

    tls: true
    skip-cert-verify: false

当然,在这种设置下,要确保M的https证书保持更新。可以使用自动续期脚本来实现这一点。

这样一来,客户端A与M之间传递的数据,将全部加密,某种程度上保护了M的安全,延长了M的“生命周期”。

5、客户端设置

配置文件经过上述的调整,已经可以满足安全要求。但在安卓版的clash客户端上,还有一些需要调整的地方。

首先在“设置”-“覆写”-“DNS”-“策略”中,默认是“使用内置”,建议改为“强制启用”。这将强行使用配置文件中的设置,而不使用安卓系统内置的DNS服务。

经过测试发现,移动运营商(联通、移动、电信在手机端)可能会不支持DoH,导致nameserver中的设置失效,其结果就是导致所有的M们都失联。这时候,Fallback Name Server的设置就派上了用场。在“设置”-“覆写”-“DNS”-“Fallback Name Server”中,添加8.8.8.8和1.1.1.1。当DoH不管用的时候,就会切换到这两个备用DNS。

“覆写”功能是比较方便的,它让你不用每次都手动修改订阅链接下载下来的配置文件,而是用一套覆写规则自动覆盖相关条目。在“DNS”设置下,还可以参考前文,手动设置nameserver、添加Name Server策略等,此处不再赘述。