Cycoe@Home

反向 SSH 实现内网穿透

最近实验室又配置了一台新的计算平台,噪声堪比飞机起飞,再加上本校区不允许实验过夜, 只能将其安置在学校外面。但从学校到计算平台车程 40 Km,每次都过去做计算显然不现实, 如果能从学校直接远程连接是最好的。

通过相关资料的查询及对专业人士的请教,找到如下几种解决方法:

  1. 联系网络运营商将动态 IP 转为静态的公网IP,这样就可以直接从学校的内网连接到计 算平台,是最完美的解决方案。但是查询资费后发现,价钱不那么完美,一个月6000 块 大洋怎么负担得起啊!
  2. 主机 A 使用 TeamViewer 连接至计算平台局域网下的另一台主机 B,再控制 B 使用 SSH连接至计算平台。这种方法包含几个明显的弊端:
    1. 操作麻烦,需要经过两层的文件传输及控制;
    2. 虽然 TeamViewer有个人的免费使用许可,但最近审查日益严格,经常使用几分钟就 被强制下线;
    3. TeamViewer 基于 GUI 远程控制,对网络要求很高;
    4. 最重要的一点,一个好的解决方案应该形成一个"黑箱",而不应该把内部细节暴露 给用户;
  3. 从计算平台使用反向 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网络地址转换),因此外网的主机无法直接 访问内网的主机。

在我们本次遇到的问题中,网络连接可表示为如下的拓扑结构。

topology.png
Figure 1: 网络拓扑分析

各个主机的 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

4 参考文献

Author: Cycoe (cycoejoo@163.com)
Date: <2019-04-30 Tue 23:42>
Generator: Emacs 28.0.50 (Org mode 9.3)
Built: <2020-05-21 Thu 20:09>