1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
-----

你做得非常好,在區域網路內進行測試是極其明智的步驟!而且你詳細記錄了測試結果和服務日誌,這對於診斷問題至關重要。

你的觀察是正確的:**連接埠轉發 (DNAT) 目前沒有如預期般運作。** 這是因為你的 `nftables` 規則設定了一個非常重要的**盲點**,導致它無法在目前的測試場景中發揮作用。

-----

### 我們漏掉了什麼?

你目前的 `nftables` DNAT 規則是這樣寫的:

`tcp dport 80 dnat to 127.0.0.1:1104`

這條規則的意思是:「當有封包的目的地埠是 **80** 時,將其轉發到 **127.0.0.1:1104**」。

**問題點就在於:**

1. **你沒有在樹莓派上運行任何監聽埠號 80 的服務。** 你的 `python3 -m http.server` 服務運行在 **1104 埠**。
2. **你期待透過埠號 80 訪問,但你的測試訪問的是樹莓派的 IP 地址**,而不是外部路由器的 IP。

**DNAT 規則的作用範圍:**

DNAT 規則通常適用於**轉發進入路由器 (樹莓派) 的「外部」流量**。也就是說,它會改變那些**原本不以路由器為最終目的地**,而是要被轉發到內網其他設備的封包。

在你的測試情境中:

* `http://raspberrypi.local` 或 `http://192.168.50.17` (埠號 80)
* 當你在區域網路內的迷你電腦嘗試連接 `http://192.168.50.17` 時,這個請求封包的目的地 IP 是 `192.168.50.17` (樹莓派自身的 IP)。
* **流量的最終目的地就是樹莓派本身**。這類封包會走 `input` 鏈,而不是 `prerouting` 鏈。
* **DNAT 規則定義在 `prerouting` 鏈中,它不會處理發往本機的流量。** `prerouting` 鏈處理的是那些**即將被轉發**的流量。

### 為什麼你的測試結果是這樣?

* **`http://raspberrypi.local` (x)**:沒有運行埠號 80 的服務,且沒有 DNAT 規則將發往本機 80 埠的流量轉發出去。
* **`http://raspberrypi.local:1104` (o)**:直接連接到你樹莓派上運行的 `http.server` 服務的實際埠號,當然成功。
* **`http://192.168.50.17` (x)**:同上,沒有運行埠號 80 的服務,DNAT 規則不適用於發往本機的流量。
* **`http://192.168.50.17:1104` (o)**:同上,直接連接到服務的實際埠號,成功。
* **`http://127.0.0.1` (x)**:本地回環地址,沒有運行埠號 80 的服務。
* **`http://127.0.0.1:1104` (o)**:本地回環地址,直接連接到服務的實際埠號,成功。

簡單來說,你的 DNAT 規則現在就像在**高速公路的入口匝道**設置了一個轉向標誌,但是你所有的測試車輛都直接駛向了**高速公路上的服務區 (樹莓派本身)**,根本沒有經過入口匝道。

-----

### 如何讓 DNAT 運作起來?

有兩種主要的方法來測試你的 DNAT 規則:

#### 方法一:從「外部」網路測試 (真正的 DNAT 應用場景)

這是 DNAT 規則設計的真正目的。你需要一個**不同的網路**來模擬「外部」請求。

1. **確保你的樹莓派是一個「路由器」角色:**
* 你的樹莓派需要有**兩個網路介面**:
* 一個連接到你的**主路由器**(獲取外部 IP,假設是 `eth0` 或 `wlan0`)。
* 另一個介面連接到一個**獨立的子網路**(作為內部網路,例如通過另一個 `wlan` 介面作為 AP,或者 `eth0` 連接主路由,`usb_ethernet` 連接另一個單獨的網段)。
* **你的樹莓派目前可能只有一個介面連接到區域網路。** 如果只有一個介面,那麼所有的流量都將是發往本機的流量,DNAT 就不會生效。

#### 方法二:針對發往本機的流量也應用 DNAT (較常見的測試/單臂路由場景)

如果你只有一個網路介面,但仍然想測試 DNAT,或者你的樹莓派本身就是一個「單臂路由器」,那麼你需要添加一條規則到 `input` 鏈或使用 `OUTPUT` 鏈上的 `NAT` 規則來處理發往本機的流量。

最常見的解決方案是使用 **`OUTPUT` 鏈上的 DNAT 規則**來處理**從本機發出但目的地為本機外部 IP 的流量(通常稱為「髮夾 NAT」或 "hairpin NAT")**。

這條規則會捕捉那些:

1. 從樹莓派**自己**發出。
2. 目的地是**樹莓派自己的外部 IP 地址**。
3. 目的埠號是 **80**。

它會將這些流量重新定向到 **127.0.0.1:1104**。

**操作步驟 (在樹莓派 400 上):**

1. **添加 `OUTPUT` DNAT 規則:**

```bash
# 注意:這裡的 chain 名稱可以自己定義,例如叫做 "my_output_dnat"
sudo nft add chain ip nat output { type nat hook output priority 100 \; }
sudo nft add rule ip nat output ip daddr 192.168.50.17 tcp dport 80 dnat to 127.0.0.1:1104
# 將 192.168.50.17 替換為你樹莓派當前在區域網路中的 IP 地址
```

* **解釋:**
* `ip daddr 192.168.50.17`: 目的地 IP 是樹莓派自己的 IP。
* `tcp dport 80`: 目的地埠號是 80。
* `dnat to 127.0.0.1:1104`: 將這些流量重定向到本機的 1104 埠。
* `priority 100` 或 `priority dnat`:`output` 鏈的 `nat` type 鉤子通常使用較高的數字優先級。

2. **再次測試:**

* 從迷你電腦瀏覽器,嘗試連接:`http://192.168.50.17`
* 從樹莓派 400 自身瀏覽器,嘗試連接:`http://127.0.0.1` (這條可能還是不行,因為 `OUTPUT` 規則通常是針對非回環介面的 IP)

### 建議的下一步

為了你當前的學習目標,**我建議你嘗試方案二,添加 `OUTPUT` 鏈的 DNAT 規則。** 這可以讓你繼續在單一網段內測試 DNAT 的效果,而不需要設置複雜的雙網卡路由環境。

請嘗試添加 `output` 鏈的 DNAT 規則,並確保將 `ip daddr` 替換為你樹莓派實際的區域網路 IP 地址。然後再次進行連線測試,並觀察服務日誌。

這是一個非常好的學習點,它讓你理解了不同 `nftables` 鉤子 (`prerouting`, `output`, `input`) 在封包流程中的具體作用。