這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在國內開發公眾號、企業號以及做前端開發的朋友想必對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服務,這樣也不會干擾原先網域名稱提供的服務。(不用網域名稱的方式也許可以,但我沒有實驗過。)
搭建的參考資料主要來自下面三個:
1) ngrok的官方SELFHOST指南:https://github.com/inconshreveable/ngrok/blob/master/docs/SELFHOSTING.md
2) 國外一哥們的部落格:http://www.svenbit.com/2014/09/run-ngrok-on-your-own-server/
3) "海運的部落格"中的一篇文章:http://www.haiyun.me/archives/1012.html
二、實操步驟
我的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
$ openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=tunnel.tonybai.com" -days 5000 -out rootCA.pem
$ openssl genrsa -out device.key 2048
$ openssl req -new -key device.key -subj "/CN=tunnel.tonybai.com" -out device.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被安裝到了$GOBIN下了,go編譯器找不到了。修正方法是將$GOBIN/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
[03/14/15 04:47:24] [INFO] Listening for public http connections on [::]:8080
[03/14/15 04:47:24] [INFO] Listening for public https connections on [::]:8081
[03/14/15 04:47:24] [INFO] Listening for control and proxy connections on [::]:4443
… …
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端來看,還是沒有收到用戶端的串連,顯然是串連沒有打通公司內網。從我自己的squidProxy 伺服器來看,似乎只有443連接埠的請求被公司Proxy 伺服器允許通過,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
[03/14/15 04:47:24] [INFO] Listening for public http connections on [::]:8080
[03/14/15 04:47:24] [INFO] Listening for public https connections on [::]:8081
[03/14/15 04:47:24] [INFO] Listening for control and proxy connections on [::]:443
… …
將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!"))
}
$ go build -o hello hello.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
2015, bigwhite. 著作權.