网上关于 SSH 端口转发的文章有很多,但个人觉得都不够简单明了,甚至有些是错误的。因此在这里总结一下,供不时之需。

首先需要明确,SSH 命令的用法还是以 Manpage 为准,参考:ssh(1) - Linux manual page (man7.org)

本文基于 ManPage 内容进行讲解这不是水博客

Well,您可以跳到实例部分,可能更加有用。

前言

为了讲解的简单,我只讲解 “端口” 的转发。实际上,Unix Socket 也可以用相同的方式转发,请参考 Man Page。

-D 动态转发

-D [_bind_address :_ ] _port_

指定一个本地的动态应用级端口转发。

其工作方式是在本地分配一个 Socket 来监听端口,每当连接到这个端口时,连接通过 SSH 隧道被转发到远程主机上,然后远程主机根据协议确定该数据被转发到哪里。此时 SSH 充当 SOCKS 代理服务器。

-L 本地转发

-L [bind_address : ] port : host : hostport
-L [bind_address : ] port : remote_socket
-L local_socket : host : hostport
-L local_socket : remote_socket

指定本地主机上给定的 TCP 端口或 Unix 套接字的连接将被转发到远程的给定主机和端口或 Unix 套接字上。

其工作方式是在本地分配一个 Socket 来监听本地的端口。每当连接到本地端口时,数据通过 SSH 隧道,被转发到远程主机的指定端口

区分 -D 和 -L:本地转发中,数据被转发到指定端口,而动态转发根据协议内容自动确定数据去向。

-R 远程转发

-R [bind_address : ] port : host : hostport
-R [bind_address : ] port : local_socket
-R remote_socket : host : hostport
-R remote_socket : local_socket

指定将远程(服务器)主机上给定 TCP 端口或 Unix 套接字的连接转发到本地端给定的主机和端口或 Unix 套接字。

其工作方式是远程主机上分配一个 Socket 监听远程的端口。每当连接到远程端口时,数据通过 SSH 隧道,被发送到本地主机的指定端口。

区分 -L 和 -R:-L 是别人向本地发数据,数据会被转发到远程的特定端口;-R 是别人往远程发数据,数据会被转发到本地的特定端口。数据流向恰好相反。

实例

举一个中国特色网络环境下的例子:

  • 本地主机 A 是我的笔记本,装有一个代理软件(Clash/V2Ray);
  • 远程主机 B 是实验室内的一台服务器,没有代理软件,但能访问实验室内网的资源;
  • 现在有两个需求:

    1. 我要在笔记本 A 上通过 B 访问内网的资源。
    2. 主机 B 上安装 GitHub 上的资源,需要代理加速。
    

现在分析一下需求,我们需要两个转发:

  1. 将 A 访问实验室内网的连接转发到 B 上 --> -D 转发
  2. 将 B 访问 GitHub 的流量转发到 A 的代理上 --> -R-L 转发

需求 1:内网访问

由于实验室内网的资源可能有很多种,比如 FTP/HTTP,并且服务不一定运行在 B 上,所以需要 SSH 根据协议自动决定转发的方向。因此选择 -D 动态转发。

ssh user@B -D 12345

由于 -D 使用 SOCKS 协议,所以要设置代理类型为 SOCKS(参考 SwitchOmega 的使用)。在 A 上打开浏览器,使用 socks5://localhost:12345 作为代理,即可访问内网的服务了。

需求 2:代理加速

由于 A 上的代理软件使用固定的端口,所以可以使用 -R 或者 -L 进行转发。

例如,A 正在使用 Clash (假设HTTP 代理端口为 10808),我们可以将其 远程转发 到 B 的 8888 端口。远程主机访问自己的 8888 端口,就使用了本地主机的 10808 端口上的代理。

# 通过 SSH 远程转发端口
ssh user@B -R 8888:localhost:10808

# 转发后在远程主机上设置代理并测试
export http_proxy=http://localhost:8888
export https_proxy=https://localhost:8888
curl https://google.com

但是,我现在已经在 B 的 Shell 里了,该怎么办呢?由于 -L-R 方向正好相反,我们可以在 B 上:

# 通过 SSH 本地转发端口
ssh user@A -L 8888:localhost:10808

# 转发后在远程主机上设置代理并测试
export http_proxy=http://localhost:8888
export https_proxy=https://localhost:8888
curl https://google.com

对四元组的解释

看到这里是不是对 8888:localhost:10808 这种表达形式有些疑惑?

这里其实是一个四元组:[bind_address : ] port : host : hostport ,其中第一项可以省略。

对于 -L 本地转发,代表 本地地址 : 本地端口 : 远程地址 : 远程端口
对于 -R 远程转发,代表 远程地址 : 远程端口 : 本地地址 : 本地端口

所以,-L 8888:localhost:10808 <=> -L localhost:8888:localhost:10808 就代表,访问本地 localhost:8888 的流量,会被转发到远程的 localhost:10808。

常用的其他选项

对于转发而言,还有几个选项比较有用:

  • -C: 使用数据压缩。
  • -f: 让 SSH 在后台运行,STDIN 会被重定向到 /dev/null
  • -N: 让 SSH 不执行远程命令,即只负责转发端口。

所以一个常见的命令组合就可以是:

ssh -CNf -D 12345 user@IP

注意事项

  • 端口转发也可以在 SSH 配置文件中设置。
  • 可以用方括号括住地址,来区分 IPv6 地址和端口号。
  • 只有超级用户可以转发特权端口。
  • 可以使用 bind_address 将连接绑定到一个特定的地址(比如客户端有多个 IP 地址的情况下,可以指定绑定的 IP)。bind_addresslocalhost 则表示只限于本地使用,而空地址或 * 表示该端口可从所有网络接口使用。
  • 注意 bind_address 默认绑在本地网络接口上,如果需要从其他主机访问该端口,需要在 sshd_config 中设置 GatewayPorts = yes。
  • 如果 port 可以省略,则表示 SSH 会自动请系统分配一个空闲的端口。