漫谈各种黑科技式 DNS 技术在代理环境中的应用

这篇文章目的是以非技术性例举方式,谈一谈各种 DNS 技术在代理环境中如何工作,主要集中在对 V2Ray 的使用上,因为最近 V2Ray 发布了一个 DNS 相关的功能:https://steemit.com/cn/@v2ray/dns,其用法很多,可以解决很多问题,但如果不理解其工作方式,就很难做出合理的配置。

本文所讨论的也不单纯是 DNS,还涉及 V2Ray 的部分工作原理,因为我们的讨论是依赖于使用 V2Ray 来对 DNS 流量做各种处理的。所以也希望读者看懂了文章中的例子后,可以举一反三,从而更好理解 V2Ray 的整体工作原理。

本文举例范围预计会包括桌面系统(Windows, macOS)、移动端(iOS, Android)、普通浏览器代理(系统代理)、全局代理(tun2socks),或许还会聊聊路由器上的透明代理。

桌面端环境

先从非常简单的例子说起,了解下大概的过程。

首先说一下不开任何代理软件,不做任何代理设置时,DNS 的工作方式:

下面假设在系统或浏览器中设置了 SOCKS 5(注意是 SOCKS 5,版本 5)代理服务器地址为:127.0.0.1:1086。

Windows 系统的用户需要注意,系统代理里直接设置 SOCKS 代理有可能用的不是 SOCKS 5,而是 SOCKS 4,SOCKS 4 是不支持远程 DNS 解析的,想要设置 SOCKS 5 的话要用 PAC 文件,PAC 文件内容可以这么写:

function FindProxyForURL(url, host) {
return "SOCKS5 127.0.0.1:1086; SOCKS 127.0.0.1:1086";
}

然后在本地开启一个 V2Ray,配置大概如下:

在这个基础上对比上面(S1)不设置代理的情况:

上面(S2)设置不涉及任何远程代理服务器,由本地的 V2Ray 充当代了理服务器,代理的 www.bilibili.com 请求是采用直连方式,也一样有向 114.114.114.114 发出了 DNS 请求。但可以看到在后一个例子发起 DNS 请求的是 V2Ray(而不是浏览器),而从 Bilibili 的服务器看来,跟它连接是 V2Ray(而不是浏览器)。

加了这么一层代理,看起来什么都没发生,只是换了角色,其实不然,角色这么一换,可做的事情就相当多了。拿 DNS 来说,原来是浏览器发起的 DNS 请求,我们没办法控制浏览器怎么去发这个 DNS 请求(你不可能去改浏览器的源代码自己编译一个来用吧?),但换成由 V2Ray 来发这个 DNS 请求后,我们就可以做很多事情。

比如稍微改一下上面(C1)的配置,在 Freedom oubound 中加入 "domainStrategy": "UseIP"

V2Ray 文档 中对 domainStrategy 的说明是这样的:

原本默认为 AsIs 的话,直接向域名发出连接就是说会用系统 DNS 来做 DNS 解析,但如果我们设置为 UseIP ,S2 中的步骤就会变成这样:

所以这里我们改了一个 domainStrategy 参数,就相当于覆盖了系统的 DNS,本来应该走 114.114.114.114(系统 DNS)的 DNS 请求现在走 223.5.5.5(V2Ray 内置 DNS)了。

上面例子都很简单,并没什么实际用处,但了解这些可以方便理解后续各种花式玩法。另外虽说简单,但也已经包括了几个需要关注的点。

比如在 S2 中是这么写的:

这里就注意了,我们设置的 SOCKS 代理需要浏览器(应用程序)发现了,浏览器(应用程序)才会去使用它。如果应用程序没发现我们设置的系统代理呢?或者浏览器(应用程序)无视了我们设置的系统代理,按正常的流程去做请求呢?

这里的关键点是,一般桌面操作系统(Windows, macOS)中的系统代理设置只是一个 可选 的连接方式。

如果设置了系统代理,一般的浏览器程序(Chrome, Firefox, Safari)默认都会发现并遵从这个设置。其它的一些应用程序就不一定会遵从这个设置了,比如 QQ 这样的程序,它默认应该是不会遵从这个设置的(它有自己的设置项),再比如打开命令行,输入以下命令:

curl 做的这个请求时是不会使用系统代理的。如果想让 curl 使用代理,可以这么设置一个环境变量, curl 会读取这个变量,从而得知代理的存在并使用它:

