反向 SSH 实现内网穿透
最近实验室又配置了一台新的计算平台,噪声堪比飞机起飞,再加上本校区不允许实验过夜, 只能将其安置在学校外面。但从学校到计算平台车程 40 Km,每次都过去做计算显然不现实, 如果能从学校直接远程连接是最好的。
通过相关资料的查询及对专业人士的请教,找到如下几种解决方法:
- 联系网络运营商将动态 IP 转为静态的公网IP,这样就可以直接从学校的内网连接到计 算平台,是最完美的解决方案。但是查询资费后发现,价钱不那么完美,一个月6000 块 大洋怎么负担得起啊!
- 主机 A 使用 TeamViewer 连接至计算平台局域网下的另一台主机 B,再控制 B 使用
SSH连接至计算平台。这种方法包含几个明显的弊端:
- 操作麻烦,需要经过两层的文件传输及控制;
- 虽然 TeamViewer有个人的免费使用许可,但最近审查日益严格,经常使用几分钟就 被强制下线;
- TeamViewer 基于 GUI 远程控制,对网络要求很高;
- 最重要的一点,一个好的解决方案应该形成一个"黑箱",而不应该把内部细节暴露 给用户;
- 从计算平台使用反向 SSH 连接到云服务器,从而建立起从云服务器到计算平台的隧道连 接。这种方法传输的速度上限在于云服务器的带宽,最大的优点在于实现了一个“鸭子类 型”,使用 SSH 连接计算平台与连接局域网内的主机无异。
1. SSH 协议与应用
SSH 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定; SSH 为建立在应用层基础上的安全协议。SSH是目前较可靠,专为远程登录会话和其他网 络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。 SSH 最初是 UNIX系统上的一个程序,后来又迅速扩展到其他操作平台。SSH 在正确使用 时可弥补网络中的漏洞。SSH 客户端适用于多种平台。几乎所有 UNIX 平台—包括 HP-UX、Linux、AIX、Solaris、Digital UNIX、Irix,以及其他平台,都可运行 SSH。
以上对 SSH 的解释来自百度百科,别问我为什么不用 Wikipedia。从中我们看到,SSH 是 建立在应用层基础上的一种安全协议,可以用来进行远程控制,或在计算机之间传送文件, 比传统的 telnet 或 ftp 协议要更安全。Linux 下最常使用的就是 OpenSSH。OpenSSH 是 SSH 协议的免费开源实现,提供了服务端后台程序(SSH Daemon)和客户端工具(SSH Client)。主机可以通过 SSH 客户端连接运行了 SSH 服务端的主机。
2. 网络拓扑分析
互联网是一张巨大的网,其中所有的主机都由有线或无线的方式相连,但并不代表任意两台 主机都能互相可见。网络是有方向的,与其说是网我觉得更像一棵树。网络在向下进行传输 时,会经过NAT(Network Address Translation网络地址转换),因此外网的主机无法直接 访问内网的主机。
在我们本次遇到的问题中,网络连接可表示为如下的拓扑结构。
各个主机的 IP 地址如下表所示
主机名 | IP | 用户名 | 备注 |
---|---|---|---|
计算平台 A | 10.0.0.100 | cal | 目标主机,处于内网 |
控制端 B | 192.168.1.100 | client | 控制主机,处于内网 |
云服务器 O | 123.123.123.123 | server | 公网服务器,起桥梁作用 |
其中 A、B 能够访问 O,但 O 不能访问 A、B,并且 A、B 之间不能直接互相访问。我们最 后要实现的目标就是使用 B 访问 A。
3. 解决方法
通俗地说:就是在主机 A 上做到 O 的反向代理;然后在 O 上做正向的代理实现本地端口 的转发。
3.1. 准备工作
A、B、O 上都要安装 SSH Client,A、O 上需要安装 SSH Daemon。
需要用到的 ssh 参数
# 反向代理 ssh -fCNR
# 正向代理 ssh -fCNL
-f 后台执行 ssh 指令 -C 允许压缩数据 -N 不执行远程指令 -R 将远程主机(服务器)的某个端口转发到本地端指定主机的指定端口 -L 将本地机(客户机)的某个端口转发到远端指定主机的指定端口 -p 指定远程主机的端口
3.2. 反向代理
首先在 A 主机上操作,建立 A 到 O 的反向代理,具体命令为
ssh -fCNR [O 主机 IP 或省略]:[O 主机端口]:[A 主机 IP ]:[A 主机端口] [O 主机的用户名@O 主机 IP]
在这里我使用了 O 主机的 3333 端口,以及 A 主机的 22 端口,按照以上命令即为
ssh -fCNR 3333:localhost:22 server@123.123.123.123
此时我们就将 O 的 3333 端口映射到了 A 的 22 端口,也就是说访问 O 的 3333 端口与 访问 A 的 22 端口效果相同。
使用 ps aux | grep 3333
命令来查看反向代理是否运行成功。
3.3. 正向代理
接下来在 O 主机上操作。完成上一步的反向代理后,在主机 O 上运行如下命令应该就可以 登陆 A 主机。
ssh -p 3333 cal@localhost
该命令的含义为,使用 cal 用户登陆本机的 3333 端口。上面已经说过,访问 O 的 3333 端口与访问 A 的 22 端口效果相同,那么我们就成功从 O 主机登陆了 A 主机。
但此时我们仍只能从 O 主机本机上登陆 A 主机,无法从其他主机登陆,因此需要再对 3333 端口进行一次端口转发,使其他主机可以访问。具体命令为
ssh -fCNL [O 主机 IP 或省略]:[转发端口]:[O 主机 IP]:[被转发端口] [登陆 O 主机的用户名@O 主机的 IP]
此处我们想要将 3333 端口转发到 2222 端口,因此转发端口为 2222,被转发端口为 3333。
ssh -fCNL *:2222:localhost:3333 localhost
此处 *
表示允许任意其他主机访问,本机的用户名可省略。使用 ps aux | grep 2222
命
令来查看正向代理是否运行成功。此处 2222 端口为本地转发端口,负责与外网进行通信,
并将数据转发到 3333 端口,实现了可以从其他主机访问的功能。
3.4. 展现奇迹的时候到了
到次为至,我们已经配置好了 A O 主机,那么我们可以从任意可联网的设备登录到计算平 台中去啦。指令为
ssh -p 2222 cal@123.123.123.123
我们实现了从任意地方连入内网计算平台的连接!
3.5. 这种反向代理是不稳定的
不幸的是这种 ssh 连接会因为超时和网络堵塞等原因而关闭,那么从的外网连通内网的通 道就无法维持了,为此我们需要维持稳定 ssh 反向代理隧道的方法。
3.5.1. 配置 ssh key 实现免密登陆
在使用 ssh 进行连接时,每次都需要输入密码,一方面不安全,另一方面也为我们接下来 使用的自动化工具带来了阻碍。
首先登陆 A 主机,并运行如下命令生成公钥私钥对。中间的配置过程一路回车即可。
ssh-keygen -t rsa -C "youremail@example.com"
此时就在 A 主机的 ~/.ssh/
目录下生成了公钥与私钥对,接下来我们需要将公钥传到 O
主机上作为我们登陆的一个比对凭证
ssh-copy-id server@123.123.123.123
此时,我们再使用 ssh server@123.123.123.123
登陆路 O 主机就不再需要密码了。
3.5.2. 用 autossh 建立稳定隧道
从 A 主机到 O 主机的反向代理连接是不稳定的,也就是说,我们需要一个工具来时刻监听 着这个连接。一但断开,再次自动重新建立连接即可。幸运的是,已经有人写出了这个工具, 那就是 autossh!感谢开源!
CentOS 的官方仓库中并没有这个软件包,我们需要从源码编译安装
sudo yum install wget gcc make
wget http://www.harding.motd.ca/autossh/autossh-1.4e.tgz
tar -xf autossh-1.4e.tgz
cd autossh-1.4e
./configure
make
sudo make install
至此我们已经安装好了 autossh,利用下面的命令来启动守护进程。
autossh -M 7200 -fCNR 3333:localhost:22 server@123.123.123.123
autossh 命令的参数与 ssh 一致,不同的是我们需要指出的 -M
参数,这个参数指定一个
端口,这个端口是外网的 O 主机用来接收内网 A 主机的信息,如果隧道不正常而返回给 A
主机让它实现重新连接。
3.5.3. 配置自动启动
我们需要将 autossh 命令配置在开机自动启动脚本中,免去了每次开机都需要重新运行脚 本的麻烦。
我们需要在 A 主机的 /etc/rc.d/rc.local
文件中添加如下内容
/bin/su -c '/usr/local/bin/autossh -M 7200 -fCNR 3333:localhost:22 server@123.123.123.123' - cal
此命令表示,以 cal 用户运行 autossh 命令,这样我们就能够使用 cal 用户反向连接到 A 主机。
为了保险起见,我们需要为自启动脚本添加执行权限
sudo chmod +x /etc/rc.d/rc.local
至此我们就建立好了稳定的连接隧道。
3.6. 进一步优化
3.6.1. 保持活连接
在测试中发现,如果一个连接长时间空置,那么就会冻结。反向代理连接也是一样,此时便 只能通过重启 A 主机使连接重置。为保持一个活连接(Keep alive),需要服务端或客户 端定时发送心跳包来确保连接活跃,此处我们选择配置服务端的心跳。
在 A 和 O 的 /etc/ssh/sshd_config
文件中添加如下内容
# server 每隔 60 秒发送一次请求给 client,然后 client 响应,从而保持连接 ClientAliveInterval 60 # server 发出请求后,客户端没有响应得次数达到 3,就自动断开连接,正常情况下,client 不会不响应 ClientAliveCountMax 3
3.6.2. Windows SSH Client 连接提示错误
使用 Windows 的 SSH Client 连接 Linux 运行的 SSH 服务端时,会提示 "ssh algorithm negotiation failed" 错误,导致此问题的原因是 ssh升级后,为了安全,默认不再采用原 来一些加密算法,我们手工添加进去即可。
在 O 主机的 /etc/ssh/sshd_config
文件中添加如下内容
# add crypt format for window ssh client Ciphers aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,3des-cbc,arcfour128,arcfour256,arcfour,blowfish-cbc,cast128-cbc MACs hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-ripemd160,hmac-sha1-96,hmac-md5-96 KexAlgorithms diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group1-sha1,curve25519-sha256@libssh.org