1. 程式人生 > >搭建自己的ngrok服務(內網穿透 使用簡單)

搭建自己的ngrok服務(內網穿透 使用簡單)

在國內開發微信公眾號、企業號以及做前端開發的朋友想必對ngrok都不陌生吧,就目前來看,ngrok可是最佳的在內網除錯微信服務的tunnel工 具。記得今年春節前,ngrok.com提供的服務還一切正常呢,但春節後似乎就一切不正常了。ngrok.com無法訪問,ngrok雖然能連上 ngrok.com提供的服務,但微信端因為無法訪問ngrok.com,導致訊息一直無法傳送到我們的服務地址上,比如xxxx.ngrok.com。 這一切都表明,ngork被牆了。沒有了ngrok tunnel,一切開始變得困難且沒有效率起來。內網到外部主機部署和除錯是一件慢的讓人想罵街的事情。

ngrok不能少。ngrok以及其服務端ngrokd都是開源的,之前我也知道通過原始碼可以自搭建ngrok服務。請求搜尋引擎後,發現國內有個朋友已經搭建了一個www.tunnel.mobi的ngrok公共服務,與ngrok.com類似,我也實驗了一下。

編寫一個ngrok.cfg,內容如下:

server_addr: “tunnel.mobi:44433”
trust_host_root_certs: true

用ngrok最新客戶端1.7版本執行如下命令:

$ngrok -subdomain tonybaiexample -config=ngrok.cfg 80

可以順利建立一個tunnel,用於本機向外部提供”tonybaiexample.tunnel.mobi”服務。

Tunnel Status online
Version 1.7/1.7
Forwarding

http://tonybaiexample.tunnel.mobi -> 127.0.0.1:80
Forwarding https://tonybaiexample.tunnel.mobi -> 127.0.0.1:80
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

而且國內的ngrok服務顯然要遠遠快於ngrok.com提供的服務,訊息瞬間即達。

但這是在公網上直接訪問的結果。放在公司內部,我看到的卻是另外一個結果:

Tunnel Status reconnecting
Version 1.7/
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

我們無法從內網建立tunnel,意味著依舊不方便和低效,因為很多基礎服務都在內網部署,內外網之間的互動十分不便。但內網連不上tunnel.mobi也是個事實,且無法知道原因,因為看不到server端的連線錯誤日誌。

於是我決定自建一個ngrok服務。

一、準備工作

搭建ngrok服務需要在公網有一臺vps,去年年末曾經在Amazon申請了一個體驗主機EC2,有公網IP一個,這次就打算用這個主機作為ngrokd服務端。

需要一個自己的域名。已有域名的,可以建立一個子域名,用於關聯ngrok服務,這樣也不會干擾原先域名提供的服務。(不用域名的方式也許可以,但我沒有試驗過。)

二、實操步驟

我的AWS EC2例項安裝的是Ubuntu Server 14.04 x86_64,並安裝了golang 1.4(go version go1.4 linux/amd64)。Golang是編譯ngrokd和ngrok所必須的,建議直接從golang官方下載對應平臺的二進位制安裝包(國內可以從 golangtc.com上下載,速度慢些罷了)。

1、下載ngrok原始碼

(GOPATH=~/goproj)
mkdir/goproj/src/github.com/inconshreveable git clone https://github.com/inconshreveable/ngrok.git
$ export GOPATH=~/goproj/src/github.com/inconshreveable/ngrok

2、生成自簽名證書

使用ngrok.com官方服務時,我們使用的是官方的SSL證書。自建ngrokd服務,我們需要生成自己的證書,並提供攜帶該證書的ngrok客戶端。

證書生成過程需要一個NGROK_BASE_DOMAIN。 以ngrok官方隨機生成的地址693c358d.ngrok.com為例,其NGROK_BASE_DOMAIN就是”ngrok.com”,如果你要 提供服務的地址為”example.tunnel.tonybai.com”,那NGROK_BASE_DOMAIN就應該 是”tunnel.tonybai.com”。

我們這裡以NGROK_BASE_DOMAIN=”tunnel.tonybai.com”為例,生成證書的命令如下:

