使用ssh打洞实现反向代理访问内网服务

不得不佩服ssh的功能之强大,各种隧道代理,n中玩法,看是很复杂的网络包转发处理,一条ssh命令搞定,真乃神器也!

我家里有个非常省电的cubieboard小主机,原来折腾过一段时间的硬件,后来就凉在一边也没再管了,最近想反正一个月也就耗1-2度点,何不用它跑些对外的服务。于是乎拿Python写了个小的web系统。

现在问题来了,我的主机是在自家的路由器环境下的,ip是192.168.1.xx,这就不能对外开放了呀(也许有人会说设置路由器的DMZ主机呀,我能说我用的方正带宽即便是设置DMZ主机,联通或者移动的网络依然无法访问么,且路由器的公网ip是动态分配的呀,有点啰嗦了,总是DMZ主机也搞不定)。

不过,我手头上有一台阿里云的主机,这个主机是对公网开放的,只要能上网哪都能访问得到。突然想到,小主机可以连通阿里云的机器,理论上来说我可以通过阿里云的机器访问到小主机啊。

我在14年10月份左右尝试过通过TCP协议打洞的原理来达到这一目的,貌似路由器上有限制,最后以失败告终。

最近在新单位经常使用ssh做socks代理访问内网的服务,心想,如果反过来应该也是可以的吧,于是研究了下ssh的几种常用代理方式: http://www.cnblogs.com/wangkangluo1/archive/2011/06/29/2093727.html

最终找到了一条路子。

假设我内网的小主机为A,阿里云的主机为B,A可以连通B,A->B,但是B无法连接到A。

我们要实现B->A,可以通过ssh的-R参数实现。

在A上执行:ssh -R 88:localhost:80 user@B,这条命令表示将B上的88端口映射到A的80端口上。

在B上执行netstat -an | grep 88,可以看到如下结果:

tcp        0      0 127.0.0.1:88            0.0.0.0:*               LISTEN  

由于我映射的是个网站,所以我在B上执行curl http://localhost:88可以看到正常返回的html内容。

不过现在还有个问题,B上的88端口绑定的是127.0.0.1这个ip,依然是无法通过公网ip加端口的形式访问。如果有域名的话,可以通过配置nginx代理访问这个端口。

如果想通过公网ip:port的形式访问,就需要改一下B机器的ssh服务配置了,编辑/etc/ssh/sshd_config,在文件的最后添加GatewayPorts yes,通过/etc/init.d/ssh restart重启ssh服务。最后在A上重新执行:ssh -R 88:localhost:80 user@B

现在就可以通过http://ip-of-b:88来访问内网小主机A上的web服务了。

设置后台执行

下面是一个参考脚本,假设脚本名为hole.sh

后台执行ssh命令的关键参数为:-CfNgqP

#!/bin/bash

ps -ef | grep 10080 | grep -v grep | awk '{print $2}' | xargs kill

ssh -CfNgqP \  
-R 10080:localhost:80 \
<user>@<your server>  

虽然可以在后台执行了,不过难免有网络异常导致ssh进程退掉,我们可以在搞个脚本放到crontab中进行检测并自动恢复:

#!/bin/bash

proc=$(ps -ef | grep 10080 | grep -v grep)

if [ -z "$proc" ]; then  
    sh /home/cubie/hole.sh
fi  

嗯,这么搞就比较妥了,也不用啥额外的软件。

有个国内的开发者用go实现了相同功能的项目:有兴趣可以研究下:https://github.com/Lupino/hole