SSH 隧道自动重连

说点什么

由于公司的人分在两个地方工作,但是之前的 svn 服务器在只在一个地方的内网,除此之外还包括 JIRA、 QuickBuild 、以及公司私有的平台等东西需要访问。鉴于当时公司有可用的拥有公网IP的阿里云的主机,所以最简单的方案就就是把内网的一些端口通过 ssh 映射到外网主机上,以供非内网环境使用。我也知道有一些诸如VPN的解决方案或者企业固定IP等等。

但是感觉目前看起来 ssh 隧道是最简单也是最合适的方式了,不过感觉上有点不太安全,而且在接近快一年的使用中也没有什么太大的问题。

其思想是,你拥有一台 公网主机A xx:xx:xx:xx, 和一台 内网主机B yy:yy:yy:yy,外网的其他人只能访问A, 但是由于某些原因,外网的人必须要访问 主机B 上面的某几个端口,同时你不能也不可能把机器部署在公网主机里,有的时候仅仅可能是临时访问以下。

使用 ssh 隧道可以比较快速的解决这个问题,ssh 隧道可以将端口 yy:yy:yy:yy:pppp 映射到 xx:xx:xx:xx:dddd 上,完成之后,外网的人访问 xx:xx:xx:xx:dddd 相当于访问内网端口 yy:yy:yy:yy:pppp,主机A 转发了所有流量。

但是稍显麻烦的是,有的时候,可能因为网络波动,导致隧道断开需要手动连接。于是今天思考了下自动重连的方法,思想和具体实现也很简单,基本就是只是需要配置就行了。目前只是在自己的电脑上测试了下,还没有部署到公司的机器上。

动手

关于 ssh 的 public key 的远端部署这里不再详细阐述,更多请参考搜索引擎结果 (关键字: ssh 免密码登录,ssh-keygen) 或者参考 man sshAUTHENTICATION 节。

主要注意的是: 我个人非常强烈建议给 key 也设置上复杂的密码,通过 ssh-agent 或者 macOS 的 keychain 帮你手动填写密码。如果真的懒,那就另外建一个普通用户为其设置上无需密码的 key 用来转发端口,不要给 root 用户设置,监听高于 1024 的端口不需要 root 权限。

通过配置,使用 ssh + public key 的验证方式登录远端机器并且转发端口

打开本机文件:

$ sudo vi /etc/ssh/ssh_config

添加如下内容 (这些内容其他文章也有阐述):

Host <用作本地标识的主机名字>
HostName <远端机器域名或ip地址>
Port <远端机器ssh登入端>
User <登入用户名>
PreferredAuthentications publickey
IdentityFile /path/to/id_rsa # 请填绝对路径

另外再找到对于所有主机配置生效的配置:

Host *

再其下方加入配置:

# 转发失败时退出进程
ExitOnForwardFailure yes 
# 启用多个 ssh 会话复用同一个网络连接, 并把 socket 描述生成到对应目录
ControlMaster auto
ControlPath ~/.ssh/sockets/%n:%p

macOS 机器加入如下配置来启动 keychain:

UseKeychain yes
AddKeysToAgent yes

在对具体主机配置中配置对于端口转发配置,详细的参数作用请参考 man ssh:

RemoteForward 127.0.0.1:8888 127.0.0.1:8888
LocalForward 127.0.0.1:7777 127.0.0.1:7777

另外请不要忘记配置客户端自动给服务器发心跳包:

# 30秒发一次心跳,失败3次断开与服务器的连接
ServerAliveInterval 30
ServerAliveCountMax 3

/etc/ssh/ssh_config 改完之后可能长这个样子:

Host *
ExitOnForwardFailure yes 
ControlMaster auto
ControlPath ~/.ssh/sockets/%n:%p
ServerAliveInterval 30
ServerAliveCountMax 3

Host name
HostName hostname
Port 22
User your_username
PreferredAuthentications publickey
IdentityFile /path/to/id_rsa
RemoteForward 127.0.0.1:8888 127.0.0.1:8888
LocalForward 127.0.0.1:7777 127.0.0.1:7777

如果远端机器 public key 和本地配置都正确,输入下列命令即可登录到远端机器并且映射配置的端口:

$ ssh name

远端配置

远端机器修改以下文件:

sudo vi /etc/ssh/sshd_config

设置以下配置:

# 启用以支持端口监听在 0.0.0.0,否则为 localhost 
GatewayPorts yes 
# 30秒发一次心跳,失败3次断开与客户端的连接
ClientAliveInterval 30
ClientAliveCountMax 3

重启sshd:

$ service sshd restart

创建重启脚本

将脚本 reconnect_ssh.sh 仍在你喜欢的地方

#/usr/bin/env sh

HOSTNAME=$1
SOCKETFILE=$2

if [[ ! -a $SOCKETFILE ]]; then
    echo "connecting $HOSTNAME .."
    ssh -fN $HOSTNAME
    if [[ $? -ne 0 ]]; then
        echo "failed to connect $HOSTNAME" >&1
        exit 2
    fi
fi

执行这个脚本查看是否连接成功:

$ sh reconnect_ssh.sh <name> <socket_file_path>

根据上面的配置,当一个 ssh 连接成功后,你可以在 ~/.ssh/sockets 目录中找到 socket 符号文件,需要注意这个目录需要手动创建

成功后 ssh 会启动会启动在后台。

加入到定时任务

$ echo "0-59   *   *   *   *   sh /path/to/reconnect_ssh.sh <name> <socket_file_path>" > ~/.crontab
$ crontab ~/.crontab

停止任务:

$ crontab -r

最后

自动重连的机制实质就是生成 socket 符号文件,依据文件是否存在来判断隧道是否已经建立,如果通讯存在异常,则 ssh 自身的心跳则会检测到,两边都会正确地断开。ssh 客户端再伺机连接上。