再比如上面场景中多次提到 DNS 请求相关内容时,用了 DNS 请求承载 DNS 请求的 UDP 流量 等多种说法,它们其实都是指同一个东西,只是在不同的上下文中用了不同的说法。

简单来说,从我们机器发出去的一个 DNS 请求会是这个样子的:

这是一个 IP 包,从某种角度来看可以说它是一个承载了 UDP 流量的 IP 包,其中 IP 头中包含了源地址(本机 IP)和目的地址(114.114.114.114),UDP 头中包含了源端口(随机)和目的端口(53),DNS 头及其请求内容中包含了请求要解析的域名(www.bilibili.com),要解析的 DNS 类型(A/AAAA)。

所谓在不同上下文用不同的说法,一般就是指在不同的时候我们关注不同信息,当表达流量从本机发送到 114.114.114.114,我们关注的是 IP 地址和端口,这时我们说承载 DNS 请求的 UDP 流量(因为 IP 地址和端口信息在 IP 头 和 UDP 头中),当我们只关注所要解析的域名 “www.bilibili.com” 时,会说 DNS 请求(因为这个信息在 DNS 头及其请求内容中)。更多的时候是混用,在这方面不会太严谨。

知道哪个信息包含在哪一部分的数据中,会对我们理解各种 DNS 技术的工作方式有很大帮助。

下面举一个实际使用中可能用上的例子,配置如下:

简单描述下上面配置:内置 DNS 用 8.8.8.8 做首选服务器,localhost 作备用,路由中首先来一条规则让 8.8.8.8 的流量一定走 proxy,匹配了 geosite:cn 中的域名的请求走 direct,如果没匹配任何规则,则走主 outbound,也即 outbounds 中的第一个,也即 proxy。

同样设置系统代理至 V2Ray 的 SOCKS inbound 127.0.0.1:1086,再来考虑浏览器做请求的过程,经过上面几个例子,可以看到很多步骤其实是一样的,所以后续例子中会简化一些:

在上面 S5 中,步骤 5 ~ 11 做了次 DNS 请求,采用代理转发 DNS 请求流量的方式,而在步骤 14 中,又说可以不解析域名,交给远端服务器来解析,这两者其实并不冲突。一般来说,前者的处理方式就是实实在在的 UDP 流量代理而已,后者一般叫作远程 DNS 解析。用了 IPIfNonMatch ,对域名做一次 DNS 解析要经过 5 ~ 11 这么多步骤,看起来效率很慢,但如果代理服务器不慢的话,这个过程一般是很快的,最重要的是 V2Ray 内置 DNS 对 DNS 结果有一个缓存,所以并不需要每次都去做 DNS 请求。不管怎么说,毕竟还是做了额外的事情,而且有可能涉及到一个 UDP over TCP 的代理请求,的确会相对地慢点。

另外要提到一点是,上面的配置中都在本地 SOCKS inbound 中用了:

在新的配置格式中这个流量嗅探的配置有了新的写法,后续会用这个写法:

"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}

这个是对流量进行域名嗅探,如果配置了,V2Ray 就会做嗅探操作,但上面的例子没有把这个操作过程列出来,是因为目前我们的场景是桌面系统 + 浏览器,在这个环境中,这个嗅探功能不会起额外的作用,浏览器会把域名交给 V2Ray,不需要 V2Ray 去嗅探。

如果看明白上面的上面几个举例,相信读者已经对 V2Ray 的工作方式有了大致的了解。一般来说,流量从 inbound 进入到 V2Ray,再进入到路由匹配过程,找到匹配到的 outbound,然后流量就给到这个 outbound 处理。V2Ray 虽然有多种 inbound,多种 outbound,以及很灵活的路由匹配规则,有时候会让人眼花缭乱,但这条流量流经的路线是默认的,没有特殊配置,流量都会按这个顺序在 V2Ray 内部传递。

V2Ray 内置 DNS

而 V2Ray 的内置 DNS 其实也只是一个很简单的功能模块,它为 V2Ray 内部其它模块提供 DNS 功能,它接受一个域名作为输入,返回一个 IP 列表作为输出。

直接拿 V2Ray 里面代码出来看,内置 DNS 功能对其它模块提供的接口是这样的,表示输入参数是域名,输出是 IP 列表以及一个错误信息,如果正常返回,错误信息会是空的,IP 列表可以同时有 IPv4 和 IPv6 地址:

LookupIP(domain string) ([]net.IP, error)

