本文将教你如何通过 SSH 隧道把本地服务映射到外网,以方便调试,通常把这种方法叫内网穿透。
目的
把运行在本地开发机上的 SSH 服务(或其他服务如HTTP等)映射到外网,让全世界都能通过外网 IP 服务到你本地开发机上 的SSH、HTTP 服务。例如你本地的 HTTP 服务监听在 127.0.0.1:22
,你有一台公网 IP 为 12.34.56.78
的服务器,通过本文介绍的方法,可以让全世界的用户通过 http://12.34.56.78:22
访问到你本地开发机上的 SSH 服务。
前提条件
为了把内网服务映射到外网,以下资源为必须的:
- 一台有外网 IP 的服务器;
- 能在本地开发机上通过
ssh
登入到外网服务器。
要满足以上条件很简单:
- 对于条件1:购买一台低配 Linux 服务器,推荐国外的 DigitalOcean;
- 对于条件2:对于 Mac、Linux 开发机是内置了 ssh 客户端的,对于 Windows 可以安装 Cygwin。
公网IP申请
去网上申请服务器,一般都会收费,如阿里云、腾讯云、百度云等。在这里推荐由世纪互联运营的微软Azure云服务器,1元体验一个月,申请与部署方式很简单,而且服务器配置与带宽很高,可惜的是就能使用一个月,其实学服务器开发的大学朋友们也可以申请一个练练手,毕竟这是一次难得的实践机会嘛!申请免费使用网址如下https://www.azure.cn/pricing/1rmb-trial-full/。
下面这个操作尤为重要,部署成功服务器后,务必参照微软的文档为服务器添加入站与出站规则,也就是哪些类型的数据包可以进出你的服务器,那些类型的数据包会被防火墙拦截,这个步骤决定着伪装的数据包是否能够进入到我们的VBS。然后我们需要建立linux虚拟机,性能不需要太好,因为她的功能只是一个桥接的作用。虚拟机申请完成后我们就会得到系统自动分配的一个公网IP地址,通过远程连接软件我们可以远程连接到这台公网的服务器。我们想要作的是将内网中的机器映射到外网,然后我们可以在公网上通过连接这台服务器连接到我们的这台内网计算机。最简单的实现方式就是利用SSH隧道进行端口映射。
实现原理
SSH 隧道就像一根管道,能把任何2台机器连接在一起,把发送到其中一台机器的数据通过管道传输到另一台机器。假如已经通过 SSH 隧道把本地开发机和外网服务器连接在了一起,外网服务器端监听在 12.34.56.78:8080
,那么所有发给 12.34.56.78:8080
的数据都会通过 SSH 隧道原封不动地传输给本地开发机的 127.0.0.1:8080
,如图所示:
也就是说,去访问 12.34.56.78:22
就像是访问本地开发机的 127.0.0.1:22
,本地开发机上的 22 端口被映射到了外网服务器上的 22 端口。
如果你的外网服务器 IP 配置了域名解析,例如 yourdomin.com
会通过 DNS 解析为 12.34.56.78
,那么也可以通过 yourdomin.com:8080
去访问本地开发机上的服务。这样就做到了访问外网地址时其实是本地服务返回的结果。
备注:通过 SSH 隧道传输数据时,数据会被加密,就算中间被劫持,黑客也无法得到数据的原内容。所以 SSH 隧道还有一个功能就是保证数据传输的安全性。
实现步骤
把本地开机和外网服务器通过 SSH 隧道连接起来就和在本地开发机 SSH 登入远程登入到外网服务器一样简单。
先来回顾以下 SSH 远程登入命令,假如想在本地远程登入到 12.34.56.78
,可以在本地开发机上执行以下命令:
ssh [email protected]
而实现 SSH 隧道只需在本地开发机上执行:
ssh -R 2000:127.0.0.1:22 [email protected]
如果想同时映射多个端口则可以执行:
ssh [email protected] -R 2000:127.0.0.1:22 -R 8081:127.0.0.1:8081
可以看出实现 SSH 隧道的命令相对于 SSH 登入多出来 -R 8080:127.0.0.1:22
,多出的这部分的含义是:
在远程机器(12.34.56.78
)上启动 TCP 8080端口监听着,再把远程机器(12.34.56.78
)上8080端口映射到本地的127.0.0.1:22
。
执行完以上命令后,就可以通过 12.34.56.78:8080
去访问本地的 127.0.0.1:22
了。
通常把这种技术叫做 SSH 远程端口转发(remote forwarding)。
其实不限于只能把本地开发机上运行的服务映射到外网服务器上去,还可以把任何本地开发机可以访问的服务映射到外网服务器上去。例如在本地开发机上能访问 github.com:80
,在本地开发机上执行:
ssh -R 8080:github.com:80 [email protected]
就能通过 12.34.56.78:8080
去访问 github.com:80
了。
保持运行
在执行完上面介绍的 SSH 隧道命令后,你会发现登入到了外网服务器上去了,如果你登出外网服务器,就会发现 12.34.56.78:8080
无法访问了。导致这个问题的原因是你登出外网服务器时,在外网服务器上本次操作对应的 SSH 进程也跟着退出了,而这个退出的进程曾负责监听在 8080 端口进行转发操作。
为了让 SSH 隧道一直保持在后台执行,有以下方法。
通过 SSH 自带的参数
SSH 还支持这些参数:
- N参数:表示只连接远程主机,不打开远程shell;
- T参数:表示不为这个连接分配TTY;
- f参数:表示连接成功后,转入后台运行;
因此要让 SSH 隧道一直保持在后台执行,可以通过以下命令:
ssh -NTf -R 8080:127.0.0.1:8080 [email protected]
OpenSSH基于安全的理由,如果用户连线到SSH Server后闲置一段时间,SSH Server会在超过特定时间后自动终止SSH连线。
用 ssh 命令连接服务器之后,如果一段时间不操作,再次进入 Terminal 时会有一段时间没有响应,然后就出现错误提示:
Write failed: Broken pipe
只能重新用 ssh 命令进行连接。
解决方法
方法一:如果您有多台服务器,不想在每台服务器上设置,只需在客户端的 ~/.ssh/ 文件夹中添加 config 文件,并添加下面的配置:
ServerAliveInterval 60
方法二:如果您有多台个人管理服务器,不想在每个客户端进行设置,只需在服务器的 /etc/ssh/sshd_config 中添加如下的配置:
ClientAliveInterval 60
方法三:如果您只想让当前的 ssh 保持连接,可以使用以下的命令:
$ ssh -o ServerAliveInterval=60 user@sshserver
但是有时候还是会出问题,这个可能就是由于网络不稳定造成的问题,我们想让SSH隧道不会调线就需要在网络不稳定的时候让他自己去自动连接,重新建立通信隧道。然后时刻保持连接,这个时候AutoSSH这个小软件就是个不错的选择。
通过 AutoSSH
SSH 隧道是不稳定的,在网络恶劣的情况下可能随时断开。如果断开就需要手动去本地开发机再次向外网服务器发起连接。
AutoSSH 能让 SSH 隧道一直保持执行,他会启动一个 SSH 进程,并监控该进程的健康状况;当 SSH 进程崩溃或停止通信时,AutoSSH 将重启动 SSH 进程。
使用AutoSSH 只需在本地开发机上安装 AutoSSH ,方法如下:
- Mac 系统:
brew install autossh
; - Linux 系统:
apt-get install autossh
;
安装成功后,在本地开发机上执行:
autossh -N -R 8080:127.0.0.1:8080 [email protected]
就能完成和上面一样的效果,但本方法能保持 SSH 隧道一直运行。
可以看出这行命令和上面的区别在于把 ssh
换成了 autossh
,并且少了 -f
参数,原因是 autossh 默认会转入后台运行。
其它代替方案
除了 SSH 隧道能实现内网穿透外,还有以下常用方法。
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp, http, https 协议。
frp 有以下特性:
- frp 比 SSH 隧道功能更多,配置项更多;
- frp 也需要一台外网服务器,并且需要在外网服务器上安装 frps,在本地开发机上安装 frpc;
ngrok 是一个商用的内网穿透工具,它有以下特点:
- 不需要有外网服务器,因为 ngrok 会为你提供;
- 只需要在本地开发机安装 ngrok 客户端,和注册 ngrok 账户;
- 按照服务收费;
这些代替方案的缺点在于都需要再额外安装其它工具,没有 SSH 隧道来的直接。
想了解更多可以访问它们的主页。
总结
举两个例子具体说明一下ssh隧道建立的具体命令参数
ssh的确很强大,可以很方便的实现从本地端口到远程端口的映射,通常情况下使用 -L 或者 -R参数,例如:
ssh [email protected] -L 8023:RemoteIP:23
-L 将本地的某个端口映射到远程主机的某个端口上,上例中就是将本地的8023端口映射到远程主机的23号端口上,这样就可以直接telnet本机的8023端口来访问远程主机了。
但是需要说明的是RemoteIP可以是127.0.0.1,此时127.0.0.1指的是远程计算机,而非本机地址。
-R 则正好与-L相反,它将远程主机的某个端口映射到本地的某个端口上,例如:
ssh [email protected] -R 8023:LocalIP:23
上例将远程主机的8023端口映射到本机的23号端口,这样远程主机就可以telnet 自己的8023端口来访问本地主机了。
最后-D参数将在本地开启一个socks5代理端口,该端口接收到的数据将通过加密隧道传输到远程主机,并有远程主机代理发出,例如:
ssh [email protected] -D 7070
上例在本地开启一个7070号socks5代理,浏览器等应用程序可以使用该端口通过远程主机访问网络。
需要说明的是,-D参数开启的socks代理,在我的机器上好像只有Chrome能使用,IE不能使用,原因未明,估计是DNS解析的问题。
开发中经常需要外网服务映射到本机内网服务的需要,便于调试。
以前都是同事帮着配,这两天自己也看了一下 ssh 端口转发。 同事分分钟钟搞定的事情,自己折腾了 2 天, 真是弱爆了。
最初老想不明白一件事,为什们外网服务器能够找到我的内网机器,现在才明白原来走的是 ssh 隧道。
需求我的内网机器 192.168.9.100, 我的阿里云外网 123.56.86.52, 现在需要所有对 123.56.86.52 80 端口的访问都映射到 192.168.9.100 的 80 端口。
显然 123.56.86.52 访问不到 192.168.9.100, 但 192.168.9.100 能访问到 123.56.86.52 , 所以很简单 ssh 建立一个远程端口转发就行了。
1 在 192.168.9.100 上执行
ssh -N -v -R 3000:127.0.0.1:80 [email protected]
-R 表示远程转发, 这句话的意思是 通过 ssh 连接到 123.56.86.52,让 123.56.86.52 监听自己的 3000 端口, 所有通过 3000 端口的数据都通过 ssh 转发到 127.0.0.1 的 80 端口。
这里 127.0.0.1 就是 192.168.9.100 这个机器。 在 ssh 连接的时候通道就建立了, 以后所有的通信都走的是这个通道。
2 既然 123.56.86.52 已经监听在 3000 端口了, 接下来就非常简单了, 服务器上用 nginx 做一个反向代理, 把 80 端口代理到 3000 端口就行了
参考文献
https://github.com/gwuhaolin/blog/issues/11
https://www.cnblogs.com/zlgxzswjy/p/9796671.html
https://www.cnblogs.com/wumz/p/9721666.html