cd/goproj/src/github.com/inconshreveable/ngrok openssl genrsa -out rootCA.key 2048
opensslreqx509newnodeskeyrootCA.keysubj/CN=tunnel.tonybai.comdays5000outrootCA.pem openssl genrsa -out device.key 2048
opensslreqnewkeydevice.keysubj/CN=tunnel.tonybai.comoutdevice.csr openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

執行完以上命令,在ngrok目錄下就會新生成6個檔案:

-rw-rw-r– 1 ubuntu ubuntu 1001 Mar 14 02:22 device.crt
-rw-rw-r– 1 ubuntu ubuntu 903 Mar 14 02:22 device.csr
-rw-rw-r– 1 ubuntu ubuntu 1679 Mar 14 02:22 device.key
-rw-rw-r– 1 ubuntu ubuntu 1679 Mar 14 02:21 rootCA.key
-rw-rw-r– 1 ubuntu ubuntu 1119 Mar 14 02:21 rootCA.pem
-rw-rw-r– 1 ubuntu ubuntu 17 Mar 14 02:22 rootCA.srl

ngrok通過bindata將ngrok原始碼目錄下的assets目錄(資原始檔)打包到可執行檔案(ngrokd和ngrok)中 去,assets/client/tls和assets/server/tls下分別存放著用於ngrok和ngrokd的預設證書檔案,我們需要將它們替換成我們自己生成的:(因此這一步務必放在編譯可執行檔案之前)

cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key

3、編譯ngrokd和ngrok

在ngrok目錄下執行如下命令,編譯ngrokd:

$ make release-server

不過在我的AWS上,出現如下錯誤:

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/…
make: bin/go-bindata: Command not found
make: * [client-assets] Error 127

go-bindata被安裝到了GOBINgoGOBIN/go-bindata拷貝到當前ngrok/bin下。

$ cp /home/ubuntu/.bin/go14/bin/go-bindata ./bin

再次執行make release-server。

~/goproj/src/github.com/inconshreveable/ngrok$ make release-server
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/…
code.google.com/p/log4go (download)
go: missing Mercurial command. See http://golang.org/s/gogetcmd
package code.google.com/p/log4go: exec: “hg”: executable file not found in $PATH
github.com/gorilla/websocket (download)
github.com/inconshreveable/go-update (download)
github.com/kardianos/osext (download)
github.com/kr/binarydist (download)
github.com/inconshreveable/go-vhost (download)
github.com/inconshreveable/mousetrap (download)
github.com/nsf/termbox-go (download)
github.com/mattn/go-runewidth (download)
github.com/rcrowley/go-metrics (download)
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)
make: * [deps] Error 1

又出錯!提示找不到hg,原來是aws上沒有安裝hg。install hg後(sudo apt-get install mercurial),再編譯。

$ make release-server
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/…
code.google.com/p/log4go (download)
go install -tags ‘release’ ngrok/main/ngrokd

同樣編譯ngrok:

$ make release-client
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

AWS上ngrokd和ngrok被安裝到了$GOBIN下。

三、除錯

1、啟動ngrokd

$ ngrokd -domain=”tunnel.tonybai.com” -httpAddr=”:8080” -httpsAddr=”:8081”
[03/14/15 04:47:24] [INFO] [registry] [tun] No affinity cache specified
[03/14/15 04:47:24] [INFO] [metrics] Reporting every 30 seconds
… …

2、公網連線ngrokd

將生成的ngrok下載到自己的電腦上。

建立一個配置檔案ngrok.cfg,內容如下:

server_addr: “tunnel.tonybai.com:4443”
trust_host_root_certs: false

執行ngrok:
$ ngrok -subdomain example -config=ngrok.cfg 80

Tunnel Status reconnecting
Version 1.7/
Web Interface 127.0.0.1:4040
、# Conn 0
Avg Conn Time 0.00ms

連線失敗。此刻我的電腦是在公網上。檢視ngrokd的日誌,沒有發現連線到達Server端。試著在本地ping tunnel.tonybai.com這個地址,發現地址不通。難道是DNS設定的問題。之前我只是設定了”*.tunnel.tonybai.com”的A地址,並未設定”tunnel.tonybai.com”。於是到DNS管理頁面,添加了”tunnel.tonybai.com”的A記錄。

