某些软件可以建立虚拟网络接口,在 macOS 上执行 ifconfig 可以看到 utun10,其 ip 地址是 198.18.0.1,Wi-Fi 的 dns 设置也被更改成 198.18.0.2.
但是 198.18.0.2 是什么?它并不是本机某网口的地址。
越研究发现越神奇。
首先使用 route -n get 198.18.0.2 结果如下:
route to: 198.18.0.2
destination: 198.18.0.2
gateway: 198.18.0.1
interface: utun10

但是只要不是 198.18.0.1 和 198.18.0.2 (有时候 0.3 也可能是)则结果如下:
route to: 198.18.0.4
destination: 128.0.0.0
mask: 128.0.0.0
gateway: 198.18.0.1
interface: utun10

route to: 198.18.1.2
destination: 128.0.0.0
mask: 128.0.0.0
gateway: 198.18.0.1
interface: utun10

看到这个 destination 应该是 CIDR 128.0.0.0/1 起作用了。
查看路由表:
~ netstat -rn
Routing tables

Internet:
Destination Gateway Flags Netif Expire
default 192.168.31.1 UGScg en1
default link#28 UCSIg utun3
1 198.18.0.1 UGSc utun10
2/7 198.18.0.1 UGSc utun10
4/6 198.18.0.1 UGSc utun10
8/5 198.18.0.1 UGSc utun10
16/4 198.18.0.1 UGSc utun10
32/3 198.18.0.1 UGSc utun10
64/2 198.18.0.1 UGSc utun10
100.64/10 link#28 UCS utun3
100.100.100.100/32 link#28 UCS utun3
100.124.11.45 100.124.11.45 UH utun3
127 127.0.0.1 UCS lo0
127.0.0.1 127.0.0.1 UH lo0
128.0/1 198.18.0.1 UGSc utun10
169.254 link#13 UCS en1 !
169.254.12.71 50:ed:3c:3:2:c UHLSW en1 !
169.254.35.85 60:dd:8e:69:5c:d0 UHLSW en1 !
192.168.31 link#13 UCS en1 !
192.168.31.1/32 link#13 UCS en1 !
192.168.31.1 88:c3:97:c8:2:b6 UHLWIir en1 1169
192.168.31.24 4:cf:8c:29:a4:97 UHLWI en1 1157
192.168.31.59 c:7a:15:c1:ad:cc UHLWI en1 !
192.168.31.73 6:6e:f7:98:f3:4b UHLWI en1 349
192.168.31.80 84:c5:a6:9e:4:3 UHLWI en1 601
192.168.31.129 86:11:14:df:18:ce UHLWI en1 555
192.168.31.144 56:f9:cf:1e:97:7d UHLWI en1 95
192.168.31.166/32 link#13 UCS en1 !
192.168.31.189 66:17:81:34:32:5c UHLWI en1 1112
192.168.31.199 60:dd:8e:69:5c:d0 UHLWIi en1 1160
192.168.31.213 50:ed:3c:3:2:c UHLWIi en1 1189
192.168.31.214 2:69:d6:3d:12:3d UHLWI en1 1179
192.168.31.222 f8:d0:27:54:e4:84 UHLWI en1 1200
192.168.31.255 ff:ff:ff:ff:ff:ff UHLWbI en1 !
198.18.0.1 198.18.0.1 UH utun10
224.0.0/4 link#13 UmCS en1 !
224.0.0/4 link#28 UmCSI utun3
224.0.0.251 1:0:5e:0:0:fb UHmLWI en1
239.255.255.250 1:0:5e:7f:ff:fa UHmLWI en1
255.255.255.255/32 link#13 UCS en1 !
255.255.255.255/32 link#28 UCSI utun3

里面关于 utun10 的几条解释:
2/7 地址范围: 2.0.0.0 到 2.255.255.255
4/6 地址范围: 4.0.0.0 到 7.255.255.255
8/5 地址范围: 8.0.0.0 到 15.255.255.255
16/4 地址范围: 16.0.0.0 到 31.255.255.255
32/3 地址范围: 32.0.0.0 到 63.255.255.255
64/2 地址范围: 64.0.0.0 到 127.255.255.255
128.0/1 地址范围: 128.0.0.0 到 255.255.255.255
198.18.0.1 就表示它自己

