WSL2中Codex CLI接入Windows VPN的实战指南

Codex中文版一键安装,送1000万token,国内大模型

从现象到根因:WSL2 与 TUN 模式的“隔离”

许多开发者习惯在 WSL2 中运行 Codex CLI,但很快会发现一个诡异的现象:Windows 侧开着 iKuuu VPN(TUN 模式),浏览器和 curl 都能正常访问 OpenAI API,可 WSL2 里的 Codex 却始终连不上。问题不在 DNS,也不在防火墙,而是 WSL2 默认的 NAT 网络与 Windows 的 TUN 虚拟网卡之间存在路由隔离。

要验证这一点,可以依次执行以下命令:

Get-NetTCPConnection -State Listen -OwningProcess
curl.exe -4 -I https://api.openai.com
curl -4 -I https://api.openai.com

第一行用于查看 Windows 上正在监听的端口,确认 VPN 客户端是否真的在监听代理端口;第二行在 Windows 终端测试连通性;第三行在 WSL2 中测试。如果 Windows 正常、WSL2 失败,就说明流量根本没有进入 TUN 隧道。

这里有一个很容易被忽视的认知:TUN 模式创建的是第三层虚拟网卡,而 WSL2 的 NAT 网络是一个独立于 Windows 主机的虚拟子网。默认情况下,WSL2 的流量只会被 NAT 转发到 Windows 的物理网卡,不会主动路由到 TUN 虚拟网卡。因此,即使 TUN 模式工作正常,WSL2 也“看不见”它。

解决方案:一个 40 行的 CONNECT 转发器

既然 WSL2 无法直接使用 TUN,我们就需要一座“桥”。最轻量的做法是在 Windows 本地运行一个 HTTP CONNECT 代理,监听 127.0.0.1:17892。WSL2 中的 Codex CLI 把代理指向这个端口,转发器收到 CONNECT 请求后,通过 Windows 的网络栈(此时已经走 TUN)与目标服务器建立连接,然后原样转发数据。

const net = require('node:net');
const HOST = '127.0.0.1';
const PORT = Number(process.env.WSL_HTTP_PROXY_PORT || 17892);
const server = net.createServer((client) => {
  client.once('data', (chunk) => {
    const end = chunk.indexOf('\r\n\r\n');
    if (end < 0) { client.end('HTTP/1.1 400 Bad Request\r\n\r\n'); return; }
    const header = chunk.subarray(0, end).toString('latin1');
    const [requestLine] = header.split('\r\n');
    const [method, target] = requestLine.split(' ');
    if (method.toUpperCase() !== 'CONNECT') { client.end('HTTP/1.1 405 Method Not Allowed\r\n\r\n'); return; }
    const separator = target.lastIndexOf(':');
    const host = target.slice(0, separator);
    const port = Number(target.slice(separator + 1));
    const upstream = net.connect({ host, port }, () => {
      client.write('HTTP/1.1 200 Connection Established\r\n\r\n');
      client.pipe(upstream);
      upstream.pipe(client);
    });
    upstream.on('error', () => { client.end('HTTP/1.1 502 Bad Gateway\r\n\r\n'); });
  });
});
server.listen(PORT, HOST, () => { console.log('Proxy listening on http://' + HOST + ':' + PORT); });

这个脚本只处理 CONNECT 方法,因为 Codex CLI 访问 OpenAI API 走的是 HTTPS,代理只需要建立隧道即可。相比在 WSL2 内部再装一个 VPN 客户端,这种方案不会产生路由冲突,也不会影响 Windows 侧已有的网络配置。

从架构上看,数据流是这样的:WSL2 中的 Codex CLI → HTTP 代理环境变量指向 127.0.0.1:17892 → Windows 本地转发器 → Windows 网络栈 → iKuuu TUN → OpenAI API。整个过程对 WSL2 透明,对 VPN 也透明。

配置 WSL2 环境与开机自启

在 WSL2 中,需要将代理环境变量指向转发器。可以写入 ~/.bashrc 或 ~/.zshrc,让新终端自动加载:

export HTTP_PROXY="http://127.0.0.1:17892"
export HTTPS_PROXY="http://127.0.0.1:17892"
export ALL_PROXY="http://127.0.0.1:17892"
export NO_PROXY="localhost,127.0.0.1,::1"

验证配置是否生效:

env | grep -i proxy
codex --version
curl -4 -I https://api.openai.com

如果 curl 返回 HTTP/2 200,说明 Codex CLI 已经能通过转发器访问 OpenAI API。

转发器本身需要在 Windows 登录后运行。用计划任务即可实现开机自启:

$node = (Get-Command node.exe).Source
$script = 'C:\\path\\to\\wsl-http-proxy-17892.js'
$action = New-ScheduledTaskAction -Execute $node -Argument ('"' + $script + '"') -WorkingDirectory (Split-Path -Parent $script)
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
Register-ScheduledTask -TaskName 'Codex WSL local proxy' -Action $action -Trigger $trigger -Description 'Local CONNECT relay for WSL Codex CLI' -Force

注意将脚本路径替换为实际位置。注册后,每次登录 Windows,转发器就会在后台静默运行。

多 VPN 切换与避坑指南

如果你同时使用多个 VPN,比如 iKuuu 和另一个客户端,它们可能各自监听不同的代理端口。切换时只需修改 WSL2 中的环境变量:

export HTTP_PROXY="http://127.0.0.1:7892"
export HTTPS_PROXY="http://127.0.0.1:7892"
export ALL_PROXY="http://127.0.0.1:7892"

想切回 iKuuu,就把 7892 改回 17892。这种灵活性是 TUN 模式本身不具备的,因为 TUN 是全局接管网络,而代理端口是应用层入口。

实践中,有几个误区特别常见:

  • 混淆控制端口与代理端口。很多 VPN 客户端的 Web 管理界面监听 7892,但那是控制端口,不转发流量。代理端口需要查看客户端设置,往往不同。
  • 两个 VPN 都监听 7892。如果同时启动,端口冲突会导致其中一个失效。建议在客户端设置里手动改掉一个。
  • 只修改 config.toml 或只配置 .bashrc。前者只影响 WSL2 的网络模式,后者只设置环境变量,但没有启动转发器,代理指向一个不存在的端口,自然失败。
  • 认为 mirrored networking 能解决一切。WSL2 的 mirrored 模式确实让网络拓扑更接近宿主机,但它不会自动把 TUN 虚拟网卡的流量引导到 WSL2,更不会自动创建代理入口。

总结一句话:iKuuu TUN 不等于 HTTP/SOCKS 代理端口,mirrored networking 也不等于自动获得任意 VPN 的代理入口。要想让 WSL2 中的 Codex CLI 稳定走 Windows VPN,显式的 CONNECT 转发器是最简单可靠的方案。