linux-ssh-nat

使用ssh实现内网穿透

反向连接:穿透防火墙访问内网机器

很多单位的机器都身处内网。内网 IP 是下面的样子,用 ifconfig 便可搞清楚。

110.0.0.0~10.255.255.255
2172.16.0.0~172.31.255.255
3192.168.0.0~192.168.255.255

有关内网 IP 的内容可以参考百度百科。跟内网 IP 对立的就是公网 IP(即凡是不是内网 IP 的 IP),它如同独一无二的信箱,世界各地的人们都能往这个地址寄东西。内网 IP 如同大杂院里的住户,东西只能寄到传达室,而不能直接送达。

内网机器可以访问公网机器,但是反之却不成立。譬如,公司里的机器 A 是内网机器,我们不能在公司外直接访问机器 A。但如果,在 A 和某台公网机器 C 之间建立一条加密隧道,便可以用任何一台机器 SSH 登录 A,就如同在 A 机器所处内网的防火墙上凿穿了一个洞。用下面的图来表达这个想法更直观一些。

1    |
2A <-|-> C <--- B
3    |
4内  防   公      任
5网  火   网      何
6机  墙   机      机
7器       器      器

反向连接

首先简单地介绍一下“反向连接”。在内网机器 A 上运行

1me~$ ssh -f -N -R 10000:localhost:22 username@C机器的公网IP

此处,10000 和 22 皆是端口号。上面的命令是由机器 A 发起,主动将 A 的端口 22 与机器 C 的端口 10000 绑定(这不会给 C 带来危险,对 A 的“投怀送抱”,C 是不拒绝的),即将本地端口 22(sshd 缺省的开放端口)映射为远程机器 C 的端口 10000。C 可以通过命令

1sockstat -l
2或者
3netstat -an

监听到本地端口 10000 被绑定。这样,在 A 和 C 之间就建立了一条加密隧道,机器 C 通过命令

1me~$ ssh username@localhost -p 10000

穿透内网机器 A 的防火墙 SSH 登录 A。这便是“反向连接”,它是穿透防火墙的常用方法。

为什么需要 A 主动联系 C 呢?这是因为,A 对 C 来说是不可见的,如果不对 A 进行端口映射,C 压根找不到 A。如今,A 主动献身将自己的某端口与 C 的某端口“合体”,C 通过这个“合体”就能做到远程控制 A。机器 C 为了安全起见,可以把 username 设置为机器 C 上的没有 shell 权限的用户。

公钥验证

为了让 C 能验证 A 的身份,A 产生一把公钥(public key)送给 C,这样每次连接 C 都不再需要输入密码了。而且,这样的公钥验证比设普通密码要安全得多。顾名思义,公钥是可以公开的,你可以大胆地告诉全世界你的公钥,把你的公钥列入 authorized_keys 的人们就能够安全地确认并接纳那个举世无双独一无二的你。公钥不会泄漏你的个人密码以及私人信息。

譬如,A 产生加密类型为 RSA、长度为 1024 比特的公钥的命令是

1me~$ ssh-keygen -t rsa -b 1024

这个命令将产生两个文件:公钥文件 id_rsa.pub 和私钥文件 id_rsa,将之存放在 $HOME/.ssh/ 里。然后,A 只要把公钥文件 id_rsa.pub 发给 C 就可以了。而 C 将之加入 $HOME/.ssh/authorized_keys,即

1me~$ cat id_rsa.pub >> ~/.ssh/authorized_keys

SSH 登录

现在,从任何一台机器 B 都可以 SSH 远程登录机器 A 了。只需要

1me~$ ssh me@C机器的公网IP -p 10000

这种方法的缺点是不稳定,有时候从 A 到 C 的端口映射会断掉。我们不可能从家里跑到单位再敲一次命令,所以搞一个自动检查连接状态的脚本是必须的。定时地(譬如说每半个小时)检查 A ---> C 是否存在,如果不存在,马上再建立一个连接。

将下面的脚本文件存为 $HOME/Reverse_SSH.sh,让它是可执行的。

1#!/usr/local/bin/bash
2while true; do
3  RET=`ps ax | grep "ssh -fNR 10000:localhost:22" | grep -v "grep"`
4  if [ "$RET" = "" ]; then
5    echo "The recent SSH failure occured at `date`" > ~/reverse_ssh.log  
6    echo "Restart reverse SSH!"
7    ssh -fNR 10000:localhost:22 username@C机器的公网IP 
8  fi

利用 crontab 让机器每隔 30 分钟就检查一次这个映射是否存在,若不存在,马上再次建立映射。具体过程是

1me~$ crontab -e

将下面的命令加入 crontab

1*/30 * * * * $HOME/Reverse_SSH.sh