昨天同事問我一個關於如何在 nginx 設定 Reverse Proxy 的問題,我在跟他說明作法之後,想說也來驗證一下,結果卻意外遇到一個網路相關的靈異事件,瞬間讓我陷入一個抓鬼情緒,花了大概一個小時左右才釐清問題。今天這篇文章我打算來說說這個狀況,相信你也會覺得毛骨聳然的。
靈異重現
首先,我只是很單純想要透過 Docker 把 nginx 容器啟動,只是我選擇了一個幸運數字 7777
:
docker run -d --name nginx --rm -p 7777:80 nginx
這段命令在我的電腦是可以順利啟動的:
然後我用 Google Chrome 打開 http://localhost:7777/ 網址,順利無誤:
但我用 curl
測試,卻得到 curl: (56) Recv failure: Connection was reset
的錯誤:
curl localhost:7777
我用 verbose mode 輸出更多資訊,得到的結果如下:
curl localhost:7777 -v
* Trying 127.0.0.1:7777...
* Connected to localhost (127.0.0.1) port 7777 (#0)
> GET / HTTP/1.1
> Host: localhost:7777
> User-Agent: curl/7.83.1
> Accept: */*
>
* Recv failure: Connection was reset
* Closing connection 0
curl: (56) Recv failure: Connection was reset
結果竟然是「連的上」,但是送出 HTTP Request 封包之後,就會立刻被斷線!
當下的我覺得相當震驚,腦海中出現的早已不是「重開機看看」的解決方案,而是從潛意識中急速的撈出排山倒海而來的各種網路知識,但還是無法得到一個有效的釐清。我大概的思考方向有幾點:
-
難道是 Windows Firewall 出了問題?
我沒有安裝 Windows Defender 以外的防毒軟體,如果真的是防火牆出問題,沒道理會特別阻擋 curl
連線,而且是在「連上」之後連線被重置,這種行為通常是「防毒軟體」會幹的事,防火牆通常是「連的上」與「連不上」的差異,所以應該可以排除這個問題。
-
難道是路由表出問題?
這也不太可能,因為我的連線是走本地迴路介面(loopback interface),根本不會繞出去外面。
-
難道是 nginx 容器沒有正常啟動?
不對啊!明明 Google Chrome 是可以連上的,而且連 Internet Explorer 都可以正常打開網頁!
到底有什麼道理讓 curl
連不上呢?
-
難道是 Port 7777 沒有正確被 LISTEN 嗎?
我用系統管理員身份執行 Command Prompt 命令提示字元,輸入 netstat -natb
查詢所有 LISTENING
狀態的 Port 與執行檔名稱,但因為內容很長,以下我列出我一開始發現到的狀況:
Active Connections
Proto Local Address Foreign Address State
............
TCP 0.0.0.0:7777 0.0.0.0:0 LISTENING
[com.docker.backend.exe]
TCP 0.0.0.0:7777 0.0.0.0:0 LISTENING
[mNHIICCService.exe]
............
我真的要大聲喊 WTF
!兩個不同的應用程式,同時監聽 0.0.0.0:7777
嗎?這有點毀我三觀了! 😅
各位,這不是靈異現象,什麼才叫靈異現象?根本現代 IT 鬼故事了!
重建曙光
這次的靈異事件我想不到怎樣處理,便到我的 LINE 社群問看看有沒有人知道問題,上面有三千多人,果然群眾智慧非常的睿智,過沒多久就有答案了! 😍
首先,一開始就有人給了 3 個建議:
- 沒有乖乖?
- 用 127.0.0.1 看看行不行
- 重開機看行不行
是蠻中肯的啦,只是我不太迷信這個 XD
我先是用 curl: (56) Recv failure: Connection was reset
關鍵字去搜尋,找到幾萬筆網頁,超多人問相關問題的,但其實很難找到一個適用於我目前的情境。最終我還是想真正釐清問題發生的主因,解決問題已經不是我的目標了。
然後有個網友提到一個重要線索:
我只有想到 7777 會被政府讀卡機憑證軟體佔用,但應該 docker run
時就會跳出來 port 衝突了
這個線索非常重要,因為我還真的查到有個 mNHIICCService.exe
確實佔用了 Port 7777
,但問題就在於,為什麼 docker run
的時候也索取了 0.0.0.0
的 Port 7777
啊,怎麼可能沒發生衝突呢?
mNHIICCService.exe
其實是健保卡驗證元件,不是政府讀卡機憑證軟體。
此時就有人提出了一個測試方法,指定網路介面位址來重新執行 nginx
容器,看會不會發生衝突:
# 先停用先前容器
docker exec -it nginx nginx -s stop
# 重新執行 nginx 容器並綁定 127.0.0.1:7777 位址
docker run -d --name nginx --rm -p 127.0.0.1:7777:80 nginx
此時真的靈光乍現,問題根源呼之欲出:
docker: Error response from daemon: Ports are not available: exposing port TCP 127.0.0.1:7777 -> 0.0.0.0:0: listen tcp 127.0.0.1:7777: bind: An attempt was made to access a socket in a way forbidden by its access permissions.
不過這個問題卻導致我 docker
命令 hang 住不動,按 Ctrl+C
也無法停止,我要開啟另一個視窗執行 docker rm -f nginx
才能砍掉這個容器:
所以,我可以收集到的證據,就是 mNHIICCService.exe
確實佔用了 Port 7777
,雖然 netstat
顯示了它綁定的是 0.0.0.0:7777
來用,但是卻只有 127.0.0.1:7777
被佔用?這也太怪了吧?難道兩個人的 localhost
有不同意義?是 IPv4 / IPv6 的問題嗎?
我後來又重新看了一次數百行的 netstat -natb
結果,這次多看到了一筆資料:
Active Connections
Proto Local Address Foreign Address State
............
TCP 0.0.0.0:7777 0.0.0.0:0 LISTENING
[com.docker.backend.exe]
TCP 0.0.0.0:7777 0.0.0.0:0 LISTENING
[mNHIICCService.exe]
............
TCP [::]:7777 [::]:0 LISTENING
[com.docker.backend.exe]
原來 com.docker.backend.exe
真還有有 LISTEN IPv6 的 [::]:7777
Port 耶!
這代表著 Google Chrome 在連接 localhost
的時候,預設是走 IPv6 協定,而 curl 則預設走 IPv4 協定。我在查詢完 curl
文件後得知,原來你可以用 -6
參數,強迫 curl
走 IPv6 協議進行連線:
curl localhost:7777 -6
此時我就可以正常連接 nginx
並順利取得網頁內容了:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
歐耶! 👍
總結
我覺得這整件事,我最需要感恩的對象,就是 Windows 詭異的 netstat
工具,因為他同時呈現兩條完全一樣的 0.0.0.0:7777
紀錄,讓我陷入了深度的思考,讓我有機會學習到這個不太常見的網路知識。平時我們在開發網站時,確實很多會碰觸到 IPv6 等議題,連在執行 Docker 的時候也從來沒有遇到過問題,這還是我第一次遇到這種狀況!
另外,我還特別找出了 PowerShell 的方法,可以更清楚、更精準的快速找到 TCP 的連線資訊:
Get-NetTCPConnection -State Listen -LocalPort 7777
圖片中的 OwningProcess
欄位,就是 Process ID,因此你可以透過以下命令,再找出更細部的 Process 資訊:
Get-NetTCPConnection -State Listen -LocalPort 7777 | foreach {
$_
Get-Process -Id $_.OwningProcess
}
這個命令可以獲得到的資訊,比 netstat
清楚的太多,而且不需要使用系統管理員身份執行就可以得到資料,方便許多:
相關連結