不把 -L、-R、-D 搞混的办法
一句话总结
-L 会在本地开放端口并将流量转发到远端,-R 会在远端开放端口并将流量引回本地,-D 则会在本地建立 SOCKS 代理。只要方向判断准确,其余只是语法问题。
为什么需要了解这些
需要连接内网数据库,但该数据库无法从外部访问,而堡垒机可以通过 SSH 连接。此时需要使用本地端口转发:将进入笔记本 15432 端口的连接经由堡垒机发送到 db.internal:5432。
ssh -L 15432:db.internal:5432 bastion
读法是:-L <내가 열 포트>:<배스천이 볼 때의 목적지>:<그 포트>。关键在于,中间的主机名以 SSH 服务器的视角解析。即使笔记本无法解析 db.internal 也没有关系。
工作原理
| 选项 | 开放端口的位置 | 流量方向 | 典型用途 |
|---|---|---|---|
-L |
本地 | 本地 → SSH 服务器 → 目标 | 访问内网数据库、管理控制台 |
-R |
远端(SSH 服务器) | 远端 → SSH 服务器 → 本地 → 目标 | 向外暴露防火墙后的服务、接收 Webhook |
-D |
本地(SOCKS) | 由应用程序选择任意目标 | 让整个浏览器经由内网访问 |
专用于隧道的连接应同时使用 -N -f。-N 表示不执行远程命令,-f 表示转入后台运行。
ssh -N -f -L 15432:db.internal:5432 bastion
-R 有一个陷阱:默认只绑定到 SSH 服务器的环回地址。 也就是说,服务器上的其他人无法使用该端口。若要向外开放,必须在服务器启用 GatewayPorts yes,而这项设置需要从安全角度慎重评估。
服务器端用于控制转发的指令如下。
AllowTcpForwarding no
AllowAgentForwarding no
GatewayPorts no
PermitOpen 10.0.5.20:5432
PermitOpen 尤其实用。它不必完全禁止转发,而是可以通过白名单限制目标地址。这非常适合“开发人员必须访问生产数据库,但不能访问其他内部服务”的场景。
代理转发有风险
-A(代理转发)虽然方便,但拥有中转服务器 root 权限的人,可以通过你的代理套接字使用你的密钥向其他服务器认证。 如果目的只是经过堡垒机,应使用 ProxyJump(-J),它无需暴露代理即可获得相同效果。
ssh -J bastion deploy@10.0.3.14
转义序列
使用隧道时,连接有时看起来像是卡住了。此时可以使用转义序列:在一行的最开头按下 ~,然后输入相应字符。
| 序列 | 操作 |
|---|---|
~. |
强制终止连接(退出卡住的会话) |
~^Z |
将 SSH 转入后台 |
~# |
列出正在转发的连接 |
~C |
打开命令行(在运行期间添加或删除 -L) |
~? |
显示帮助 |
知道可以通过 ~C 在连接期间添加转发的人意外地少。当需要在不断开会话的情况下再建立一条隧道时,它非常有用。
避免混淆三个方向的方法
-L、-R、-D 虽然只差一个字母,作用却完全不同。只要明确在哪一端监听、
从哪一端发出流量,就不会混淆。
| 选项 | 监听位置 | 流出位置 | 使用场景 |
|---|---|---|---|
-L 5432:db:5432 |
本机 | 远程服务器 | 连接本机无法直接访问的数据库 |
-R 8080:localhost:3000 |
远程服务器 | 本机 | 让外部访问本地开发服务器 |
-D 1080 |
本机(SOCKS) | 远程服务器 | 一次访问多个目标 |
-L 中的 db:5432 是远程服务器看到的名称。它通过服务器的 DNS 解析,
而不是使用本机的 hosts 文件。这里是最容易配置错误的地方。
为什么远程端口无法从外部访问
设置 -R 8080:localhost:3000 后,从其他设备连接却被拒绝,这并非配置故障,
而是默认行为。在 GatewayPorts no 下,sshd 只会把转发端口开放在远程服务器的环回地址。必须在服务器的 /etc/ssh/sshd_config 中改为
GatewayPorts clientspecified,并像 -R 0.0.0.0:8080:localhost:3000 那样
明确指定地址,端口才会对外开放。启用该选项意味着,任何能够连接这台服务器的人,都可以把你笔记本上的端口暴露到互联网,
因此不要在公用跳板服务器上启用。
恢复断开的隧道不是 ssh 的职责
隧道必然会断开:NAT 表会过期、无线网络会中断、服务器会重启。
ssh 本身不会重新连接,因此应由外部工具负责重建连接。
autossh -M 0 -N \
-o ServerAliveInterval=15 -o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes \
-L 5432:db.internal:5432 jump.example.com
ServerAliveInterval 用来确认静默连接是否已经失效。它每 15 秒询问一次,
连续三次没有响应就断开连接。ExitOnForwardFailure=yes 可确保端口开放失败时不会把连接视为成功。若省略它,就会留下没有隧道却仍存活的 ssh 连接,
造成看似已连接、实际什么都无法使用的状态。
通过复用减少连接开销
如果反复连接同一台服务器,可以共享一个连接。
Host jump
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
从第二次连接开始会跳过握手与认证,体验会明显改善。
不过 ControlPath 是套接字文件,如果主目录位于 NFS 上就无法工作。
此时应将它移到 /run/user/$UID 下。
生产现场中的常见情况
打开隧道后忘记关闭。 通过 -N -f 送入后台的 ssh 进程会安静地持续运行。几周后就会有人问:“这个端口是谁开的?”如果在 ss -ltnp 中看到进程为 ssh,那就是隧道。有些团队会约定在 ControlPath 或进程名称中标注用途。
下一次实验要做什么
分别创建本地、远程和动态三种转发,并使用 ss 确认各条隧道在哪一端开放了端口。最后提交包含实际证据的三种方式对照表。