待DNS記錄重新整理OK後,再次啟動ngrok:

Tunnel Status online
Version 1.7/1.7
Forwarding http://epower.tunnel.tonybai.com:8080 -> 127.0.0.1:80
Forwarding https://epower.tunnel.tonybai.com:8080 -> 127.0.0.1:80
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

這回連線成功了!

3、內網連線ngrokd

將ngrok拷貝到內網的一臺PC上,這臺PC設定了公司的代理。

按照同樣的步驟啟動ngrok:

$ ngrok -subdomain example -config=ngrok.cfg 80

Tunnel Status reconnecting
Version 1.7/
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

不巧,怎麼又失敗了!從Server端來看,還是沒有收到客戶端的連線,顯然是連線沒有打通公司內網。從我自己的squid代理伺服器來看,似乎只有443埠的請求被公司代理伺服器允許通過,4443則無法出去。

1426301143.558 9294 10.10.126.101 TCP_MISS/000 366772 CONNECT api.equinox.io:443 – DEFAULT_PARENT/proxy.xxx.com - 通過了
1426301144.441 27 10.10.126.101 TCP_MISS/000 1185 CONNECT tunnel.tonybai.com:4443 – DEFAULT_PARENT/proxy.xxx.com - 似乎沒有通過

只能修改server監聽埠了。將-tunnelAddr由4443改為443(注意AWS上需要修改防火牆的埠規則,這個是實時生效的,無需重啟例項):

$ sudo ngrokd -domain=”tunnel.tonybai.com” -httpAddr=”:8080” -httpsAddr=”:8081” -tunnelAddr=”:443”
[03/14/15 04:47:24] [INFO] [registry] [tun] No affinity cache specified
[03/14/15 04:47:24] [INFO] [metrics] Reporting every 30 seconds
… …

將ngrok.cfg中的地址改為443:

server_addr: “tunnel.tonybai.com:443”

再次執行ngrok客戶端:

Tunnel Status online
Version 1.7/1.7
Forwarding http://epower.tunnel.tonybai.com:8080 -> 127.0.0.1:80
Forwarding https://epower.tunnel.tonybai.com:8080 -> 127.0.0.1:80
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

這回成功連上了。

4、80埠

是否大功告成了呢?我們看看ngrok的結果,總感覺哪裡不對呢?噢,轉發的地址怎麼是8080埠呢?為何不是80?微信公眾號/企業號可只是支援80埠啊!

我們還需要修改一下Server端的引數,將-httpAddr從8080改為80。

$ sudo ngrokd -domain=”tunnel.tonybai.com” -httpAddr=”:80” -httpsAddr=”:8081” -tunnelAddr=”:443”

這回再用ngrok連線一下:
Tunnel Status online
Version 1.7/1.7
Forwarding http://epower.tunnel.tonybai.com -> 127.0.0.1:80
Forwarding https://epower.tunnel.tonybai.com -> 127.0.0.1:80
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

這回與我們的需求匹配上了。

5、測試

在內網的PC上建立一個簡單的http server 程式:hello

//hello.go
package main

import “net/http”

func main() {
http.HandleFunc(“/”, hello)
http.ListenAndServe(“:80”, nil)
}

func hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(“hello!”))
}

gobuildohellohello.go sudo ./hello

通過公網瀏覽器訪問一下“http://epower.tunnel.tonybai.com”這個地址,如果你看到瀏覽器返回”hello!”字樣,那麼你的ngrokd服務就搭建成功了!

四、注意事項

客戶端ngrok.cfg中server_addr後的值必須嚴格與-domain以及證書中的NGROK_BASE_DOMAIN相同,否則Server端就會出現如下錯誤日誌:

[03/13/15 09:55:46] [INFO] [tun:15dd7522] New connection from 54.149.100.42:38252
[03/13/15 09:55:46] [DEBG] [tun:15dd7522] Waiting to read message
[03/13/15 09:55:46] [WARN] [tun:15dd7522] Failed to read message: remote error: bad certificate
[03/13/15 09:55:46] [DEBG] [tun:15dd7522] Closing