这就是从其它 V2Ray 模块的角度来看 V2Ray 内置 DNS 功能,至于内置 DNS 内部是否做域名分流,内部向哪个 DNS 服务器发出 DNS 请求,用何种方式发这个 DNS 请求,其它模块是管不着的,也完全不知情的。这样看内置 DNS 功能就很简单,其它模块,比如上面提到路由模块用内置 DNS 来解析域名后用 IP 再次进行匹配(或者后面会提到的 DNS outbound 模块),其实就是这样调用了内置 DNS:

从其它模块(外部)看 V2Ray 的内置 DNS 就这么简单的,那从内部看它呢?从内部看也是比较简单的,只要分清楚外部和内部各自负责的事情,各自所能得到的信息,就很好理解。

比如内置 DNS 支持 hosts,这个功能是怎么做的呢?按上面的描述,外部只传入一个域名,其它的管不了,那内置 DNS 里面随便返回什么样的结果,外部都不能有任何疑问,比如路由模块要查 www.google.com 的 IP,传它给内置 DNS,但如果内置 DNS 根本就不发任何 DNS 请求,不做任何额外处理,直接就返回一个 127.0.0.1 给路由模块,这时路由模块也不得有任何异议,就算很显示 127.0.0.1 不可能是 www.google.com 真正的 IP 地址。那样在内置 DNS 中配置一个 域名 -> IP 的列表,只要传进来的域名匹配了列表中的域名,就直接返回相应 IP,这就等效于 hosts 了。

再看内置 DNS 的 servers 一项,如果内置 DNS 这样配置:

路由模块传入 www.bilibili.com 这个域名,想要它的 IP 地址,内置 DNS 会从上至下按顺序,向 servers 里每个 DNS 服务器发 DNS 请求,一个一个地来,直到有结果返回,然后再把结果返回给路由模块。

从内置 DNS 向 servers 列表中的 DNS 服务器发出的 DNS 请求的流量,并不是从本机直接发到对应的服务器(localhost 除外),而是会通过 outbound 发出去,假如是 Freedom outbound,的确还是会从本机直接发到相应 DNS 服务器,但假如是一个 VMess outbound,DNS 请求的流量就会被代理到 VMess 代理服务器上,由代理服务器发到相应的 DNS 服务器。

内置 DNS 发出的请求的流量可能的流经线路:

使用哪个 outbound,取决于路由配置。

所以这里就很有趣了:

可是呢,考虑一下我们的配置以及要查询的这个域名,8.8.8.8 在列表第一项,要先用它来查,但我们要查的域名是 www.bilibili.com,一般来说是不太合适向 8.8.8.8 查这个域名。所以无论路由模块判断走 Freedom outbound 还是 VMess outbound,都不合适。因为走 Freedom outbound 发到 8.8.8.8,恐怕返回结果是假的,走 VMess outbound,恐怕返回结果是国外 CDN 的。

这里必须再插一段话,要考虑返回结果如何如何,我们还得考虑这个返回结果是用来干什么的,如果是我们当前的例子,是用来给路由模块做 IP 规则匹配的,那返回结果是不是假的并不重要,重要的是它是否能匹配到 direct 规则;如果返回结果用作其它目的,比如后面会说到的 DNS outbound,就又是另一个故事了~

回到主线,不管怎么,我们不想考虑太多东西,我们只想这个 DNS 请求能够合理地发出,很明显的一个选择就是让这个 www.bilibili.com 的 DNS 请求发到 223.5.5.5,且走 Freedom outbound。

那把 223.5.5.5 放到列表第一项如何?这是治标不治本,想想假如又有另一个 DNS 请求要发到 www.google.com 的,这个请求先发到 223.5.5.5 就又不太合适了。

V2Ray 当然已经考虑到这情况,有了所谓的 DNS 分流 功能:

这样配置内置 DNS,虽然 8.8.8.8 在第一,但因为要查询的域名 www.bilibili.com 匹配了第二项中的域名规则,内置 DNS 就会优先向第二个 DNS 服务器发出 DNS 请求。

这里有一个很容易忽略掉的过程,就是选择 outbound,我们用 DNS 分流功能选择了 DNS 服务器,还得在路由中配置合适的规则,来选合适的 outbound 向这个 DNS 服务器发出 DNS 请求流量。

就算 DNS 分流配置好了,如果路由规则配置不当,让 udp:223.5.5.5:53 的流量走了 proxy,就有可能发生上面第二个线路,返回的 IP 地址就有可能是 Bilibili 在国外 CDN 的 IP。(IP 结果是真的,只是…)

