一、ngrok简介及作用
ngrok 是一款用go语言开发的开源软件,它是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。下图简述了ngrok的原理。
ngrok 可捕获和分析所有通道上的流量,便于后期分析和重放。
这些看上去很麻烦,我们更需要的是了解ngrok的用途。
- 完美代替“花生壳”软件。
“花生壳”是一款老牌的内网穿透软件,一年的内网版服务要两百多块钱,都快可以买一年垃圾点的VPS服务器了。而免费版的“花生壳”稳定性较差,隔三差五的不能访问,每个月只有1G流量,以前做项目没少被坑。ngrok是一款免费开源的软件,稳定性极强,我曾做过测试,将ngrok客户端所在计算机的网络断开一阵子,再连接另一个网络,ngrok很快会自动重连,几乎不受影响。
- 用于对处在内网环境中,无外网IP的计算机的远程连接。
ngrok可以做TCP端口转发,对于Linux可以将其映射到22端口进行SSH连接。Windows的远程桌面可以将其映射到3389端口来实现。同理,如果要做MySQL的远程连接,只需映射3306端口即可。
- 用作临时搭建网站并分配二级域名,可用作微信二次开发的本地调试。
微信公众平台二次开发时,服务器必须要能通过外网访问,而且必须是80接口。我们一般会在自己的电脑上写代码,但是由于电信运营商将80端口屏蔽了,甚至很多人通过无线路由器上网,根本就没有公网ip。在这种情况下,我们每次都要上传代码到服务器对微信公众平台进行接口调试,十分的不方便。而ngro可以将内网映射到一个公网地址,这样就完美的解决了我们的问题。
ngrok官方为我们免费提供了一个服务器,我们只需要下载ngrok客户端即可正常使用,但是后来官方的服务越来越慢,直到ngrok官网被完全屏蔽。现在我们已经无法使用ngrok官方的服务器了。所以,接下来我们自行搭建属于自己的ngrok服务器,为自己提供方便快捷又稳定的服务,一劳永逸。
二、环境准备
VPS
这里以阿里云ECS为例,操作系统为CentOS7(64位)。
域名
将一个域名或二级域名泛解析到VPS服务器上。例如将*.tunnel.mydomain.com解析到VPS的IP。要注意,此时还需要将tunnel.mydomain.com的A记录设置为VPS的IP。
安装git
Shell
yum install git
#注意git版本应大于1.7.9.5
1
2
3
|
#安装git
yum install git
#注意git版本应大于1.7.9.5
|
配置go环境
下载go1.4.2源码包并解压:
Shell
wget http://www.golangtc.com/static/go/1.4.2/go1.4.2.linux-amd64.tar.gz
#解压到/usr/local/
tar -C /usr/local/ -zxf ./go1.4.2.linux-amd64.tar.gz
1
2
3
4
|
#下载go1.4.2源码包
wget http://www.golangtc.com/static/go/1.4.2/go1.4.2.linux-amd64.tar.gz
#解压到/usr/local/
tar -C /usr/local/ -zxf ./go1.4.2.linux-amd64.tar.gz
|
打开/ect/profile
Shell
#
#
vim /ect/profile
1
2
3
4
|
#配置profile
#
#
vim /ect/profile
|
在最后面添加:
Vim
export GOROOT=/usr/local/go
export PATH=$GOROOT/bin:$PATH
1
2
3
4
|
#添加下面两行代码
export GOROOT=/usr/local/go
export PATH=$GOROOT/bin:$PATH
|
保存并退出,然后编译/etc/profile文件,使之前的配置生效
Shell
#
#
source /ect/profile
1
2
3
4
|
#编译profile
#
#
source /ect/profile
|
查看go环境是否配置成功
Shell
#显示go version go1.4.2 linux/amd64则说明go环境配成功
#
go version
1
2
3
4
|
#查看go环境是否配置成功
#显示go version go1.4.2 linux/amd64则说明go环境配成功
#
go version
|
三、准备编译Ngrok
下载Ngrok源码包
Shell
cd ~
git clone https://github.com/inconshreveable/ngrok.git
cd ngrok/
1
2
3
4
|
#下载ngrok源码包
cd ~
git clone https://github.com/inconshreveable/ngrok.git
cd ngrok/
|
为Base域名生成自签名证书
ngrok需要一个域名作为base域名,ngrok会为客户端分配base域名的子域名。例如:ngrok的base域名为tunnel.mydomain.com,客户端即可被分配子域名test.tunnel.mydomain.com。
使用ngrok官方服务时,base域名是ngrok.com,并且使用默认的SSL证书。现在自建ngrok服务器,所以需要重新为自己的base域名生成证书。
Shell
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=tunnel.mydomain.com" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=tunnel.mydomain.com" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000
1
2
3
4
5
6
|
#为base域名tunnel.mydomain.com生成证书
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=tunnel.mydomain.com" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=tunnel.mydomain.com" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000
|
执行完上述命令,正常情况下,该目录会多出device.crt、device.csr、device.key、rootCA.key、rootCA.pem、rootCA.srl六个文件,用它们来替换默认的证书文件即可。默认的证书文件在“./assets/client/tls”和“./assets/server/tls/”目录中
Shell
cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key
1
2
3
4
|
#替换默认的证书文件
cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key
|
修改代码
按理来说,跳过这一步直接进行后续的编译工作没有什么问题。但是由于google无法访问,造成编译的时候从code.google.com上自动下载依赖包log4go超时而编译失败。
所以,我们需要将依赖包的下载地址改为github上的地址。
Shell
#
#
vim ./src/ngrok/log/logger.go
1
2
3
4
|
#修改依赖包log4go下载地址
#
#
vim ./src/ngrok/log/logger.go
|
找到第4行,将 log "code.google.com/p/log4go"
改为 log "github.com/keepeye/log4go"即可。
四、开始编译ngrok
编译服务器端ngrokd
ngrokd就是ngrok服务器端,默认编译为Linux的执行文件,我们的VPS本身就是Linux的,所以直接make编译就好。
Shell
#
#
make release-server
1
2
3
4
|
#编译ngrokd(服务器端)
#
#
make release-server
|
显示以下内容,没有任何报错的话,服务器端ngrokd就编译成功了。
GOOS="" GOARCH="" go get github.com/jteeuwen/go-bindata/go-bindata
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/client/assets/assets_release.go \
assets/client/...
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/server/assets/assets_release.go \
assets/server/...
go get -tags 'release' -d -v ngrok/...
github.com/gorilla/websocket (download)
github.com/inconshreveable/go-vhost (download)
github.com/inconshreveable/mousetrap (download)
github.com/keepeye/log4go (download)
github.com/nsf/termbox-go (download)
github.com/mattn/go-runewidth (download)
github.com/rcrowley/go-metrics (download)
Fetching https://gopkg.in/inconshreveable/go-update.v0?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0?go-get=1 (status code 200)
get "gopkg.in/inconshreveable/go-update.v0": found meta tag main.metaImport{Prefix:"gopkg.in/inconshreveable/go-update.v0", VCS:"git", RepoRoot:"https://gopkg.in/inconshreveable/go-update.v0"} at https://gopkg.in/inconshreveable/go-update.v0?go-get=1
gopkg.in/inconshreveable/go-update.v0 (download)
github.com/kardianos/osext (download)
github.com/kr/binarydist (download)
Fetching https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1 (status code 200)
get "gopkg.in/inconshreveable/go-update.v0/check": found meta tag main.metaImport{Prefix:"gopkg.in/inconshreveable/go-update.v0", VCS:"git", RepoRoot:"https://gopkg.in/inconshreveable/go-update.v0"} at https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1
get "gopkg.in/inconshreveable/go-update.v0/check": verifying non-authoritative meta tag
Fetching https://gopkg.in/inconshreveable/go-update.v0?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0?go-get=1 (status code 200)
Fetching https://gopkg.in/yaml.v1?go-get=1
Parsing meta tags from https://gopkg.in/yaml.v1?go-get=1 (status code 200)
get "gopkg.in/yaml.v1": found meta tag main.metaImport{Prefix:"gopkg.in/yaml.v1", VCS:"git", RepoRoot:"https://gopkg.in/yaml.v1"} at https://gopkg.in/yaml.v1?go-get=1
gopkg.in/yaml.v1 (download)
go install -tags 'release' ngrok/main/ngrokd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#服务器端ngrokd编译成功显示的内容
GOOS="" GOARCH="" go get github.com/jteeuwen/go-bindata/go-bindata
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/client/assets/assets_release.go \
assets/client/...
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/server/assets/assets_release.go \
assets/server/...
go get -tags 'release' -d -v ngrok/...
github.com/gorilla/websocket (download)
github.com/inconshreveable/go-vhost (download)
github.com/inconshreveable/mousetrap (download)
github.com/keepeye/log4go (download)
github.com/nsf/termbox-go (download)
github.com/mattn/go-runewidth (download)
github.com/rcrowley/go-metrics (download)
Fetching https://gopkg.in/inconshreveable/go-update.v0?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0?go-get=1 (status code 200)
get "gopkg.in/inconshreveable/go-update.v0": found meta tag main.metaImport{Prefix:"gopkg.in/inconshreveable/go-update.v0", VCS:"git", RepoRoot:"https://gopkg.in/inconshreveable/go-update.v0"} at https://gopkg.in/inconshreveable/go-update.v0?go-get=1
gopkg.in/inconshreveable/go-update.v0 (download)
github.com/kardianos/osext (download)
github.com/kr/binarydist (download)
Fetching https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1 (status code 200)
get "gopkg.in/inconshreveable/go-update.v0/check": found meta tag main.metaImport{Prefix:"gopkg.in/inconshreveable/go-update.v0", VCS:"git", RepoRoot:"https://gopkg.in/inconshreveable/go-update.v0"} at https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1
get "gopkg.in/inconshreveable/go-update.v0/check": verifying non-authoritative meta tag
Fetching https://gopkg.in/inconshreveable/go-update.v0?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0?go-get=1 (status code 200)
Fetching https://gopkg.in/yaml.v1?go-get=1
Parsing meta tags from https://gopkg.in/yaml.v1?go-get=1 (status code 200)
get "gopkg.in/yaml.v1": found meta tag main.metaImport{Prefix:"gopkg.in/yaml.v1", VCS:"git", RepoRoot:"https://gopkg.in/yaml.v1"} at https://gopkg.in/yaml.v1?go-get=1
gopkg.in/yaml.v1 (download)
go install -tags 'release' ngrok/main/ngrokd
|
我们可以在./bin/目录中找到文件ngrokd。可以先运行测试一下。
Shell
#
#
./bin/ngrokd -domain="tunnel.mydomain.com" -httpAddr=":8080"
1
2
3
4
|
#执行ngrokd
#
#
./bin/ngrokd -domain="tunnel.mydomain.com" -httpAddr=":8080"
|
出现类似以下内容,则说明我们的服务器端ngrokd正常运行了。
[02/12/16 17:30:59] [INFO] [registry] [tun] No affinity cache specified
[02/12/16 17:30:59] [INFO] [metrics] Reporting every 30 seconds
[02/12/16 17:30:59] [INFO] Listening for public http connections on [::]:8080
[02/12/16 17:30:59] [INFO] Listening for public https connections on [::]:443
[02/12/16 17:30:59] [INFO] Listening for control and proxy connections on [::]:4443
1
2
3
4
5
6
|
#服务器端ngrokd正常执行的显示
[02/12/16 17:30:59] [INFO] [registry] [tun] No affinity cache specified
[02/12/16 17:30:59] [INFO] [metrics] Reporting every 30 seconds
[02/12/16 17:30:59] [INFO] Listening for public http connections on [::]:8080
[02/12/16 17:30:59] [INFO] Listening for public https connections on [::]:443
[02/12/16 17:30:59] [INFO] Listening for control and proxy connections on [::]:4443
|
之后Ctrl+C退出ngrokd,继续来编译ngrok客户端。
编译客户端ngrok
编译linux客户端很简单,一条命令就搞定。
Shell
#
#
make release-client
1
2
3
4
|
#编译Linux客户端
#
#
make release-client
|
显示以下内容,没有任何报错的话,客户端ngrok就编译成功了,我们在./bin/目录下就可以找到执行文件ngrok。
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/client/assets/assets_release.go \
assets/client/...
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/server/assets/assets_release.go \
assets/server/...
go get -tags 'release' -d -v ngrok/...
go install -tags 'release' ngrok/main/ngrok
1
2
3
4
5
6
7
8
9
10
11
|
#客户端ngrok编译成功显示的内容
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/client/assets/assets_release.go \
assets/client/...
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/server/assets/assets_release.go \
assets/server/...
go get -tags 'release' -d -v ngrok/...
go install -tags 'release' ngrok/main/ngrok
|
但是,我们现在需要在windows下运行ngrok客户端。所以,重点是我们需要编译Windows版的ngrok客户端。那么我们难道要安装WIndows版的go环境,在Windows下编译吗?那太麻烦了,不用这样。go开发环境为我们提供了强大的跨平台交叉编译功能,在Linux下即可完成Windows版的编译。
Shell
cd /usr/local/go/src
#给Go编译器加上交叉编译windows/amd64程序的功能
GOOS=windows GOARCH=amd64 ./make.bash
1
2
3
4
|
#切到go的安装目录
cd /usr/local/go/src
#给Go编译器加上交叉编译windows/amd64程序的功能
GOOS=windows GOARCH=amd64 ./make.bash
|
如果这里提示错误
./make.bash: eval: line 135: syntax error near unexpected token `('
有可能是之前使用yum安装过go,通过命令 yum remove go 卸载当前的go并按照本文的方法重新安装。
Shell
cd -
#执行如下命令编译Windows 64位客户端
GOOS=windows GOARCH=amd64 make release-client
#以上GOARCH=amd64指的是编译为64位版本,如需32位改成GOARCH=386即可
1
2
3
4
5
|
#切回到ngrok目录
cd -
#执行如下命令编译Windows 64位客户端
GOOS=windows GOARCH=amd64 make release-client
#以上GOARCH=amd64指的是编译为64位版本,如需32位改成GOARCH=386即可
|
没有错误的话,Windows客户端ngrok就编译成功了,我们可以在./bin/windows_amd64/目录下找到执行文件ngrok.exe。将其下载到Windows上。
五、运行并测试
启动服务器端ngrokd
这个很简单,首先执行VPS上的服务器端ngrokd,这里的8080指的是服务器启用8080端口,就是说内网穿透后的域名为xxx.tunnel,mydomain.com:8080。如果在80端口未作他用的情况下,也可将8080端口改为80,这样更方便些。而如果我们VPS的80端口被占用了,但是我们还想用80端口作为服务端口,那么可以使用nginx做一个xxx.tunnel.mydomain.com的反向代理。
Shell
#
#
./bin/ngrokd -domain="tunnel.mydomain.com" -httpAddr=":8080"
1
2
3
4
|
#执行ngrokd
#
#
./bin/ngrokd -domain="tunnel.mydomain.com" -httpAddr=":8080"
|
写客户端配置文件
在ngrok.exe所在目录下建立文件ngrok.cfg,用记事本等文本编辑器写入以下内容并保存。
Shell
#
server_addr: "tunnel.mydomain.com:4443"
trust_host_root_certs: false
1
2
3
4
|
#配置文件ngrok.cfg的内容
#
server_addr: "tunnel.mydomain.com:4443"
trust_host_root_certs: false
|
映射HTTP
之后打开Windows服务器,这里以80端口为例。打开命令提示符,切到ngrok.exe所在目录,并执行以下命令。
Shell
#注意:如果不加参数-subdomain=test,将会随机自动分配子域名。
#
ngrok -config=ngrok.cfg -subdomain=test 80
1
2
3
4
|
#启动ngrok客户端
#注意:如果不加参数-subdomain=test,将会随机自动分配子域名。
#
ngrok -config=ngrok.cfg -subdomain=test 80
|
正常情况下,客户端上会显示以下内容,表示成功连接到服务器端。
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.7/1.7
Forwarding http://test.tunnel.mydomain.com:8080 -> 127.0.0.1:80
Forwarding https://test.tunnel.mydomain.com:8080 -> 127.0.0.1:80
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
1
2
3
4
5
6
7
8
9
10
|
#客户端ngrok正常执行显示的内容
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.7/1.7
Forwarding http://test.tunnel.mydomain.com:8080 -> 127.0.0.1:80
Forwarding https://test.tunnel.mydomain.com:8080 -> 127.0.0.1:80
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
|
打开浏览器,分别在地址栏中输入http://localhost和http://test.tunnel.mydomain.com:8080,如果后者正常显示并且和http://localhost显示的内容相同,则证明我们已经成功了。
映射TCP
有时候,我们使用远程桌面功能,或者在linux中进行SSH连接,对于处在内网环境中的计算机,我们可以对该端口进行TCP映射。
Shell
#
#
./ngrok -proto=tcp 22
1
2
3
4
|
#这里以SSH连接Linux时的22端口为例
#
#
./ngrok -proto=tcp 22
|
映射成功的话,会显示如下内容:
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.7/1.7
Forwarding tcp://tunnel.mydomain.com:49805 -> 127.0.0.1:22
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
1
2
3
4
5
6
7
8
9
|
#客户端ngrok正常执行显示的内容
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.7/1.7
Forwarding tcp://tunnel.mydomain.com:49805 -> 127.0.0.1:22
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
|
现在,在putty等ssh工具中即可连接tunnel.mydomain.com。切记端口是号49805,是随机分配的一个端口号,而不是默认的22端口了。
Windows的远程桌面可以将其映射到3389端口来实现。同理,如果要做MySQL的远程连接,只需映射3306端口即可。FTP可映射21端口。
六、后续定制及优化
通过以上操作,我们的ngrok服务器就已经成功搭建了,客户端也成功的跑了起来。但是,如果我们想要对ngrok进行一些定制和优化,可以参考这些后续定制及优化的方法。
修改客户端ngrok默认服务地址
客户端每次还需要加载配置文件,这样显得有些麻烦。能不能像官方服务那样直接执行命令 ngrok 80就能使用呢?我们只需要在编译客户端之前,稍作修改即可。同样,如果需要指定域名可以执行命令 ngrok -subdomain=test 80来运行客户端。
Shell
#
#
vim ./src/ngrok/client/model.go
1
2
3
4
|
#修改默认服务地址
#
#
vim ./src/ngrok/client/model.go
|
找到第23行,将 defaultServerAddr = "ngrokd.ngrok.com:443"
修改为 defaultServerAddr = "tunnel.mydomain:4443" 即可。
修改客户端ngrok左上角蓝色文字logo
运行客户端后,我们会发现在客户端左上角会有一个蓝色字体的“ngrok”字样的文字logo,如果觉得不太喜欢,或者想修改一下的话,可以在编译客户端之前,作如下修改。
Shell
#
#
Vim ./src/ngrok/client/views/term/view.go
1
2
3
4
|
#修改客户端蓝色文字logo
#
#
Vim ./src/ngrok/client/views/term/view.go
|
找到第100行,将
v.APrintf(termbox.ColorBlue|termbox.AttrBold, 0, 0, "ngrok")
修改为
v.APrintf(termbox.ColorBlue|termbox.AttrBold, 0, 0, "your logo")
即可。
修改客户端帮助信息
ngrok客户端默认的帮助信息很少,我们可以在编译客户端之前,自己定制帮助内容。
Shell
#
#
vim ./src/ngrok/client/client/cli.go
1
2
3
4
|
#修改客户端默认帮助信息
#
#
vim ./src/ngrok/client/client/cli.go
|
找到第14行,修改 const usage2 string的值即可。
客户端程序加壳优化
编译好的Windows客户端ngrok.exe大小为10MB,感觉有点大,这样加载到内存中,需要读取硬盘的内容也相对较多,影响速度。所以,我们来给客户端程序加个压缩壳,对程序进行压缩。
这里采用mpress进行加壳,先从网上下载mpress.exe,之后将ngrok.exe拖放到mpress.exe的图标上,就能完成加壳操作。我们可以看到,加壳后的程序只有1.94MB,压缩率不到20%,大大节省了磁盘空间。同时,小文件加载起来,速度会更快。
本文来自http://blog.lzp.name/archives/24转载请注明出处!以便于作者及时收到读者的反馈,修改文章中可能出现的错误。同时方便读者阅读最新版本的文章。
本篇文章为作者建立个人博客以来的第一篇技术类文章,如有错误,希望广大读者能够及时给予批评和指正。
ngrok开源项目地址:https://github.com/inconshreveable/ngrok
参考文章:
吾爱的博客《自行编译ngrok服务端客户端,替代花生壳,跨平台》
Tony Bai《搭建自己的ngrok服务》