Git SSH 连接异常:WSL2 DNS 劫持导致的 github.com 解析错误

问题现象

在 Windows 环境下使用 Git 通过 SSH 推送代码到 GitHub 时,突然遇到连接重置错误:

1
2
3
4
5
6
$ git push
Connection reset by xxx.xxx.x.x port 22
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

更奇怪的是,前一天还能正常推送,今天却突然失败。

问题排查

初步诊断

首先测试 SSH 连接:

1
2
$ ssh -T [email protected]
Connection reset by xxx.xxx.x.x port 22

检查远程仓库配置:

1
2
3
$ git remote -v
origin [email protected]:username/repo.git (fetch)
origin [email protected]:username/repo.git (push)

远程地址配置正确,问题不在这里。

DNS 解析异常

尝试 ping GitHub:

1
2
3
4
$ ping github.com

Pinging github.com [xxx.xxx.x.x] with 32 bytes of data:
Reply from xxx.xxx.x.x: bytes=32 time<1ms TTL=64

问题暴露github.com 被解析到了一个内网 IP 地址(127.x.x.x 段),而不是 GitHub 的真实公网 IP(应该是 x.x.x.x 段)。

排除常见原因

按照常规思路,逐一排查:

  1. hosts 文件

    1
    type C:\Windows\System32\drivers\etc\hosts | findstr github

    结果:无相关记录 ✓

  2. 端口占用

    1
    netstat -ano | findstr xxx.xxx.x.x

    结果:无进程监听 ✓

  3. VPN/代理配置

    • 检查 Proxifier 规则:正常
    • 检查 Git 配置:无异常

定位根本原因:WSL2

注意到系统后台运行着 WSL2(Windows Subsystem for Linux),这是一个关键线索。

WSL2 的网络架构

  • WSL2 使用虚拟网络适配器(Hyper-V 虚拟交换机)
  • 有独立的虚拟 IP 地址段(通常是 172.x.x.x127.x.x.x
  • 可能拦截并重定向 Windows 的 DNS 查询

验证假设

1
2
3
4
5
6
7
8
# 关闭 WSL
wsl --shutdown

# 清理 DNS 缓存
ipconfig /flushdns

# 重新测试
ping github.com

结果:github.com 被正确解析到 140.82.x.x,问题确认!

解决方案

根本原因分析

WSL2 默认会自动生成 /etc/resolv.conf 文件,其中的 DNS 配置可能与 Windows 主机冲突,导致:

  1. WSL 内部的 DNS 查询被转发到 Windows
  2. Windows 的 DNS 查询被 WSL 拦截
  3. 某些域名(如 github.com)被错误解析到 WSL 的虚拟网络地址

永久修复步骤

步骤 1:禁用 WSL 的自动 DNS 生成

在 WSL 终端中执行:

1
2
# 编辑 WSL 配置文件
sudo nano /etc/wsl.conf

添加以下配置:

1
2
[network]
generateResolvConf = false

保存并退出(Ctrl+XYEnter)。

这一步告诉 WSL 不要自动生成 DNS 配置,避免与 Windows 冲突。

步骤 2:手动配置 WSL 的 DNS

1
2
3
4
5
# 删除自动生成的 resolv.conf
sudo rm /etc/resolv.conf

# 创建新的 DNS 配置
sudo nano /etc/resolv.conf

写入可靠的公共 DNS 服务器:

1
2
nameserver 1.1.1.1
nameserver 8.8.8.8

锁定文件,防止被自动覆盖:

1
sudo chattr +i /etc/resolv.conf

步骤 3:重启 WSL 使配置生效

在 Windows PowerShell(管理员权限)中:

1
wsl --shutdown

等待几秒后,重新启动 WSL:

1
wsl

步骤 4:验证修复

在 WSL 中验证 DNS:

1
2
3
4
5
cat /etc/resolv.conf
# 应显示:nameserver 1.1.1.1

nslookup github.com
# 应解析到 140.82.x.x

在 Windows 中验证:

1
2
3
4
5
6
7
8
9
ipconfig /flushdns
ping github.com
# 应解析到 140.82.x.x

ssh -T [email protected]
# 应输出:Hi username! You've successfully authenticated...

git push
# 成功推送

技术原理

WSL2 网络模型

WSL2 与 WSL1 的网络实现完全不同:

特性 WSL1 WSL2
网络实现 共享 Windows 网络栈 独立的虚拟网络(Hyper-V)
IP 地址 与 Windows 相同 独立的虚拟 IP
DNS 解析 直接使用 Windows DNS 自动生成 /etc/resolv.conf

DNS 劫持的触发条件

  1. WSL2 的 DNS 服务(systemd-resolved 或类似服务)启动
  2. Windows 网络适配器优先级配置不当
  3. WSL 虚拟网络适配器的 Metric 值低于物理网卡
  4. DNS 查询被路由到 WSL 的虚拟 IP

为什么 generateResolvConf = false 有效?

默认情况下,WSL2 会根据 Windows 的网络配置自动生成 /etc/resolv.conf,这可能导致:

  • 循环引用:WSL 的 DNS 指向 Windows,Windows 的 DNS 查询又被 WSL 拦截
  • 缓存污染:错误的解析结果被缓存,持续影响后续查询

禁用自动生成后,WSL 使用固定的公共 DNS(1.1.1.1),彻底切断与 Windows DNS 的耦合。

总结

这次问题的教训:

  1. WSL2 是一个完整的虚拟机环境,有独立的网络栈,可能与 Windows 主机产生冲突
  2. DNS 劫持问题难以察觉,因为错误的解析结果看起来”合理”(能 ping 通本地地址)
  3. 昨天能用、今天不能用的问题,往往是系统配置变化(如 WSL 更新、网络适配器顺序变化)导致的
  4. 排查网络问题的正确顺序:DNS 解析 → 路由跟踪 → 防火墙规则 → 应用层配置

通过禁用 WSL 的自动 DNS 生成并手动配置可靠的公共 DNS,可以彻底解决此类问题,同时保持 WSL 和 Windows 主机的网络独立性。

参考资料