SSH 端口转发功能能够将其他 TCP 端口的网络数据通过 SSH 链接来转发,并且自动提供了相应的加密及解密服务。其实这一技术就是我们常常听说的隧道(tunnel)技术,原因是 SSH 为其他 TCP 链接提供了一个安全的通道来进行传输。
我们知道,FTP 协议是以明文来传递数据的。但是我们可以让 FTP 客户端和服务器通过 SSH 隧道传输数据,从而实现安全的 FTP 数据传输。
更常见的情况是我们的应用经常被各种防火墙限制。常见的有禁止访问某些网站、禁用某类软件,同时你的所有网络行为都被监控并分析!同样的通过 SSH 隧道技术我们完全可以规避这些限制。
如上图所示,通过 SSH 的端口转发, 应用程序的客户端和应用程序的服务器端不再直接通讯,而是转发到了 SSH 客户端及 SSH 服务端来通讯。这样就可以同时实现两个目的:数据的加密传输和穿透防火墙!
在具体的使用场景中,端口转发又被细分为本地端口转发、远程端口转发、动态端口转发等。本文将详细的介绍其技术原理及使用方法。
本地端口转发
假设我们有一台主机 B,上面运行着 smtp 服务器,监听的端口号为 25,但是只监听了 localhost 网络接口。也就是说只有运行在主机 B 上的邮件客户端才能与 smtp 服务器建立连接。此时另外一台主机 A 上的邮件客户端如果想要通过主机 B 上的 smtp 服务器收发邮件该怎么设置呢?通过 SSH 的本地端口转发功能可以轻松的搞定这样的场景!
假设两台主机上都安装了 SSH,我们可以使用主机 A 上的 SSH 客户端向主机 B 上的 SSH 服务器发起请求,建立一条执行端口转发的隧道:
$ ssh -L 10025:localhost:25 HostB
此命令的运行原理如下图所示(此图来自互联网):
运行上面的命令后,SSH 客户端程序在主机 A 上监听了 localhost:10025(你可以用 1024 - 65535 之间的任意端口代替 10025,只要不与已有端口冲突就行)。所有在主机 A 上发往 10025 端口的消息都会通过 SSH 隧道转发到主机 B 上的 25 端口。接下来需要配置主机 A 上的邮件客户端程序,让它把消息发送到 localhost:10025。完成之后主机 A 上的邮件客户端就可以通过主机 B 上的 smtp 服务器收发邮件了。具体的数据包的流向为:
1 邮件客户端把数据包发送到 localhost(主机 A) 的 10025 端口
2 SSH 客户端把数据包加密并从主机 A 发送到主机 B 的 SSH 服务器
3 SSH 服务器把数据包解密并发送到 localhost(主机 B) 的 25 端口
从 smtp 服务器返回的数据包则是沿着原路返回以完成数据的双向传递。
至此我们已经完成了一个最基本的本地端口转发 demo 的介绍。接下来让我们来聊一下究竟什么叫本地端口转发?
在上面的 demo 中我们注意到一共有两对的客户端与服务器程序,分别是 smtp 应用的客户端和服务器与 SSH 的客户端和服务器。如果应用程序的客户端和 SSH 的客户端位于 SSH 隧道的同一侧,而应用程序的服务器和 SSH 服务器位于 SSH 隧道的另一侧,那么这种端口转发类型就是本地端口转发。需要使用 -L 选项来创建。
前面的 demo 中应用程序的客户端和 SSH 客户端位于同一台主机上,应用程序的服务器端和 SSH 的服务器端也位于同一台主机上,真实的情况往往不是这样的:
上图中的场景可能更符合真实情况(此图来自互联网)。应用程序的客户端和 SSH 客户端分别位于 SSH 隧道同一侧的两台不同的主机上,而应用的服务器端和 SSH 服务器分别位于 SSH 隧道另一侧的两台不同的主机上。此时我们需要使用下面的命令:
$ ssh -g -L P:HostS:W HostB
应用 -g 选项后主机 A 不仅会监听 localhost 的 P 端口,还能够监听所有网络接口的 P 端口,所以主机 C 上的应用客户端就可以把消息发送到主机 A 的 P 端口。
接下来我们必须要介绍本地端口转发的命令格式了:
ssh -L: :
SSH server host 是 SSH 服务器所在的主机, remote host 和 remote port 则分别指应用程序服务器所在主机和监听端口。如果 remote host 指定为 localhost 则认为应用程序服务器和 SSH 服务器在同一台主机上。
在结束本地端口转发之前还需要介绍另外两个选项,它们是 f 和 N。上面的命令在创建隧道的同时登录到远程主机,一般情况下我们不需要这个登录。况且一旦这个登录退出,隧道也会随之关闭。我们更期望的是能够创建在后台运行的隧道,这时就需要添加 f 和 N 选项。
远程端口转发
我们必须区别远程端口转发和本地端口转发,因为它们对应了不同的应用场景,当然使用的命令行选项也是不一样的。如果应用程序的客户端和 SSH 的服务器位于 SSH 隧道的同一侧,而应用程序的服务器和 SSH 的客户端位于 SSH 隧道的另一侧,那么这种端口转发类型就是远程端口转发。远程端口转发的结构如下图所示(此图来自互联网):
所以,区分本地端口转发和远程端口转发主要是看 SSH 客户端与应用程序的哪一部分在 SSH 隧道的同一侧!远程端口转发的命令格式为:
ssh -R: :
其它的细节两者基本也是一样的。但是远程端口转发不支持 -g 参数,这让我们很难实现类似下面的用例:
内网中主机 A 上运行 Jenkins 服务器监听本机 8080 端口,并运行 SSH 客户端。
外网中的主机 B 上运行 SSH 服务器。
希望可以通过远程端口转发的方式在主机 A 和 B 之间建立隧道,
然后外网的 Bitbucket 等代码管理服务可以通过 Webhook 的方式访问主机 B 从而触发 Jenkins 服务器中的 Build。
这个问题的根源在于我们执行下面的远程端口转发命令后:
$ ssh -R 18080:localhost:8080 HostB
主机 B 只能监听 localhost 的 18080 端口:
如何让 HostB 监听本机所有网络接口的 18080 端口呢? 需要通过修改 SSH 服务器的配置来实现这个功能!在 SSH 服务器的配置文件 /etc/ssh/sshd_config 中添加一行:
GatewayPorts yes
保存后重启 SSH 服务器,然后重新建立隧道:
此时主机 B 已经可以接受外部 webhook 的调用了。
动态端口转发
相对于动态端口转发,前面介绍的端口转发类型都叫静态端口转发。所谓的 "静态" 是指应用程序服务器端的 IP 地址和监听的端口是固定的。试想另外一类应用场景:设置浏览器通过端口转发访问不同网络中的网站(比如在家里连接公司内网中的站点,哈哈)。这类应用的特点是目标服务器的 IP 和端口是未知的并且总是在变化,创建端口转发时不可能知道这些信息。只有在发送 HTTP 请求时才能确定目标服务器的 IP 和端口。在这种场景下静态端口转发的方式是搞不定的,因而需要一种专门的端口转发方式支持即 "动态端口转发"。SSH 动态端口转发是通过 Socks 协议实现的,创建动态端口转发时 SSH 服务器就类似一个 Socks 代理服务器,所以这种转发方式也叫 Socks 转发。
动态端口转发的命令格式为:
$ ssh -D
例如:
$ ssh -D 11080 [email protected]
注意,命令中不需要指定目标服务器和端口号。执行上面的命令后 SSH 客户端就开始监听本机 localhost 的 11080 端口。你可以把本机上浏览器网络配置中的 Socks 服务器指定为 localhost:11080。然后浏览器中的请求会被转发到 SSH 服务器端,并从SSH 服务器端与目标站点建立连接进行通信。
总结
SSH 端口转发是一项非常实用的技术,灵活的使用它不仅可以解决工程项目中繁杂的网络问题,还能够给我们的生活添加乐趣!