Contents

SSH 隧道简明教程

本章主要介绍了什么是 SSH 隧道以及如何使用 SSH 隧道,包括 SSH 隧道加密数据传输以及绕过防火墙。

省流

本地转发

ssh -N -L [本地IP:]本地端口:目标服务器IP:目标端口 用户名@SSH服务器 

例如

ssh -N -L 192.168.10.36:18888:192.168.10.100:8888 root@192.168.10.100

远程转发

ssh -N -R [目标服务器IP:]目标服务器端口:本地IP:本地端口 用户名@SSH服务器 

例如

ssh -N -R 192.168.10.36:18888:192.168.10.36:8888 root@192.168.10.36

1. 什么是 SSH 隧道

SSH 隧道是 SSH 中的一种机制,它能够将其他 TCP 端口的网络数据通过 SSH 连接来转发,并且自动提供了相应的加密及解密服务。因为 SSH 为其他 TCP 链接提供了一个安全的通道来进行传输,因此这一过程也被叫做“隧道”(tunneling)。

SSH 隧道也可以叫做端口转发

SSH 隧道能够提供两大功能:

  • 1)加密 SSH Client 端至 SSH Server 端之间的通讯数据。
  • 2)突破防火墙的限制完成一些之前无法建立的 TCP 连接。

本地转发和远程转发

SSH 端口转发自然需要 SSH 连接,而 SSH 连接是有方向的,从 SSH Client 到 SSH Server 。而我们的应用也是有方向的,比如需要连接 MySQL Server 时,MySQL Server 自然就是 Server 端,我们应用连接的方向也是从应用的 Client 端连接到应用的 Server 端。如果这两个连接的方向一致,那我们就说它是本地转发。而如果两个方向不一致,我们就说它是远程转发

相关参数

  • “-L选项”:local,表示使用本地端口转发创建 ssh 隧道

  • “-R选项”:remote,表示使用远程端口转发创建 ssh 隧道

  • “-D选项”:dynamic,表示使用动态端口转发创建 ssh 隧道

  • “-N选项”: 表示创建隧道以后不连接到 sshServer端,通常与”-f”选项连用

  • “-f选项”:表示在后台运行ssh隧道,通常与”-N”选项连用

  • “-g选项”:表示 ssh 隧道对应的转发端口将监听在主机的所有IP中,不使用”-g选项”时,转发端口默认只监听在主机的本地回环地址中,”-g” 表示开启网关模式,远程端口转发中,无法开启网关功能

2. 演示

本地转发

一般是 server 端有 IP 能访问时用本地转发。

涉及到两台机器:

  • 本地(client):192.168.10.36
  • 服务器(server):192.168.10.100

需求:希望将服务器(192.168.10.100) 上的 8888 端口映射到本地(192.168.10.36)的 18888 端口。

即将本地(192.168.10.36) 18888 端口流量转发到服务器 (192.168.10.100) 上的 8888 端口。

比如不能直接访问 server 的 8888 端口,此时就可以使用 ssh 隧道来突破该限制。

在服务器(192.168.10.100) 上启动一个 http server:

python -m SimpleHTTPServer 8888

然后在本地执行以下命令,开启 ssh 隧道。

语法如下:

 ssh -N -L [本地IP:]本地端口:目标服务器IP:目标端口 用户名@SSH服务器 

具体命令有多种写法,以下任意一种方式都可以:

# 全部参数
ssh -N -L 192.168.10.36:18888:192.168.10.100:8888 root@192.168.10.100
# 远程 IP 改成 localhost
ssh -N -L 192.168.10.36:18888:localhost:8888 root@192.168.10.100
# 省略本地 IP
ssh -N -L 18888:localhost:8888 root@192.168.10.100

该命令表示在本地(192.168.10.36)和服务器(192.168.10.100) 之间建立 ssh 隧道,由 ssh 客户端监听本地(192.168.10.36) 的 18888 端口并将流量转发到服务器(192.168.10.100) 的 sshServer,最终由 sshServer 在转发到 localhost(或者192.168.10.36) 的 8888 端口

由于 sshServer 和服务器是同一台机器,因此填 localhost 和 192.168.10.36 都行。

