今天在 macOS 上踩了一整天 Clash Verge 的坑。核心现象不是浏览器打不开 GitHub,而是终端里的 git 命令连不上远程仓库:开了虚拟网卡(TUN)模式之后,git fetch / git pull 直接失败;关了 TUN、走系统代理又正常。中间还被 Fake-IP、DNS 覆写、Git SSH/HTTPS 三层误导了一圈。把过程记下来,避免下次再绕。
现象
最先撞上的是终端 git:
Connection closed by 198.18.0.6 port 443
fatal: Could not read from remote repository.
为了对照,再用 curl 测了一下裸访问:
* Connected to github.com (198.18.0.4) port 443
* LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL ...
关键信号是目标 IP 落在 198.18.0.0/16。这正是 Clash Fake-IP 段,说明 DNS 其实已经被劫持了;真正坏掉的是后面那一段“虚拟握手成功、真实转发失败”。所以问题看起来像“git 终端命令失效”,根子却在 TUN 数据面。
误判一:以为是 Git 没配代理
第一反应很容易是:
DNS 覆写对 Git 不生效,是因为没给 Git 配代理。
这不对。DNS 覆写 / TUN 接管和“有没有给 Git 配 http.proxy”是两层事:
- TUN 真正生效时,Git、curl、浏览器都不需要各自配代理,流量在系统路由层就被接管。
- Git 的
http.proxy/https.proxy只对 HTTPS remote 生效。 - 如果 remote 是
git@github.com:...,走的是 SSH,这两个配置项完全读不到。
所以“给 Git 配了代理仍然失败”,并不证明代理坏了,更可能是:
- remote 其实是 SSH;
- 或者 TUN 根本没真正接管流量。
误判二:看到 Fake-IP 就以为 DNS 坏了
DNS 配置大致是这样:
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
于是 nslookup github.com / curl -v 都会看到:
github.com -> 198.18.0.4
很多人会立刻觉得“DNS 解析错了”。在 Fake-IP 模式下,这恰恰是正常表现。预期链路是:
curl
-> 连 198.18.0.4
-> Clash 查 Fake-IP 映射表
-> 还原真实 GitHub IP
-> 走代理节点转发
真正异常的是:连接被直接打到 198.18.0.4:443,Clash 没有把这个 Fake-IP 连接接管并转发出去。也就是说,DNS 劫持成功了,数据面失败了。
误判三:给 Git 配了 HTTP 代理,却在测 SSH remote
当时执行了:
git config --global http.proxy http://127.0.0.1:7897
git config --global https.proxy http://127.0.0.1:7897
git fetch
仍然报:
Connection closed by 198.18.0.6 port 443
这句 Connection closed by <ip> port <port> 是 SSH 客户端 的报错格式,不是 HTTP/curl 的。先确认 remote:
git remote -v
如果是 git@github.com:xxx/xxx.git,那刚才配的 HTTP 代理天然无效。
两条修法:
方案 A:改成 HTTPS remote
git remote set-url origin https://github.com/<user>/<repo>.git
git fetch
这样就能吃到 http.proxy。推送时用 GitHub PAT,不要用账号密码。
方案 B:保留 SSH,给 SSH 单独走代理
Clash 混合端口同时支持 HTTP 和 SOCKS5,可用系统自带 nc:
Host github.com
HostName github.com
User git
ProxyCommand nc -X 5 -x 127.0.0.1:7897 %h %p
如果本来就有 ssh.github.com + Port 443 这类国内常见绕行配置,把 ProxyCommand 加进现有 Host 块即可。
验证:
ssh -T git@github.com
git ls-remote https://github.com/git/git.git
第二条专门验证 HTTPS 代理是否通,不依赖当前仓库 remote。
真正的分水岭:TUN 虚拟栈 vs 系统代理
TUN 开着时,不带代理的 curl:
curl -v https://github.com
会复现同样的 TLS 失败。原因是:
- curl 发起连接;
- 系统路由把 Fake-IP 流量导向 TUN 设备;
- Clash / mihomo 的虚拟 TCP/IP 栈先在本地把 TCP 握手“应答”掉;
- 于是你看到
Connected to github.com port 443——这只是连上了虚拟栈,不是连上了真实 GitHub; - 随后 TLS Client Hello 需要走“真实节点转发 + 写回”链路;
- macOS 上这一层一旦出问题,表现永远是:握手秒成功,一发数据就断。
系统代理模式则完全不同:进程直接连 127.0.0.1:7897 的 loopback socket,不经过虚拟网卡,自然碰不到这层坑。
一个很干净的对照实验:
# TUN 开着,但显式走本地代理端口
curl -x http://127.0.0.1:7897 https://github.com
如果这条能通,而裸 curl https://github.com 不通,就可以基本坐实:问题在 TUN 数据面,不在节点、不在订阅、也不在 DNS 名单本身。
根因组合:GVisor + Fake-IP + DNS 劫持过宽
这次配置里还有两个容易放大问题的点。
1. DNS 劫持写成 any:53
过宽的劫持会把所有 DNS 请求都塞进 Clash:
系统 DNS
-> TUN
-> Clash DNS
-> Fake-IP
-> 198.18.x.x
建议先改成更保守的:
127.0.0.1:53
或者直接关掉 DNS 劫持做对比测试。
2. TUN 堆栈用了 GVisor
在 macOS 上,GVisor 对 TLS / Git / HTTP2 一类流量偶尔会有兼容问题。优先改成:
TUN 堆栈: Mixed
自动设置全局路由: 开
严格路由: 开
自动选择流量出口接口: 开
DNS 劫持: 127.0.0.1:53
改完后重启 Clash,并清一次系统 DNS 缓存:
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
再测:
curl -v https://github.com
如果 Fake-IP 映射和转发都恢复正常,仍然可能看到 198.18.x.x,但 TLS 会继续走下去;如果已经切到非 Fake-IP / 劫持关闭,则更常见的是看到真实公网 IP。
排查顺序建议
以后再遇到“开了 Clash,终端里 git fetch / git pull 连不上远程仓库”,按这个顺序更快:
- 看 TUN / 服务模式有没有真正起来,别只看开关亮着。
- 查
/etc/hosts有没有写死github.com。 nslookup github.com:落到198.18.x.x说明 Fake-IP 生效;落到公网 IP 说明请求没进 Clash DNS。- 对照实验:
curl https://github.comcurl -x http://127.0.0.1:7897 https://github.com
- 确认 Git remote 是 HTTPS 还是 SSH,别给 SSH remote 配 HTTP 代理还以为代理坏了。
- TUN 堆栈从 GVisor 换 Mixed,DNS 劫持从
any:53收窄到127.0.0.1:53。 - 实在不稳,先关 TUN,改用系统代理 + 必要工具的显式代理配置。
一句话结论
今天真正的坑不是“DNS 没对 Git 生效”,也不是“Git 没配代理”,而是:
终端里的 git 命令被 TUN 数据面卡住了:Fake-IP 已经把名字解析进了 Clash,但连接没有被正确转发;再叠加 SSH remote 不吃 HTTP 代理,以及 GVisor / 过宽 DNS 劫持的兼容问题,就会表现为握手成功、TLS 立刻断。
先用 curl -x 对照实验坐实 TUN 问题,再决定是换 Mixed、收窄 DNS 劫持,还是干脆关掉 TUN。Git 侧则先分清 HTTPS / SSH,别把两套代理模型搅在一起。