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

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

### 靈異重現

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

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

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

![docker run -d --name nginx --rm -p 7777:80 nginx](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/61f59cb9f9ff893736f9-182506084-28320de7-d735-466a-8d88-de46085a5f5a.png)

然後我用 Google Chrome 打開 [http://localhost:7777/](http://localhost:7777/) 網址，順利無誤：

![http://localhost:7777](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/99be5ce04cce99f83db1-182506249-822238be-0173-47b2-b90b-135a42d151cd.png)

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

```bash
curl localhost:7777
```

![curl: (56) Recv failure: Connection was reset](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/e3c1a92b80549df7a688-182520822-a23ccfd9-ec01-4921-a295-7060573b1570.png)

我用 verbose mode 輸出更多資訊，得到的結果如下：

```bash
curl localhost:7777 -v
```

```batch
*   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 社群](https://line.me/ti/g2/Uhq2NM0bA6j3C5O0XBAVnm9CGMxJZgY_UMBsPA)問看看有沒有人知道問題，上面有三千多人，果然群眾智慧非常的睿智，過沒多久就有答案了！ 😍

首先，一開始就有人給了 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` 其實是[健保卡驗證元件](https://download.tax.nat.gov.tw/ibx/UninstallNHICC.pdf)，不是政府讀卡機憑證軟體。

此時就有人提出了一個測試方法，指定網路介面位址來重新執行 `nginx` 容器，看會不會發生衝突：

```bash
# 先停用先前容器
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](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/825a9b1d6d0d2e57d3e6-182525782-7da53638-a8a8-4fb5-a704-a53cfece0fee.png)

所以，我可以收集到的證據，就是 `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](https://en.wikipedia.org/wiki/IPv6) 協定，而 curl 則預設走 [IPv4](https://en.wikipedia.org/wiki/IPv4) 協定。我在查詢完 `curl` 文件後得知，原來你可以用 `-6` 參數，強迫 `curl` 走 IPv6 協議進行連線：

```bash
curl localhost:7777 -6
```

此時我就可以正常連接 `nginx` 並順利取得網頁內容了：

```html
<!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 的連線資訊：

```powershell
Get-NetTCPConnection -State Listen -LocalPort 7777
```

![Get-NetTCPConnection -State Listen -LocalPort 7777](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/2abda7daea1ffa693b19-182532014-9a5fc6a1-5bd3-43f7-bcc6-66f185b8bc5a.png)

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

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

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

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/5138f2fbd3e8e07e8bec-182531885-7922e63f-28c5-4a85-92ce-9d39c77a7a68.png)

### 相關連結

-   [How to Find Listening Ports with Netstat and PowerShell](https://adamtheautomator.com/netstat-port/)
-   [Get-NetTCPConnection (NetTCPIP) | Microsoft Docs](https://learn.microsoft.com/en-us/powershell/module/nettcpip/get-nettcpconnection?view=windowsserver2019-ps&WT.mc_id=DT-MVP-4015686)
-   [IPv4 - Wikipedia](https://en.wikipedia.org/wiki/IPv4)
-   [IPv6 - Wikipedia](https://en.wikipedia.org/wiki/IPv6)
