网上关于 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 上的资源,需要代理加速。
现在分析一下需求,我们需要两个转发:
- 将 A 访问实验室内网的连接转发到 B 上 -->
-D
转发 - 将 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_address
为localhost
则表示只限于本地使用,而空地址或*
表示该端口可从所有网络接口使用。 - 注意
bind_address
默认绑在本地网络接口上,如果需要从其他主机访问该端口,需要在 sshd_config 中设置 GatewayPorts = yes。 - 如果
port
可以省略,则表示 SSH 会自动请系统分配一个空闲的端口。