背景
-
很多時候,我們都有這樣的需求:需要將本地正在開發的服務暴露在公網上,也就是從外網直接訪問我們本機上的服務。
-
正常情況下,這是辦不到的,因爲我們的本機並沒有公網 IP,我們的本機處在內網當中。
NAT 穿透原理
這裏需要順手提及一個知識:NAT 穿透。
一個栗子
我們的機器一般都在路由器的內網當中,IP 地址基本上都是192.168.x.x
系列,我們並沒有公網 IP,那麼如何訪問外網呢?
-
我們打開瀏覽器訪問 Google,Google 與我們主機之間如何通信?
假設我們主機 IP 爲192.168.0.100
,路由器 LAN IP 爲192.168.0.1
,WAN IP 爲211.22.145.234
(這是一個公網 IP),Google 服務器 IP 爲74.125.204.101
。 -
詳細通信流程如下:
- 主機構建 HTTP 請求數據包,目標 IP 爲
74.125.204.101
,目標端口80/443
,源 IP 爲192.168.0.100
,源端口隨機生成,假定爲5000
。 - 主機檢查目標 IP 地址,發現不在一個網段,數據包丟給默認網關
192.168.0.1
。 - 路由器 LAN 口收到數據包,構建 NAT 映射,隨機生成端口,假定爲
5500
,這樣映射就是 :5500 -> 192.168.0.100:5000
。WAN 口收到的數據包,如果目標端口是5500
,則轉發給內網 IP 爲192.168.0.100
的機器的5000
端口。 - 路由器修改數據包的源端口爲
5500
,源 IP 地址爲211.22.145.234
,使用 WAN 口將數據包發送出去。 - Google 服務器收到請求,構建響應 HTTP 數據包,目標 IP 地址
211.22.145.234
,目標端口爲5500
。 - 路由器 WAN 口收到數據包,目標端口爲
5500
,查詢 NAT 表,發現對應的機器是192.168.0.100:5000
,所以修改目標 IP 爲192.168.0.100
,目標端口爲5000
。並通過 LAN 口發送給主機。 - 主機接收到數據包,完成這一次通信。
- 主機構建 HTTP 請求數據包,目標 IP 爲
從上面可以看出,內網機器能夠和外網通信,全靠擁有公網 IP 的路由器做交通樞紐。
路由器通過查詢 NAT 表,來確定數據包該發送給內網哪臺機器。
所以內網多臺機器都可以通過這一臺路由器和外網進行通信。這極大的節省了寶貴的公網 IP 資源。
Ngrok
- 而 ngrok 就是利用以上原理實現了內網穿透的工具,只是稍有不同,交換的工具從路由器變成了我們具有固定 IP 的 VPS。
當然原理沒有大變,都是找一個公網服務器做中介。此處成爲服務器 A。流程如下。
1. 本地內網主機和服務器A構建一條連接
2. 用戶訪問服務器A
3. 服務器A聯繫本地內網主機獲取內容
4. 服務器A將獲取到的內容發送給用戶
5. 通過上面的流程,就實現了用戶訪問到了我們內網的內容。
- 那麼幫助我們實現這個功能的程序就是 Ngrok 。通過在服務器上安裝 Ngrok ,我們就可以和本地主機構建一條隧道。來讓外網用戶訪問本地主機的內容。
準備工作
安裝依賴
- 注意 golang 需要 1.6 以上,否則不能編譯客戶端
- 下面是 1.7.3,其他的自己去官網下載,我使用的 Ubuntu 16,自帶的即可
wget https://storage.googleapis.com/golang/go1.7.3.linux-amd64.tar.gz
tar -zxvf go1.7.3.linux-amd64.tar.gz -C /usr/local
獲取 ngrok 源碼
git clone https://github.com/inconshreveable/ngrok.git ngrok
## 建議請使用下面的地址,修復了無法訪問的包地址
git clone https://github.com/tutumcloud/ngrok.git ngrok
cd ngrok
生成證書
- 生成並替換源碼裏默認的證書,注意域名修改爲你自己的。
(之後編譯出來的服務端客戶端會基於這個證書來加密通訊,保證了安全性)
NGROK_DOMAIN="liyuans.com"
openssl genrsa -out base.key 2048
openssl req -new -x509 -nodes -key base.key -days 10000 -subj "/CN=$NGROK_DOMAIN" -out base.pem
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=$NGROK_DOMAIN" -out server.csr
openssl x509 -req -in server.csr -CA base.pem -CAkey base.key -CAcreateserial -days 10000 -out server.crt
cp base.pem assets/client/tls/ngrokroot.crt
編譯服務端
sudo make release-server
- 如果一切正常,ngrok/bin 目錄下應該有 ngrok、ngrokd 兩個可執行文件。
ngrokd 爲服務器端使用的,ngrok 是 linux 客戶端使用的
服務端
- 前面生成的 ngrokd 就是服務端程序了,指定證書、域名和端口啓動它(證書就是前面生成的,注意修改域名):
sudo ./bin/ngrokd -tlsKey=server.key -tlsCrt=server.crt -domain="liyuans.com" -httpAddr=":8081" -httpsAddr=":8082"
- 到這一步,ngrok 服務已經跑起來了,可以通過屏幕上顯示的日誌查看更多信息。
- httpAddr、httpsAddr 分別是 ngrok 用來轉發 http、https 服務的端口,可以隨意指定。
- ngrokd 還會開一個 4443 端口用來跟客戶端通訊(可通過 -tunnelAddr=":xxx" 指定),如果你配置了 iptables 規則,需要放行這三個端口上的 TCP 協議。
測試連接
-
現在,通過
http://liyuans.com:8081
和https://liyuans.com:8082
就可以訪問到 ngrok 提供的轉發服務。
爲了使用方便,建議把域名泛解析到 VPS 上,這樣能方便地使用不同子域轉發不同的本地服務。 -
可以看到這樣一行提示:
Tunnel liyuans.com:8081 not found
,這說明萬事俱備,只差客戶端來連了。
編譯客戶端
#windows
GOOS=windows GOARCH=amd64 make release-client
#mac
GOOS=darwin GOARCH=amd64 make release-client
客戶端
-
如果要把 linux 上的服務映射出去,客戶端就是前面生成的 ngrok 文件。(在 bin 文件夾內)
-
寫一個簡單的配置文件,隨意命名如 ngrok.cfg:
server_addr: imququ.com:4443
trust_host_root_certs: false
- 指定子域、要轉發的協議和端口,以及配置文件,運行客戶端:
./ngrok -subdomain pub -proto=http -config=ngrok.cfg 80
- 不出意外可以看到這樣的界面,這說明已經成功連上遠端服務了