使用SSH反向隧道进行内网穿透
对应的情况
这篇文章主要介绍了如何利用SSH 反向隧道穿透NAT,并演示了如何维持一条稳定的SSH 隧道。
假设有机器A 和B,A 有公网IP,B 位于NAT 之后并无可用的端口转发,现在想由A 主动向B 发起SSH 连接。由于B 在NAT 后端,无可用公网IP + 端口 这样一个组合,所以A 无法穿透NAT,这篇文章应对的就是这种情况。
首先有如下约定,因为很重要所以放在前面:
机器代号 | 机器位置 | 地址 | 账户 | ssh/sshd 端口 | 是否需要运行sshd |
---|---|---|---|---|---|
A | 位于公网 | usera | 22 | 是 | |
B | 位于NAT之后 | userb | 22 | 是 | |
C | 位于NAT之后 | userc | 22 | 否 |
这里默认你的系统 init 程序为
systemd
,如果你使用其他的 init 程序,如果没有特殊理由还是换到一个现代化的GNU/Linux 系统吧……
SSH 反向隧道
这种手段实质上是由B向A主动地建立一个SSH隧道,将A的6766端口转发到B的22端口上,只要这条隧道不关闭,这个转发就是有效的。有了这个端口转发,只需要访问A的6766 端口反向连接B即可。
首先在B上建立一个SSH隧道,将A的6766端口转发到B的22端口上:
B $ ssh -p 22 -qngfNTR 6766:localhost:22 usera@a.site
然后在A上利用6766端口反向SSH到B:
A $ ssh -p 6766 userb@localhost
要做的事情其实就是这么简单。
隧道的维持
稳定性维持
然而不幸的是SSH连接是会超时关闭的,如果连接关闭,隧道无法维持,那么A就无法利用反向隧道穿透B所在的NAT了,为此我们需要一种方案来提供一条稳定的SSH反向隧道。
一个最简单的方法就是autossh
,这个软件会在超时之后自动重新建立SSH隧道,这样就解决了隧道的稳定性问题,如果你使用Arch Linux
,你可以这样获得它:
$ sudo pacman -S autossh
下面在B上做之前类似的事情,不同的是该隧道会由autossh
来维持:
B $ autossh -p 22 -M 6777 -NR 6766:localhost:22 usera@a.site
-M
参数指定的端口用来监听隧道的状态,与端口转发无关。
之后你可以在A上通过6766端口访问B了:
A $ ssh -p 6766 userb@localhost
隧道的自动建立
然而这又有了另外一个问题,如果B重启隧道就会消失。那么需要有一种手段在B每次启动时使用autossh
来建立SSH隧道。很自然的一个想法就是做成服务,之后会给出在systemd
下的一种解决方案。
“打洞”
之所以标题这么起,是因为自己觉得这件事情有点类似于UDP打洞,即通过一台在公网的机器,让两台分别位于各自NAT之后的机器可以建立SSH连接。
下面演示如何使用SSH反向隧道,让C连接到B。
首先在 A 上编辑sshd
的配置文件/etc/ssh/sshd_config
,将GatewayPorts
开关打开:
GatewayPorts yes
然后重启sshd
:
A $ sudo systemctl restart sshd
然后在B上对之前用到的autossh
指令略加修改:
B $ autossh -p 22 -M 6777 -NR '*:6766:localhost:22' usera@a.site
之后在C上利用A的6766端口SSH连接到B:
C $ ssh -p 6766 userb@a.site
至此你已经轻而易举的穿透了两层NAT。
最终的解决方案
整合一下前面提到的,最终的解决方案如下:
首先打开A上sshd
的GatewayPorts
开关,并重启sshd
(如有需要)。
然后在B上新建一个用户autossh
,根据权限最小化思想,B上的autossh
服务将以autossh
用户的身份运行,以尽大可能避免出现安全问题:
B $ sudo useradd -m autossh
B $ sudo passwd autossh
紧接着在B上为autossh用户创建SSH密钥,并上传到A:
B $ su - autossh
B $ ssh-keygen -t 'rsa' -C 'autossh@B'
B $ ssh-copy-id usera@a.site
注意该密钥不要设置密码,也就是运行ssh-keygen
指令时尽管一路回车,不要输入额外的字符。
然后在B上创建以autossh用户权限调用autossh
的service
文件。将下面文本写入到文件/lib/systemd/system/autossh.service
,并设置权限为644:
[Unit]
Description=Auto SSH Tunnel
After=network-online.target
[Service]
User=autossh
Type=simple
ExecStart=/bin/autossh -p 22 -M 6777 -NR '*:6766:localhost:22' usera@a.site -i /home/autossh/.ssh/id_rsa
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always
[Install]
WantedBy=multi-user.target
WantedBy=graphical.target
在B上让network-online.target
生效:
B $ systemctl enable NetworkManager-wait-online
如果你使用
systemd-networkd
,你需要启用的服务则应当是systemd-networkd-wait-online
。
然后设置该服务自动启动:
B $ sudo systemctl enable autossh
如果你愿意,在这之后可以立刻启动它:
B $ sudo systemctl start autossh
然后你可以在A上使用这条反向隧道穿透B所在的NAT SSH连接到B:
A $ ssh -p 6766 userb@localhost
或者是在C上直接穿透两层NAT SSH连接到B:
C $ ssh -p 6766 userb@a.site
如果你对SSH足够熟悉,你可以利用这条隧道做更多的事情,例如你可以在反向连接时指定动态端口转发:
C $ ssh -p 6766 -qngfNTD 7677 userb@a.site
假设C是你家中的电脑,A是你的VPS,B是你公司的电脑。如果你这样做了,那么为浏览器设置端口为7677
的sock4
本地(localhost)代理后,你就可以在家里的浏览器上看到公司内网的网页。
本文转载自:https://arondight.github.io/2016/02/17/使用SSH反向隧道进行内网穿透/