LabHub
学习 学习路径 课程

SSH 与文件传输

不把 -L、-R、-D 搞混的办法

在 LabHub 中继续学习

一句话总结

-L在本地开放端口并将流量转发到远端-R在远端开放端口并将流量引回本地-D 则会在本地建立 SOCKS 代理。只要方向判断准确,其余只是语法问题。

概念图: 在本地开放端口并将流量转发到远端 · 在远端开放端口并将流量引回本地 · 在本地建立 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) 由应用程序选择任意目标 让整个浏览器经由内网访问

SSH 转发的三个方向。-L 在本机开放端口,经由 SSH 服务器把流量送往远端网络中的目标;-R 则在 SSH 服务器开放端口,把进入的流量引向本机一侧的目标;-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 确认各条隧道在哪一端开放了端口。最后提交包含实际证据的三种方式对照表。