每部分参数具体含义如下:

  • “-N选项”: 表示创建隧道以后不连接到 sshServer 端

  • “-L选项”:local,表示使用本地端口转发创建 ssh 隧道

  • 192.168.10.36:18888:192.168.10.100:8888

    • 192.168.10.36:18888,其中 192.168.10.36: 为本地 IP,指定在 192.168.10.36 对应网卡上监听 18888 端口

    • 192.168.10.100:8888:为需要转发到的目的 IP 和端口号

  • 192.168.10.36:18888:localhost:8888

    • 192.168.10.36:18888:同上

    • localhost:8888:将要转发的 ip 直接改成了 localhost,注意这个 localhost 是对 sshServer 来说的,也就是转发到 sshServer 所在节点的 8888 端口。

  • 18888:localhost:8888:本地 18888 端口会被转发到 sshServer 的 localhost:8888 端口上去。

    • 18888:省略了本地 IP,会监听 127.0.0.1 网卡
    • localhost:8888:同上
  • root@192.168.10.100:我们创建的 ssh 隧道是连接到 192.168.10.100 上的 root 用户的

测试一下,请求本地 8888 端口能否访问到 远程服务器上的 http server,根据本地监听的网卡不同,需要使用不同的访问方式:

curl http://localhost:18888
curl http://192.168.10.36:18888

这时转发数据流是: client 上的应用客户端将数据发送到本地的 18888 端口上, client 上的 ssh 客户端将本地 18888 端口收到的数据加密后发送到 ssh server上,ssh server 会解密收到的数据并将之转发到监听的 localhost:8888, 最后再将从 http server 返回的数据原路返回以完成整个流程。

网关模式

执行不同命令,会在本地监听不同的端口,例如执行

ssh -N -L 18888:localhost:8888 root@192.168.10.100

会监听本地的 127.0.0.1

[root@docker ~]# ss -tunlp|grep 18888
tcp    LISTEN     0      128    127.0.0.1:18888                 *:*                   users:(("ssh",pid=26018,fd=5))
tcp    LISTEN     0      128       [::1]:18888              [::]:*                   users:(("ssh",pid=26018,fd=4))

执行

ssh -N -L 192.168.10.36:18888:localhost:8888 root@192.168.10.100

则会监听命令中指定的 IP 192.168.10.36

[root@docker ~]# ss -tunlp|grep 18888
tcp    LISTEN     0      128    192.168.10.36:18888                 *:*                   users:(("ssh",pid=26036,fd=4))

如果你觉得这还不够,希望所有 IP 地址的 18888 端口都被监听,那么可以在建立隧道时开启”网关功能”,使用”-g”选项可以开启”网关功能”:

ssh -g -N -L 18888:localhost:8888 root@192.168.10.100

监听情况如下:

[root@docker ~]# ss -tunlp|grep 18888
tcp    LISTEN     0      128       *:18888                 *:*                   users:(("ssh",pid=26039,fd=4))
tcp    LISTEN     0      128    [::]:18888              [::]:*                   users:(("ssh",pid=26039,fd=5))

至此,我们已经了解 ssh 本地转发功能了,接下来看一下远程转发。

远程转发

还是上面的环境:

  • 本地:192.168.10.36
  • 服务器:无可直接访问 IP

当服务器端没有可以直接访问的 IP 时就无法使用本地转发了,如果本地有 IP 可以直接访问那么还可以使用远程转发。

在服务器上启动一个 http server:

python -m SimpleHTTPServer 8888

正好本地有一个服务器能访问的 IP 192.168.10.36,那么就可以在服务器上执行以下命令将端口转发到本地端口。

语法:

 ssh -N -R [目标服务器IP:]目标服务器端口:本地IP:本地端口 用户名@SSH服务器 

和本地转发类似,只是把 -L 变成 -R 了,然后本地 IP 和服务器 IP 换了个位置。

完整命令如下:

ssh -N -R 192.168.10.36:18888:localhost:8888 root@192.168.10.36

可以看做是在服务端执行的一个本地转发🙃,因为服务端没有 ip 所以需要反过来操作。

端口监听都是在本地发生,但是执行命令的机器却变成了服务端。

命令执行后会在本地(192.168.10.36)监听端口,并将流量转发到我们的服务端。之前本地转发时也是在本地监听端口。

将本地(192.168.10.36) 的 18888 端口转发到 server 端的 8888 端口。

唯一区别是远程转发模式下 只能监听本地回环地址,不能监听其他IP,也不能开启网关模式,即无法让其他机器通过 本地服务器(192.168.10.36 ) 来访问 server

由于是在服务端执行的命令,因此该场景中服务端端扮演的是 ssh client 的角色,而 client 端则是 ssh server。

