Network / Firewall
===
###### tags: `OS / Ubuntu / Network`
###### tags: `OS`, `Ubuntu`, `linux`, `command`, `network`, `firewall`, `firewalld`
<br>
| cmd | package |
|-----|---------|
| `firewalld` | `apt install firewalld` |
| `nft` | `apt install nftables` |
| `ufw` | `apt install ufw` |
<br>
## Ubuntu 防火牆架構圖
### nftables(底層門神)× firewalld(上層總管)
> 視覺化層級與資訊流:上是控制面(設定規則),下是資料面(封包實際流動)。

:::spoiler 圖片繪製原始碼
```
export default function FirewallStackDiagram() {
// Simple helper to draw an SVG box with centered label
const Box = ({ x, y, w, h, title, subtitle }: { x: number; y: number; w: number; h: number; title: string; subtitle?: string; }) => (
<g>
<rect x={x} y={y} width={w} height={h} rx={14} ry={14} fill="white" stroke="black" />
<text x={x + w / 2} y={y + 28} textAnchor="middle" fontSize={16} fontWeight={700}>{title}</text>
{subtitle && (
<text x={x + w / 2} y={y + 52} textAnchor="middle" fontSize={13}>{subtitle}</text>
)}
</g>
);
// Arrow helper (straight)
const Arrow = ({ x1, y1, x2, y2, label, dashed }: { x1: number; y1: number; x2: number; y2: number; label?: string; dashed?: boolean; }) => (
<g>
<line x1={x1} y1={y1} x2={x2} y2={y2} stroke="black" markerEnd="url(#arrow)" strokeDasharray={dashed ? "6,6" : undefined} />
{label && (
<text x={(x1 + x2) / 2} y={(y1 + y2) / 2 - 8} textAnchor="middle" fontSize={12}>{label}</text>
)}
</g>
);
// Polyline arrow for bent routes
const BendArrow = ({ points, label, dashed }: { points: [number, number][]; label?: string; dashed?: boolean; }) => {
const d = points.map((p, i) => (i === 0 ? `M ${p[0]} ${p[1]}` : `L ${p[0]} ${p[1]}`)).join(" ");
// Arrow head at the last segment
const [x1, y1] = points[points.length - 2];
const [x2, y2] = points[points.length - 1];
return (
<g>
<path d={d} fill="none" stroke="black" strokeDasharray={dashed ? "6,6" : undefined} markerEnd="url(#arrow)" />
{label && (
<text x={(x1 + x2) / 2} y={(y1 + y2) / 2 - 8} textAnchor="middle" fontSize={12}>{label}</text>
)}
</g>
);
};
return (
<div className="w-full h-full flex items-center justify-center p-6 bg-neutral-50">
<div className="w-full max-w-[1100px] bg-white rounded-2xl shadow p-4">
<h1 className="text-2xl font-bold mb-2">nftables(底層門神)× firewalld(上層總管)</h1>
<p className="text-sm text-neutral-700 mb-4">視覺化層級與資訊流:上是控制面(設定規則),下是資料面(封包實際流動)。</p>
<svg viewBox="0 0 1100 760" className="w-full">
{/* Arrow marker definition */}
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="black" />
</marker>
</defs>
{/* Background swimlanes */}
<g>
<rect x={20} y={40} width={1060} height={290} rx={12} ry={12} fill="#f6f6f6" stroke="#e5e5e5" />
<text x={40} y={65} fontSize={14} fontWeight={700}>控制面(設定規則)</text>
<rect x={20} y={500} width={1060} height={220} rx={12} ry={12} fill="#f6f6f6" stroke="#e5e5e5" />
<text x={40} y={525} fontSize={14} fontWeight={700}>資料面(封包實際流動)</text>
</g>
{/* Top row: CLI tools */}
<Box x={110} y={90} w={190} h={70} title="firewall-cmd" subtitle="firewalld 的 CLI" />
<Box x={350} y={90} w={150} h={70} title="nft" subtitle="nftables 低階指令" />
<Box x={550} y={90} w={150} h={70} title="ufw" subtitle="UFW 的 CLI" />
<Box x={750} y={90} w={190} h={70} title="iptables" subtitle="相容層/legacy" />
{/* Middle row: managers */}
<Box x={150} y={210} w={260} h={90} title="firewalld" subtitle="上層總管(zones/rich rules)" />
<Box x={560} y={210} w={240} h={90} title="UFW" subtitle="上層總管(Ubuntu 常見)" />
{/* Engine */}
<Box x={390} y={360} w={320} h={100} title="nftables" subtitle="底層門神(Kernel 規則)」" />
{/* Control-plane arrows */}
<Arrow x1={205} y1={160} x2={280} y2={210} label="管理" />
<Arrow x1={625} y1={160} x2={680} y2={210} label="管理" />
<Arrow x1={480} y1={160} x2={540} y2={360} label="直接下規則" />
<Arrow x1={835} y1={160} x2={640} y2={360} label="相容層" dashed />
<Arrow x1={280} y1={300} x2={470} y2={360} label="產生/維護規則" />
<Arrow x1={680} y1={300} x2={630} y2={360} label="產生/維護規則" />
{/* Data-plane objects */}
<Box x={70} y={590} w={200} h={70} title="網卡/封包" subtitle="ens10f0 / …" />
<Box x={820} y={590} w={220} h={70} title="應用程式" subtitle="python http.server 等" />
{/* Data-plane arrows (bent to pass through nftables) */}
<BendArrow points={[[170, 590], [170, 520], [550, 460]]} label="封包進入內核" />
<BendArrow points={[[710, 460], [930, 520], [930, 590]]} label="判決後交付/丟棄" />
{/* Legend */}
<g>
<rect x={20} y={450} width={320} height={34} fill="white" stroke="#e5e5e5" rx={8} ry={8} />
<text x={35} y={472} fontSize={12}>— 實線箭頭:一般控制面流程(設定→規則)</text>
<text x={35} y={488} fontSize={12}>╌╌ 虛線箭頭:iptables 相容層路徑</text>
</g>
</svg>
</div>
</div>
);
}
```
:::
- ### 重點
* **nftables** = 底層門神(真正擋/放封包)。`nft` 是它的低階操作工具。
* **firewalld / UFW** = 上層總管(把你的策略翻成底層規則)。`firewall-cmd`/`ufw` 是它們的前端指令。
* 用 `nft` 直接改規則會**生效**,但被上層總管(例如 firewalld)**reload 後可能覆蓋**。
* **不要同時啟用 firewalld 與 UFW**,避免互踩規則。
- ### 資訊流關係(控制面 vs. 資料面)
```
【控制面(設定規則)】
你 → firewall-cmd → firewalld ─┐
你 → ufw → UFW ──┼─► nftables(或 iptables 相容層)→ 寫入/更新內核規則
你 → nft(直上) ──────────────┘
【資料面(封包實際流動)】
網卡(ens10f0/…) → Netfilter hooks → nftables 規則判決 → ACCEPT/DROP/REJECT → 應用程式/棄置
(這條路與你用哪個上層總管無關;只看內核現有規則)
```
- ### 小抄(判斷誰在當家)
* 看 firewalld 是否在跑:`sudo firewall-cmd --state`(running = 由 firewalld 管)
* 看 nftables 現況:`sudo nft list ruleset`(**最終真相**)
* firewalld 後端:`grep -i ^FirewallBackend /etc/firewalld/firewalld.conf`(多半是 `nftables`)
* iptables 是否走 nft 相容層:`iptables -V`(出現 `nf_tables` 代表相容層)
<br>
---
## firewall-cmd
- ### 安裝方式
```
sudo apt install firewalld
```
- ### 服務狀態確認
```bash
# 確認 firewalld 是否在跑
sudo firewall-cmd --state
```
```
$ sudo systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2025-08-14 15:58:49 CST; 1 week 3 days ago
Docs: man:firewalld(1)
Main PID: 2459 (firewalld)
Tasks: 2 (limit: 309132)
Memory: 36.9M
CPU: 3.397s
CGroup: /system.slice/firewalld.service
└─2459 /usr/bin/python3 /usr/sbin/firewalld --nofork --nopid
```
- ### 公網區域(public)臨時開 38888/tcp
```
# 公網區域(public)臨時開 38888/tcp
sudo firewall-cmd --zone=public --add-port=38888/tcp
```
- ### 常用操作
```bash=
# 看有哪些 Zone
sudo firewall-cmd --get-zones
# 看預設 Zone
sudo firewall-cmd --get-default-zone
# 設定預設 Zone(影響未綁定的介面/來源)
sudo firewall-cmd --set-default-zone=public
# 看哪些 Zone 正在用 & 對應介面/來源
sudo firewall-cmd --get-active-zones
# 把介面綁到某 Zone(runtime)
sudo firewall-cmd --zone=public --change-interface=ens10f0
# 持久化(建議加 --permanent,再 reload)
sudo firewall-cmd --permanent --zone=public --change-interface=ens10f0
sudo firewall-cmd --reload
# 在某 Zone 開服務/連接埠
sudo firewall-cmd --zone=public --add-service=ssh
sudo firewall-cmd --zone=public --add-port=48888/tcp
# 永久化
sudo firewall-cmd --permanent --zone=public --add-port=48888/tcp
sudo firewall-cmd --reload
# 查看某 Zone 目前完整設定
sudo firewall-cmd --zone=public --list-all
```
<br>
---
## 情境1:server 有設定 iptables ,開放某個子網域連入,卻無法連入該 server
### iptables
```
sudo iptables -A INPUT -s 10.78.153.0/24 -p tcp -j ACCEPT
```
### 測試方法
```
$ sudo python3 -m http.server 48888 --bind 0.0.0.0
Serving HTTP on 0.0.0.0 port 48888 (http://0.0.0.0:48888/) ...
---
# 先看目前值(0=關閉, 1=嚴格, 2=寬鬆)
$ sysctl net.ipv4.conf.all.rp_filter
net.ipv4.conf.all.rp_filter = 0
$ sysctl net.ipv4.conf.default.rp_filter
net.ipv4.conf.default.rp_filter = 2
$ sysctl net.ipv4.conf.ens10f0.rp_filter
net.ipv4.conf.ens10f0.rp_filter = 2
$ sysctl net.ipv4.conf.ens10f0/206.rp_filter
net.ipv4.conf.ens10f0/206.rp_filter = 0
---
$ sudo ufw status
Status: inactive
---
$ sudo iptables -L
(略)
---
$ sudo nft list ruleset
(略)
---
# 瀏覽器:http://10.78.26.241:48888/
$ sudo tcpdump -ni any tcp port 48888
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
# 封包確實有進來
15:50:55.689891 ens10f0 In IP 10.78.153.144.57094 > 10.78.26.241.48888: Flags [S], seq 1070280506, win 64240, options [mss 1460,sackOK,TS val 4117789310 ecr 0,nop,wscale 7], length 0
15:50:55.689893 ens10f0.206 In IP 10.78.153.144.57094 > 10.78.26.241.48888: Flags [S], seq 1070280506, win 64240, options [mss 1460,sackOK,TS val 4117789310 ecr 0,nop,wscale 7], length 0
15:50:55.691515 ens10f0 In IP 10.78.153.144.57098 > 10.78.26.241.48888: Flags [S], seq 2965925806, win 64240, options [mss 1460,sackOK,TS val 4117789312 ecr 0,nop,wscale 7], length 0
15:50:55.691517 ens10f0.206 In IP 10.78.153.144.57098 > 10.78.26.241.48888: Flags [S], seq 2965925806, win 64240, options [mss 1460,sackOK,TS val 4117789312 ecr 0,nop,wscale 7], length 0
---
# 瀏覽器:http://10.78.26.241:48888/
This site can’t be reached
http://10.78.26.241:48888/ is unreachable.
ERR_ADDRESS_UNREACHABLE
```
<br>
## 無法連進任意 port ----> 分析 nftables
### `$ sudo iptables -L`
```
$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT tcp -- 10.78.153.0/24 anywhere
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER-USER all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp -- anywhere 172.17.0.2 tcp dpt:8888
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
RETURN all -- anywhere anywhere
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target prot opt source destination
DROP all -- anywhere anywhere
RETURN all -- anywhere anywhere
Chain DOCKER-USER (1 references)
target prot opt source destination
RETURN all -- anywhere anywhere
```
---
### `$ sudo nft list ruleset`
```
$ sudo nft list ruleset
table inet firewalld {
...
chain filter_INPUT {
...
jump filter_INPUT_ZONES
...
}
...
chain filter_INPUT_ZONES {
...
goto filter_IN_public
}
...
chain filter_IN_public {
jump filter_INPUT_POLICIES_pre
jump filter_IN_public_pre
jump filter_IN_public_log
jump filter_IN_public_deny
jump filter_IN_public_allow
jump filter_IN_public_post
jump filter_INPUT_POLICIES_post
meta l4proto { icmp, ipv6-icmp } accept
reject with icmpx admin-prohibited (擋掉了)
}
...
chain filter_IN_public_deny {
}
chain filter_IN_public_allow {
tcp dport 22 ct state { new, untracked } accept
ip6 daddr fe80::/64 udp dport 546 ct state { new, untracked } accept
}
```
---
### 分析原因
- ### gpt-5 (2025/08/21)
看起來問題已經很明確了:**firewalld(nftables)正在擋你 48888/TCP**。
你用 `tcpdump` 已經看到來自 `10.78.153.144` 的 SYN 封包進來,但服務端沒有回 SYN-ACK;在你貼的 `nft` 規則裡,public zone 只 **允許 22/TCP**,其他進來的連線最後都被 `reject with icmpx admin-prohibited` 擋掉了(你用 `tcp port 48888` 當過濾,因此沒看到 ICMP 拒絕封包)。
重點觀察:
* `table inet firewalld` → `filter_INPUT` 會跳到 `filter_IN_public`,而 `filter_IN_public_allow` 只有 `tcp dport 22 accept`。
* `iptables -L` 雖然看起來放行,但現在 **真正生效的是 nftables/firewalld**;所以 iptables 的那條允許規則救不了你。
下面用最小變動把 48888 打開(建議):
### 1) 立即打開 48888/TCP(runtime)
```bash
# 確認 firewalld 是否在跑
sudo firewall-cmd --state
# 公網區域(public)臨時開 48888/tcp
sudo firewall-cmd --zone=public --add-port=48888/tcp
```
### 2) 讓設定永久化
```bash
sudo firewall-cmd --zone=public --add-port=48888/tcp --permanent
sudo firewall-cmd --reload
```
### 3)(可選)直接用 nft 新增一條允許規則(等效、偏低階)
如果你的環境不使用或沒安裝 `firewall-cmd`,可以直接加 nft 規則:
```bash
sudo nft add rule inet firewalld filter_IN_public_allow tcp dport 48888 ct state { new, untracked } accept
```
### 4) 再次驗證
```bash
# 確認服務有在聽 0.0.0.0:48888
ss -ltnp | grep :48888
# 抓封包(這次也看 ICMP 拒絕)
sudo tcpdump -ni any 'host 10.78.153.144 and (tcp port 48888 or icmp)'
# 從客戶端重試 http://10.78.26.241:48888/
```
---
### 可能的加分檢查(若仍不通)
* 確認本機 IP 綁在你以為的介面上:
```bash
ip -br -4 addr | grep 10\.78\.26\.241
```
---
### 為什麼會這樣?
* 你同時看到 **iptables** 和 **nftables** 規則,但 **firewalld(nftables)在這台機器上是主導**,public zone 預設只開 22/TCP。
* 因此你看到 SYN 進來(路由、ARP 都 OK),但被 nftables 在 `filter_IN_public` 收尾處拒絕,瀏覽器才會報 `ERR_ADDRESS_UNREACHABLE`。開啟 48888 後就能正常回應。
<br>
{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}