所以除了 198.18.0.1 之外其余的都应该解析成 128.0.0.1 才对啊。那 198.18.0.2 为啥也解析成自己了呢?
我的理解: 跑的服务程序可以监听本地网口, 每个网口有对应的 ip 地址,如果监听 0.0.0.0 则表示所有网口,如果监听 127.0.0.1 则表示监听 lo0 ,如果监听 192.168.31.166 (我电脑 en0 地址)则表示监听 en0 网口,任何发送到这个网口的数据(当然要指定好端口号)都会被转发到这个服务程序。
但查看 ifconfig 则看不到 198.18.0.2 是属于哪一个接口的,而它是 utun10(198.18.0.1)的一个子网 ip 。当开启虚拟网口后(软件的增强模式透明模式) dns 被改成 198.18.0.2 是为什么?使用 dig 程序测试也是没问题的:
~ dig @198.18.0.2 -p 53 baidu.com
~ dig @198.18.0.2 -p 53 google.com
~ dig @198.18.0.2 -p 53 fb.com

结果都是 fakeip 。
所以,我哪里理解错了吗?为什么会有 198.18.0.2 ?

写一个程序来测试:javascriptconst http = require('http');const server = http.createServer((req, res) => {res.setHeader('Access-Control-Allow-Origin', '*');res.setHeader('Content-Type', 'text/plain');res.end('Hello');});const ifrq = "198.18.0.2";server.listen(3000, ifrq, () => {console.log(`Server running at ${ifrq}:3000/`);});开启增强模式后 如果 ifrq 是 198.18.0.1 则可以正常绑定到这个地址,打开这个地址 3000 端口后正常响应。但如果将 ifrq 改成 198.18.0.2 ,则服务起不了 Error: listen EADDRNOTAVAIL: address not available 198.18.0.2:3000.

装了 ClashX ?开了增强模式?

对,故意没有说这几个单词的,容易触发关键字,被移动到某节点下,不会出现在首页。

#2 clash 的增强模式会新建一个虚拟网卡接管本机的网络,你发的这个 IP 就是虚拟网络的内网

为什么不是 198.18.0.1 而是 0.2 ? 通过 dig 命令可以测试到 198.18.0.1 也是可以响应 dns 请求的( listen: 0.0.0.0:53 起作用)。为什么要用 0.2 这个子网?

虚拟网卡不必在意。

en.wikipedia.org/wiki/Reserved_IP_addresses198.18.0.0/15 是保留地址