所以千万不能忘记,除了配置好 DNS 分流域名,还要为各个 DNS 服务器配置路由规则:

总结一下上面的,要正确配置 V2Ray 内置 DNS:

  1. 正确配置 servers 列表及域名分流(用域名信息来选 DNS 服务器)
  2. 正确为发出的 DNS 请求配置路由规则(用 DNS 服务器的地址信息以及流量的协议信息:IP,端口,tcp/udp)

V2Ray 内置 DNS 还有一个叫 clientIp 的项是什么用的?我个人不太建议去依赖这个选项,但如果你想知道它的作用,是这样的,假如上面例子中真的在路由规则上配置错了,出现了这个线路的情况:

clientIP 是 DNS 请求中可以带上的一个参数,它用来提示 DNS 服务器(223.5.5.5)你所在的地理位置,223.5.5.5 就知道你在哪,就 有可能 会返回一个合适你所在的地理位置的 CDN 的 IP(也即 Bilibili 国内 CDN 的 IP)。

关于内置 DNS,最后再说一说 localhost ,上面的描述不适用于 localhost,简单说,内置 DNS 在向 servers 列表中的 localhost 发 DNS 请求时,不会用任何 outbound 来发(甚至不用 Freedom outbound),而是直接从本机发出,就像任何其它程序做 DNS 请求那样,直接调用系统的 DNS API,用系统 DNS 中配置的 DNS 服务器。

V2Ray DNS Outbound

接下来会说一说 V2Ray 的 DNS outbound。因为我们上面的例子中所说的都是路由模块去调用内置 DNS,但路由模块仅仅是用它的结果来做规则匹配,而且是当配置了 IPIfNonMatch/IPOnDemand ,而且没有匹配任何域名规则的情况下,才用得到它,当下各种域名列表盛行,它的应用范围实际上是很窄的。

DNS outbound 可以说是一个把内置 DNS 的功能真正地开放出来,让各种程序、各种模块可以更加方便地使用得上内置 DNS 的一个功能。为什么要把内置 DNS 开放出来呢?很明显啊,因为现在它支持 DNS 分流 ,相信大部分读者都知道已经存在很多专门做 DNS 分流的工具,这已经说明 DNS 分流是一个很有用的功能,而 V2Ray 已经支持 DNS 分流了,可是外部用不了就等于没有,所以就得开放出来。

要说 DNS outbound 的配置,它目前就只有那么 3 (network, address, port)个配置项:

而这 3 个配置项其实都不是本文想要关注的点,下文只会稍微提一下。DNS outbound 最重要的功能是它的默认行为:对进来的 DNS 流量进行拦截、解析,以及对 A, AAAA 类型的 DNS 查询做重新转发。

其中最难理解的,恐怕就是 “拦截”、“重新转发” 两个词的具体意思。

这里可以回看上面的 F1,是这么描述一般 DNS 请求的:

对于进入到 DNS outbound 里面的流量,DNS outbound 会把这个流量当作是 DNS 请求,也就是说把它当作一块数据,这块数据里面包含了 DNS 请求相关的信息,包括但不限于:DNS 请求的域名,DNS 请求的记录类型;还有所有类型的 outbound 都能够得到的一些通常的信息,比如这个流量的目的地址,目的端口,所用传输协议等等,这些信息都是 DNS outbound 能够轻易得到的。

因为 DNS 请求里面的数据都是明文的,流经的中间设备、中间模块都看得到里面内容,那 DNS 请求的流量既然流经 DNS outbound,DNS outbound 理所当然也能看得到;而且返回数据的时候,就算 DNS outbound 把返回的数据删改了,或者索性自己凭空生成一个假的 DNS 回复返回给应用程序,应用程序也没办法知道真假。

这就是 DNS outbound 的工作原理。只不过它并不是凭空生成一个假的 DNS 回复,而是求助于内置 DNS 模块:

DNS outbound 的这种行为,实质上相当于 DNS 劫持,但说它是 DNS 劫持也不太对,因为是我们配置的,自愿让它 “劫持” 的嘛。

DNS outbound 本身的工作原理相信已经比较清晰了,它只是劫持了进来的 DNS 请求的流量,然后找内置 DNS 模块帮忙解析 DNS,然后假装啥事没发生,返回结果而已。

要应用它,重点在于要用什么样的方式,把 DNS 流量引导到 DNS outbound 里去。

