在 VPN 之上把 SSH 动态转发用作 SOCKS 代理
只会说 SOCKS 的应用在普通 VPN 上没有代理端点。两跳、一个环回监听器,以及一次必须由 SSH 服务器自己完成的名称解析。
只会说 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 see | What it usually means | Where to look |
|---|---|---|
| The app connects and nothing follows | It ignores proxy settings entirely | Its own network settings, not the SSH session |
| A name resolution error for the destination | The SSH server cannot resolve it | Server resolver and search domains |
| A UDP-only application fails | Dynamic forwarding carries TCP only | This arrangement cannot serve it |
| curl works, the browser does not | The browser is not using the socks5h endpoint | Browser proxy configuration |
| The listener answers after the session ended | Another process owns port 1080 | The ss output, then the owning process |
前三行是人们最容易误判的,因为每一种产生的都是连接尝试,而不是关于代理本身的报错。在笔记本上能解析的名字,说明不了服务器的解析器,于是失败被读成证书或超时问题。只走 UDP 的应用是在设计之外,而不是设计之内坏了,多加一个 ssh 选项也改变不了。浏览器那一行是那句老提醒:代理设置是按应用分别生效的。最后一行正是关闭检查存在的理由:仍在监听的端口,意味着现在它是别的东西的了。