只会说 SOCKS 的应用在普通 VPN 上无话可说,因为隧道承载的是数据包,而应用想要的是一个代理端点。当访问所有者允许时,OpenSSH 可以提供这个端点,因为动态转发会把一个 SSH 会话变成本地代理。接下来要分清两跳:抵达 SSH 主机的隧道,以及该主机随后向外发起的连接。把两者分开,既能说明这套安排为什么有效,也能说明它永远不会做什么。

SSH 监听器成为什么、不成为什么

动态转发让 SSH 客户端在本地监听,并与任何接入方以 SOCKS4 或 SOCKS5 对话。每个请求都会指名一个目标,由 SSH 服务器发起后续连接,因此目标解析可以在远端完成。这是应用层的 TCP 转发,不是整机 VPN。它不承载 UDP 关联,不承载 ICMP,对完全忽略代理设置的应用也毫无帮助。从 SSH 服务器到应用的那一跳同样不在你的隧道里,当目标只接受来自已知网络的流量时,这一点很关键。

前提是实打实的:到 SSH 主机的路由、被授权的账户、经过验证的服务器主机密钥,以及转发许可。这些都不是代理创造的,而最后一项通常最先缺失。在排查其他任何问题之前,先确认这四项,因为无论缺哪一项,从客户端看到的失败都一样。

在环回上以显式端口启动它

选一个未使用的本地端口,并显式绑定到环回,而不是省略地址。下面的三个选项各有一个存在理由。

ssh -D 127.0.0.1:1080 -NT -o ControlPath=none -o ExitOnForwardFailure=yes \
  [email protected]

-N 跳过远程命令,-T 跳过伪终端,因此该会话只为转发而存在。ExitOnForwardFailure 在监听器无法建立时让客户端退出,而不是在没有你要的转发的情况下继续运行。ControlPath=none 让会话避开一条可能追加自身转发的多路复用连接。环回让监听器不暴露在 LAN 上,但在共享机器上它不是授权边界,因为其他本地进程同样能连上。OpenSSH 客户端参考记录了所有这些标志,而环回的警告直接来自监听套接字本身是什么。

让名称经由代理,而不是绕过代理

SOCKS5 客户端可以自己解析目标,也可以请代理解析,这个选择决定了由哪一侧的解析器作答。用 socks5h 时,curl 把主机名交给代理,因此解析发生在 SSH 服务器上。用普通 socks5 时,笔记本先解析,只有连接本身出门,当名称是内部名称时,这就在无声地废掉了本意。下面的请求还清空了不使用代理的名单,使继承来的 NO_PROXY 值无法把它绕开代理。

curl -sS --noproxy '' --proxy socks5h://127.0.0.1:1080 https://app.corp.example/health

证书校验和应用自身的登录仍然生效,因为代理两者都不替代。还剩两次解析:笔记本必须经允许的 VPN 与 DNS 路径抵达 SSH 主机,SSH 服务器必须自己解析应用名称。后者可以在任何流量经过代理之前单独测试。

ssh [email protected] getent hosts app.corp.example

先证明一个被允许的目标,然后关闭会话

既测一个策略允许的目标,也测一个策略应当拒绝的目标,因为转发能用只证明监听器能用,仅此而已。应用看到的是 SSH 服务器选择的源地址,那并不是你笔记本的隧道地址。在远端按源地址记日志的地方,请求被拒时第一个要查的就是这个差异。

通过拥有该会话的进程来停止它,而不是去杀掉一个无关的 shell。然后确认监听器已经消失,因为仍在应答的端口,就是一个你并没有真正关闭的会话。只移除这次练习引入的代理设置,其余应用配置保持不动。一个无人看管、攥着转发规则的终端不是服务,而长期代理需要经过批准的声明周期,而不是一扇被遗忘的窗口。

ss -ltn | grep 1080

这套安排修不了的故障

有些症状直接指向上面这些限制,早点认出来能省下一个下午的代理调试。表把看到的现象对应到它通常的含义。最后一列里的任何一条都是设计问题,而不是配置失误。

What you seeWhat it usually meansWhere to look
The app connects and nothing followsIt ignores proxy settings entirelyIts own network settings, not the SSH session
A name resolution error for the destinationThe SSH server cannot resolve itServer resolver and search domains
A UDP-only application failsDynamic forwarding carries TCP onlyThis arrangement cannot serve it
curl works, the browser does notThe browser is not using the socks5h endpointBrowser proxy configuration
The listener answers after the session endedAnother process owns port 1080The ss output, then the owning process

前三行是人们最容易误判的,因为每一种产生的都是连接尝试,而不是关于代理本身的报错。在笔记本上能解析的名字,说明不了服务器的解析器,于是失败被读成证书或超时问题。只走 UDP 的应用是在设计之外,而不是设计之内坏了,多加一个 ssh 选项也改变不了。浏览器那一行是那句老提醒:代理设置是按应用分别生效的。最后一行正是关闭检查存在的理由:仍在监听的端口,意味着现在它是别的东西的了。

相关文章