对于桌面系统,如果用浏览器来访问网站,那么,前面也说了,SOCKS5 协议可以带上域名给远端解析,根本不需要本地去解析域名。但假如不是用浏览器的情况呢?比如运行一个 nslookup 命令,默认它也不会用系统设置的代理,它是直接向系统设置的 DNS 服务器发 DNS 请求,怎么把这个请求的流量引导到 V2Ray 的 DNS outbound 呢?

考虑下以下配置:

这个配置让 V2Ray 的 dokodemo-door inbound 监听在本地 53 端口,对于任何进来的流量,会因为我们的路由规则,而被送到 DNS outbound 处理,然后我们把系统 DNS 设置为 127.0.0.1,相当于说 V2Ray 的 dokodemo-door inbound 就是一个 DNS 服务器。

让 V2Ray 充当 DNS 服务器,设置系统 DNS 是其中一个把 DNS 流量引导到 DNS outbound 中的方式。但今天我们大部分的上网操作都是在浏览器上进行了,浏览器用了 SOCKS5 代理的话也不需要本地去解析 DNS,所以这个应用场景不常见。

DNS outbound 开放出来后,最重要的应用场景,是在移动端和路由器上。

在讨论移动端和路由器之前,先看看桌面系统上怎样可以做真正的全局代理。前面也说了,一般的浏览器代理,只代理浏览器的流量,要把 DNS 流量也代理了,就需要像上面那样更改系统 DNS 为 127.0.0.1,当我们需要真正的全局代理时(VPN 就是真正的全局代理),这种方便显然不合适,也不可能做得到真正全局代理。

tun2socks

下面介绍一种通过 tun2socks,能够把所有本机应用程序发出的流量都交给 V2Ray/SOCKS/Shadowsocks,从而达到全局 TCP/UDP 代理(对,只是 TCP/UDP,ICMP 等等是不支持的,全局性还是不比真正的 VPN)。

为此我特意写了一篇关于在 Windows 上如何使用 tun2socks 实现全局 TCP/UDP 流量代理的教程:

tun2socks 就是这么一种可以把所有应用程序的 TCP/UDP 流量都交给 V2Ray 的方式,当然其中包括 DNS 的流量,所以用了 tun2socks 后,我们甚至不需要更改系统的 DNS 设置,所有 DNS 请求的流量都能够被引导到 V2Ray,然后我们可以在 V2Ray 里加一条路由规则,识别出 DNS 的流量,把这些流量转给 DNS outbound:

配置做了简化,真拿来用的话要自己补上其它部分。配置中没有 inbound,因为我们所用的 tun2socks 软件:go-tun2socks 本身把 tun2socks 内置为 V2Ray 的一个 inbound 了。

go-tun2socks 是一个命令行工具,要使用的话还要自己创建 TUN 接口,配置路由表等,比较繁琐,这里还有另一个应用叫 Mellow ,对 go-tun2socks 进行了包装,可以自动完成这些繁琐的步骤,实际使用起来的效果就相当于 Proxifier、SSTap、Surge for Mac 等软件,可以把所有流量都代理了:

说白了,目的还是之前所说的,找到一种方法把 DNS 请求的流量引导到 DNS outbound 中。

移动端环境

上面提 tun2socks,是因为在移动端上,要代理所有 app 的流量,tun2socks 是一种必须的手段,它们跟桌面系统上用 tun2socks 很相似,都是要把所有的 TCP/UDP 流量引导进 V2Ray,但也有不一样的地方,移动端系统一般都提供了特殊的方法,能够让指定的流量不受路由表基于 IP 地址的路由控制

在移动端系统方面,iOS 既可以像桌面系统用 tun2socks 那样把所有 TCP/UDP 流量引导到 V2Ray,也可以设置一个 HTTP 代理把 HTTP 请求代理到 V2Ray 的 HTTP inbound 里;而 Android,只能以 tun2socks 那样的方式把 TCP/UDP 流量引导进去。

iOS 的 HTTP/S 代理跟 SOCKS5 代理比较相似,应用程序不需要自己解析 DNS,可以把域名带上交给 V2Ray,然后如果请求匹配到代理的路由规则,这个域名就通过代理协议(VMess/Shadowsocks/SOCKS5)交给代理服务器自己去解析,所以本地不需要做任何 DNS 解析。问题是很明显这个只适用于 HTTP/S 请求(而且并不是所有的 HTTP/S 请求,貌似应用程序还必须要用 iOS 系统提供的 HTTP 库来做请求才会被代理到)。

