SSH隧道版本答案: 正向隧道+反向隧道实现内网服务器公网访问
由于访问大学的计算资源需要连接学校内网。但虚拟专用网需要的安全凭证过于麻烦,需要密码+一次性密码双重认证。于是我利用了在大学里的一台能访问公网的服务器(无需公网IP!)向家中服务器和其他内网服务器搭建了几条SSH隧道,实现了公网通过SSH访问内网服务器。
网络拓扑
Internet
|
+----------------------+
| Public Server |
| IP: 125.239.204.25 |
+----------------------+
|
|
+-----------------+
| Firewall |
| (outbound only) |
+-----------------+
|
University Network
|
+---------------------------------+
| |
+----------------------+ +----------------------+
| Server V | | Server A |
| IP: 130.216.0.2 |---------| IP: 130.216.0.3 |
| Public Access | | Public Access |
+----------------------+ +----------------------+
| |
+----------------------+ +----------------------+
| Server C1 | | Server C2 |
| IP: 130.216.4.2 |---------| IP: 130.216.0.3 |
| (No Internet Access) | | (No Internet Access) |
+----------------------+ +----------------------+
其中,Server V
代表一台DGX Station V100,Server A
代表一台DGX Station A100它们都接入了学校内网并且在防火墙下允许访问公网;Server C1
和C2
代表Cluster 1和2,它们接入了学校内网但不允许访问公网。一台具有公网IP的服务器作为桥梁来建立SSH隧道。
SSH隧道
所谓隧道,是指经过SSH身份认证之后建立的一条持久化TCP连接。这条隧道不仅可以传输终端命令,也可以传输网页等一切基于TCP协议的数据。
生活中的隧道没有正反,但SSH隧道分为正向隧道和反向隧道。SSH隧道本身是双向的,类似于生活中的隧道,但我们规定:发出命令的方向为正向,返回结果的方向为反向。
正向隧道
假如一条隧道由发出命令的一方建立,那么这条隧道就叫做正向隧道。在SSH命令中用参数-L
表示。
例如,想要在Server V的8955
端口和Server C1的22
端口之间搭建一条正向隧道,使得访问Server V的8955
端口就像访问Server C1的22
端口一样,就应该在Server V的终端使用如下命令:
ssh -N -L 8955:localhost:22 <credential>
<credential>
指在~/.ssh/config
中配置对方的的登录凭据。也可以使用<user>@<hostname>
的形式。注意,这个凭据仅用于建立SSH隧道的身份认证用。在使用SSH隧道时,可以为任何用户。
在这条命令中,-N
选项代表在执行命令后不进入目标机器的终端。-L
选项表示建立正向隧道,它的参数是这条隧道的正向表示,即从发出命令的端口(8955
)传输数据到接收命令的机器和端口(localhost:22
)。
反向隧道
假如一条隧道由接收并执行命令的一方建立,那么这条隧道就叫做反向隧道。在SSH命令中用参数-R
表示。
例如,想要在公网服务器的8933
端口和防火墙之下的Server V的22
端口之间搭建一条反向隧道,使得访问公网服务器的8933
端口就像访问Server V的22
端口一样,就应该在Server V的终端使用如下命令:
ssh -N -R 8933:localhost:22 <credential>
<credential>
指在~/.ssh/config
中配置对方的的登录凭据。也可以使用<user>@<hostname>
的形式。注意,这个凭据仅用于建立SSH隧道的身份认证用。在使用SSH隧道时,可以为任何用户。
在这条命令中,-N
选项代表在执行命令后不进入目标机器的终端。-R
选项表示建立反向隧道,它的参数是这条隧道的正向表示,即从发出命令的端口(8933
)传输数据到接收命令的机器和端口(localhost:22
)。
建立SSH隧道实现内网服务器公网访问
根据正向隧道和反向隧道的原理,我们可以:
在
Server V
上配置一条到公网服务器的反向隧道,实现公网访问Server V
在
Server V
上配置数条到内网服务器的正向隧道,实现一台服务器代表所有内网服务器在
Server V
上配置数条到公网服务器的反向隧道,实现公网访问所有内网服务器
基于以上,即可实现访问公网服务器的不同SSH 端口来连接到不同的内网服务器。
但是,还有一些杂项需要注意:
开启SSH服务器作为网关配置
默认的sshd
配置中,在建立一条隧道之后,如果使用netstat -tuln
命令来查看端口占用的话,会发现SSH服务仅监听了本机传入的TCP连接:
tcp 0 0 127.0.0.1:8955 localhost:* LISTEN
这是因为:sshd
配置中,GatewayPorts
默认是关闭的。这会使得建立隧道时,仅允许本机连接该端口。使用管理员权限修改sshd
配置文件:
vim /etc/ssh/sshd_config
找到GatewayPorts
一行,将注释去除并将no
改为yes
即可。
然后重新加载SSH服务:
sudo systemctl reload sshd
或者,重启SSH服务:
sudo systemctl restart sshd
使用autossh保持连接活跃
一旦出现网络波动或者sshd
进程被清理,隧道就永久性地断掉了,而autossh
可以自动断线重连,保持隧道的健壮。
autossh
与ssh
的使用方法完全对等,仅需将程序名称改为autossh
,例如:
autossh -N -R 8933:localhost:22 <credential>
同时,还可以加入ssh
客户端配置,使得连接保持活跃,无需断线重连:
autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -N -R 8933:localhost:22 <credential>
使用脚本一键开启所有所需隧道
综上,针对整个网络拓扑,我们可以有这样一个脚本,仅需在Server V
上运行一次,就可以在公网访问所有内网服务器:
#!/bin/bash
nohup autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -N -R 8933:localhost:22 <public-credential> > autossh_public.log 2>&1 &
nohup autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -N -L 8944:localhost:22 <Server-V-credential> > autossh_serverv.log 2>&1 &
nohup autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -N -R 8944:localhost:8944 <public-credential> > autossh_public_8944.log 2>&1 &
nohup autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -N -L 8955:localhost:22 uoa > autossh_serverc1.log 2>&1 &
nohup autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -N -R 8955:localhost:8955 <public-credential> > autossh_public_8955.log 2>&1 &
nohup autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -N -L 8966:localhost:22 uoa2 > autossh_serverc2.log 2>&1 &
nohup autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -N -R 8966:localhost:8966 <public-credential> > autossh_public_8966.log 2>&1 &
关闭服务
当不需要SSH隧道时,可能需要将其关闭。但是由于进程运行在后台,且nohup
命令使得即使退出终端也不会结束进程,我们需要获取autossh
的PID
手动关闭。
使用ps
命令查看autossh
所有进程的详细信息:
ps aux | grep autossh
使用pgrep
查看autossh
的所有PID
:
pgrep autossh
若要一键杀掉所有autossh
的进程,可以结合xargs
和kill
命令:
pgrep autossh | xargs kill