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(上層總管) > 視覺化層級與資訊流:上是控制面(設定規則),下是資料面(封包實際流動)。 ![](https://hackmd.io/_uploads/ByD6dn4Ygl.png) :::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 %}