再考虑到 Android 并不能很自然地支持 HTTP/S 或者 SOCKS5 代理,所以想要一个通用的解决方案,还是要从 TCP/UDP 流量入手。

假如你使用的 Android 客户端是 Kitsunebi,那大致上可以这么配置:

配置中的 inboundTag tun2socks 是必须的,虽然单凭 53 端口 和 network udp 大致达到识别出 UDP 流量的目的,但因为在 V2Ray 中 UDP 流量并不只从 tun2socks inbound 进来,还会从 内置 DNS 那边过来,如果不加上 inboundTag tun2socks 这个限制,内置 DNS 那边过来的流量就会被转到 DNS outbound,接着有可能又被转给 内置 DNS,造成环路。

通过前面桌面端上对 V2Ray 的 内置 DNS 和 DNS outbound 的说明,现在移动端也可以方便地拿到所有 TCP/UDP 流量,上面的方法都可以套用下来,就不继续说了。

大体上,从流量流向的角度来看,一般 DNS 处理方式也就 3 种:

  • 直接从本机发出 DNS 请求到目的 DNS 服务器
  • 通过代理把 DNS 请求发送到目的 DNS 服务器
  • 本机不向互联网发出 DNS 请求流量(由远端代理服务器来解析或者直接本地伪造 DNS 答复返回)

Fake DNS

虽然现在已经可以完全利用 V2Ray 的 DNS 功能模块对 DNS 的 UDP 流量做各种处理,可以对 DNS 请求按域名做分流,但仔细想想,分流时不管是发到 freedom outbound 直连还是发到 VMess outbound 代理,这些 DNS 请求终究是要以实际的流量形式从本地发送到互联网上,是否有方法可以在利用 tun2socks 获取所有 TCP/UDP 流量的同时,又可以像 HTTP/S 和 SOCKS5 代理那样,本地完全不发出 DNS 请求流量,只把域名交给代理服务器,让代理服务器自己去解析呢?

答案是有的,那就是 Fake DNS,Fake DNS 是有一个 RFC 的,某些使用 iOS 的读者可能也并不陌生,Surge 中的 force-remote-dns 就是利用了这种技术。

大致工作原理如下:

上面过程中,虽然 app 是发出了 DNS 请求流量,但这个流量被拦截下来了,并没有真正地发到互联网上;最终 V2Ray 也拿到了域名,如果这个域名匹配到代理规则,就会交到代理服务器来解析,也就是所谓的 远程 DNS 解析。

上面过程中,Fake DNS 模块如果没有对域名做过滤,对任何域名都返回伪造的 IP 地址,则可能会引起一些问题,有一种做法是在 Fake DNS 模块里内置一个域名列表,只对匹配到列表的域名返回 伪造 DNS 答复。

Fake DNS 不管是原理上还是实现上都是非常简单的,如果有兴趣可以看一下 这里的实现代码

DNS Fallback

接下来再说说一种叫做 DNS fallback 的技术(这个名字可能不太对),在代理服务器不支持 UDP 的时候,单纯的全局 TCP/UDP 流量代理 就会出问题了,因为要解析 DNS 就需要 UDP,服务器不支持 UDP 就没办法处理 DNS ,也就没办法处理所有域名请求了,DNS fallback 是一种可以强制让应用程序把 DNS 请求以 TCP 流量发送出去的技术。

需要说明的是,如果用了 V2Ray 的技术,或者 Fake DNS,那 DNS fallback 是没必要的,DNS fallback 也是一种处理 DNS 相关问题技术,这里只是想把这种技术也列举出来

众所周知一般的 DNS 请求都是用 UDP 发的,但 DNS 的 UDP 报文的大小会被限制在大概 512 字节,如果某个 DNS 答复很长,超过了这个限制,就不能通过 UDP 来传输了,为此 DNS 规范中提出,对于这种超过限制大小的 DNS 请求,DNS 服务器可以在答复中设置一个 flag(truncated),来告诉客户端这个答复太长,你不能用 UDP 来做这个 DNS 请求,请用 TCP,于是客户端就会用 TCP 来重新请求 DNS。

DNS fallback 正是利用了这一点,在 tun2socks 给过来的 UDP 流量中识别出 DNS 请求,然后伪造一个设置了 truncated flag 的答复返回给客户端,客户端就会转用 TCP 来做 DNS 请求了。

DNS fallback 的实现也是非常简单的,有兴趣可以看一下 这里的代码

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store