我写了一个程序来测试:const http = require('http');const server = http.createServer((req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Content-Type', 'text/plain'); res.end('Hello');});const ifrq = "198.18.0.2";server.listen(3000, ifrq, () => { console.log(`Server running at ${ifrq}:3000/`);});开启增强模式后 如果 ifrq 是 198.18.0.1 则可以正常绑定到这个地址,打开这个地址 3000 端口后正常响应。但如果将 ifrq 改成 198.18.0.2 ,则服务起不了 Error: listen EADDRNOTAVAIL: address not available 198.18.0.2:3000

dns server 没有 listen on 198.18.0.2198.18.0.2 走的也是 128.0/1 这条路由,被当成普通流量送往了 utun10utun10 是虚拟网关,它检查到 dst ip 是 198.18.0.2 之后,认为这是一个 dns 请求,给与 fake ip dns 响应

一种可能是 redirect ;一种是独立的协议栈,可以理解为你通过 utun 发给了另一台虚拟主机。

surge/clash 没有选择直接 bind 198.18.0.1:53 ,应当是出于兼容性考虑,如果本机有其他程序 bind 0.0.0.0:53 ,会导致一方失败。

#8 "198.18.0.2 走的也是 128.0/1 这条路由,被当成普通流量送往了 utun10"第一:使用 route -n get 198.18.0.2 的结果为什么和 198.18.0.4 有差别呢?第二:我搜了下 clash-meta 的源码,没有找到 198.18.0.2 的关键字,clashxpro 和 surge 没有开源,所以也看不到具体的实现。所以 "检查到 dst ip 是 198.18.0.2 之后,认为这是一个 dns 请求" 可能是这样的,但并不能确定。另外,谢谢回复。

github.com/Dreamacro/clash/wiki/Configuration/a1d5c350643b51500cbdc78ab249f73aacb4cf2c#fake-ipfake ip 减少一次 dns query

clash-meta 相关代码在这里: github.com/MetaCubeX/Clash.Meta/blob/53f9e1ee7104473da2b4ff5da29965563084482d/listener/sing_tun/server.go#L145取了 options.Inet4Address 的下一位地址作为 DNS 地址。options.Inet4Address 来源在这里: github.com/MetaCubeX/Clash.Meta/blob/Alpha/config/config.go#L1343是从 fake-ip-range 配置项取出来的第一个地址。route -n get 198.18.0.2 结果可以看到有个 flag WASCLONED,简而言之这是 macOS 动态生成的一条路由,缓存用途 etutorials.org/Networking/Integrated+cisco+and+unix+network+architectures/Chapter+8.+Static+Routing+Concepts/Route+Cloning/

"检查到 dst ip 是 198.18.0.2 之后,认为这是一个 dns 请求"在 clash meta 的实现: github.com/MetaCubeX/Clash.Meta/blob/53f9e1ee7104473da2b4ff5da29965563084482d/listener/sing_tun/dns.go#L38并且这里只检查 dst ip ,不检查 port 。因此 dig .18.0.2 baidu.com -p 12345 这样使用任意 port 都可以得到 DNS 响应。

其实 这个是作者随便写的一个地址。。配置 hijack 后写什么地址都行,clashx 是取了网关地址+1

正解,198.18.0.2 和 dns-hijack 走的都是同一套逻辑

#13 谢谢。执行 route -n get ip 时候确实漏掉了一些关键信息,比如 198.18.0.2 的 flag 其中有 H, 说明是个 HOST 点对点的地址,不需要经过路由。所以冒昧再问一个问题,这个 198.18.0.2 在哪里被添加成 HOST 的?难道在 dnsAdds 的 ListenerHandler 处理的吗?如果是一个普通的监听服务器,如果 198.18.0.2 没有对应的网口则监听失败,和我 append 中的情况一样。所以这一块核心在哪里?找了一会没有找到。

“198.18.0.2 的 flag 其中有 H, 说明是个 HOST 点对点的地址,不需要经过路由”这个说法不完全对。所有 /32 路由都会被标记为 H 。他们还是会经过路由,需要查路由表确定发送到哪个 gateway“198.18.0.2 在哪里被添加成 HOST 的”正如我之前解释,198.18.0.2/32 这是一条由系统动态生成的 route ,用于缓存加速(匹配一条 /32 比匹配 128.0/1 效率更高,所以系统首次匹配完后,会生成一条 /32 路由缓存)。因为它是一条 /32 路由,所以标上了 H flag

netstat -rna 你会发现还有很多其他动态生成的路由,不仅局限于 198.18 段

“难道在 dnsAdds 的 ListenerHandler 处理的吗?如果是一个普通的监听服务器,如果 198.18.0.2 没有对应的网口则监听失败,和我 append 中的情况一样”注意这里不是监听,不是 socket/bind 。而是虚拟网卡,流经网卡的所有 packet 都会由程序捕捉到,无论 src/dst ip/port 是什么。哪些 packet 会经过虚拟网卡,则是由系统路由表决定的

首先,他不能是 198.18.0.1 ,因为这是你本机的 ip ,向本机 ip 发送的数据包会直接进本地回环,不进 clash 注册的那个 tun 设备,进而导致 clash 根本收不到你发出去的 dns 请求,无法劫持你的 dns ,也就不能正常的代理连接,至于为什么是 198.18.0.2 ,没有太多的原因,就是随手取的

并不赞同,可以看到 append 中的例子,是可以绑定 192.18.0.1 并且正常收到请求的。" clash 注册的那个 tun 设备"的 ip 地址就是 198.18.0.1 ,和 127.0.0.1 还有 192.168.11.109 (我的电脑)等都是网口的地址,198.18.0.1 是 utun 的网口地址,127.0.0.1 是 lo0 ,192.168.11.109 是 en0 的。如果程序监听 0.0.0.0 ,则向前面这三个网口的地址发起请求都可以正常收到。

#22 并且没有选用 198.18.0.1 的原因在 10 楼已经给出答案了。如果其他程序 bind 了所有网口 53 端口,那么会冲突。

#14 这个代码中有检查 port: targetAddr.Port() == 53 ,并且经过测试(clashxpro 的测试) dig .18.0.2 baidu.com -p 12345 是没有结果的。所以应该是有一点错误的。

macOS 的 tun 只支持 point-to-point 模式,就是说你要设置一个本机的地址,再设另一个 point 的地址,然后默认情况下只有 dst 是对面地址的 ip packet 会被送到 tun 里面。198.18.0.2 就是设置的那个地址。这个 tun 的实现挺奇怪的,好像也可以把两个地址设成一样的。不能 bind .2 的原因很简单,它不是你这台机器的 ip 地址。“198.18.0.2 在哪里被添加成 HOST 的”这是 tun 实现的一部分。这一堆东西基本上就是在折腾 macOS 奇怪的 tun 实现,没什么意思,要真想搞明白就自己写代码创建一个 tun ,然后把路由表改一改看什么情况下会把 dst 哪里的 packet 发给 tun 。Linux 的 tun 就很正常,没这些奇怪的东西。

你用 clash 就会有