The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

Windows 10 同時有兩個應用程式 LISTEN Port 7777 之靈異事件整理

昨天同事問我一個關於如何在 nginx 設定 Reverse Proxy 的問題,我在跟他說明作法之後,想說也來驗證一下,結果卻意外遇到一個網路相關的靈異事件,瞬間讓我陷入一個抓鬼情緒,花了大概一個小時左右才釐清問題。今天這篇文章我打算來說說這個狀況,相信你也會覺得毛骨聳然的。

靈異重現

首先,我只是很單純想要透過 Docker 把 nginx 容器啟動,只是我選擇了一個幸運數字 7777

docker run -d --name nginx --rm -p 7777:80 nginx

這段命令在我的電腦是可以順利啟動的:

docker run -d --name nginx --rm -p 7777:80 nginx

然後我用 Google Chrome 打開 http://localhost:7777/ 網址,順利無誤:

http://localhost:7777

但我用 curl 測試,卻得到 curl: (56) Recv failure: Connection was reset 的錯誤:

curl localhost:7777

curl: (56) Recv failure: Connection was reset

我用 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 封包之後,就會立刻被斷線!

當下的我覺得相當震驚,腦海中出現的早已不是「重開機看看」的解決方案,而是從潛意識中急速的撈出排山倒海而來的各種網路知識,但還是無法得到一個有效的釐清。我大概的思考方向有幾點:

  1. 難道是 Windows Firewall 出了問題?

    我沒有安裝 Windows Defender 以外的防毒軟體,如果真的是防火牆出問題,沒道理會特別阻擋 curl 連線,而且是在「連上」之後連線被重置,這種行為通常是「防毒軟體」會幹的事,防火牆通常是「連的上」與「連不上」的差異,所以應該可以排除這個問題。

  2. 難道是路由表出問題?

    這也不太可能,因為我的連線是走本地迴路介面(loopback interface),根本不會繞出去外面。

  3. 難道是 nginx 容器沒有正常啟動?

    不對啊!明明 Google Chrome 是可以連上的,而且連 Internet Explorer 都可以正常打開網頁!

    到底有什麼道理讓 curl 連不上呢?

  4. 難道是 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 個建議:

  1. 沒有乖乖?
  2. 用 127.0.0.1 看看行不行
  3. 重開機看行不行

是蠻中肯的啦,只是我不太迷信這個 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 才能砍掉這個容器:

image

所以,我可以收集到的證據,就是 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

Get-NetTCPConnection -State Listen -LocalPort 7777

圖片中的 OwningProcess 欄位,就是 Process ID,因此你可以透過以下命令,再找出更細部的 Process 資訊:

Get-NetTCPConnection -State Listen -LocalPort 7777 | foreach {
  $_
  Get-Process -Id $_.OwningProcess
}

這個命令可以獲得到的資訊,比 netstat 清楚的太多,而且不需要使用系統管理員身份執行就可以得到資料,方便許多:

image

相關連結

留言評論