此时的数据流向为:client 端(就是本地(192.168.10.36))数据发送到 18888 端口,本地的 ssh server 监听该端口并对数据进行加密后转发到服务器的 ssh client,ssh client 解密数据后转发到服务器的 8888 端口。

扩展-跨机器转发

具体场景如下图所示:

../../../img/linux/ssh/ssh-local-forward.png

如上图所示,我们想要在A与B之间创建隧道,最终通过隧道访问到ServerC中的 http 服务。

  • ServerAIP:192.168.10.205

  • ServerBIP:192.168.10.85

  • ServerCIP:192.168.10.134

ServerA 与 ServerB 上没有开启任何 http 服务,ServerC 中开启了 http 服务,监听了 8888 端口。

我们需要在 ServerA 上执行以下命令开启 ssh 隧道:

ssh -N -L 8888:192.168.10.134:8888 root@192.168.10.85

执行后 serverA 上已经开始监听 8888 端口了,默认是在本地回环地址上,需要其他机器访问的话可以指定 ip 或者增加 -g 参数开启网关模式。

[root@kc-1 ~]# netstat -tunlp|grep 8888                                             
tcp        0      0 127.0.0.1:8888          0.0.0.0:*               LISTEN      32464/ssh
tcp6       0      0 ::1:8888                :::*                    LISTEN      32464/ssh 

然后测试一下

$ curl http://localhost:8888                                                                                                                                      
 Directory listing for /                                             
.bash_history                                       
.bash_logout                                          
.bash_profile                                        
.bashrc                                                    
.cshrc                                                      
.pki/                                                       
.ssh/                                             
.tcshrc                                            
anaconda-ks.cfg                                  
original-ks.cfg                                                
tmp/

注意

上述场景中存在一个问题,就是数据安全性的问题,我们之所以使用ssh隧道,就是为了用它来保护明文传输的数据,从而提升安全性,不过,在上例的场景中,只有ServerA与ServerB之间的传输是受ssh隧道保护的,ServerB与ServerC之间的传输,仍然是明文的,所以,如果想要在上述场景中使用ssh隧道进行数据转发,首先要考虑ServerB与ServerC之间的网络是否可靠。

其实,当我们在创建隧道时如果开启了网关功能,那么应用客户端与ServerA之间的通讯也会面临同样的问题。

../../../img/linux/ssh/ssh-forward-issue.png

其实一般这种场景主要是为了绕过防火墙。

../../../img/linux/ssh/ssh-tunnel-corss-fw.png

动态转发

对于本地端口转发远程端口转发,都存在两个一一对应的端口,分别位于 SSH 的客户端和服务端,而动态端口转发则只是绑定了一个本地端口,而目标地址:目标端口则是不固定的。目标地址:目标端口是由发起的请求决定的,比如,请求地址为192.168.1.100:3000,则通过 SSH 转发的请求地址也是192.168.1.100:3000

在本地执行以下命令,

ssh -N -D localhost:2000 root@192.168.10.85

会在本地开启一个 socks 代理,监听 2000 端口

[root@kc-1 tmp]# netstat -tunlp|grep 2000                                           
tcp        0      0 127.0.0.1:2000          0.0.0.0:*               LISTEN      25496/ssh
tcp6       0      0 ::1:2000                :::*                    LISTEN      25496/ssh

我们只需要在本地配置上 socks 代理,localhost:2000 即可把所有请求通过 ssh 2000 端口转发到 192.168.10.85 这台机器上去了。

3. 小结

SSH 有以下功能:

  • 1)保护 tcp 会话,保护会话中明文传输的内容。

  • 2)绕过防火墙或者穿透到内网,访问对应的服务。

常用命令:

创建本地转发模式的ssh隧道,命令如下

 ssh -N -L [本地IP:]本地端口:目标服务器IP:目标端口 用户名@SSH服务器 

本机上的 forwardingPort 将会被监听,访问本机的 forwardingPort,就相当于访问 targetIP 的 targetPort,ssh隧道建立在本机与 sshServer 之间。

创建远程转发模式的ssh隧道,命令如下

 ssh -N -R [目标服务器IP:]目标服务器端口:本地IP:本地端口 用户名@SSH服务器 

sshServer 上的 forwardingPort 将会被监听,访问 sshServer 上的 forwardingPort,就相当于访问 targetIP 的 targetPort,ssh 隧道建立在本机与 sshServer 之间。

4. 参考

SSH Tunneling: Examples, Command, Server Config

ssh端口转发:ssh隧道