Mạng Linux & Container/iptables & netfilter — filter, NAT, hooks
5/21
Bài 5 / 21~13 phútLinux Network InternalsMiễn phí lượt xem

iptables & netfilter — filter, NAT, hooks

Netfilter hooks trong kernel, tables filter/nat/mangle, chains INPUT/FORWARD/PREROUTING/POSTROUTING, và rule Docker tự thêm — nền cho SNAT/DNAT module sau.

TL;DR: Netfilter là framework hook trong kernel trên path IP; iptables (hoặc nftables) là frontend user-space gắn rule vào hook đó. Rule nằm trong table (filter, nat, mangle…) và chain (PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING). Gói tới process host đi INPUT; gói xuyên host (container ↔ WAN, container ↔ container qua IP forward) đi FORWARD. Docker bridge inject chain riêng (DOCKER-USER, DOCKER, …) và thường MASQUERADE trên POSTROUTING — survey rule trước, SNAT/DNAT sâu ở Module 2. Pitfall kinh điển: thứ tự rule + FORWARD policy DROP làm “LAN ảo sống, Internet chết”.

UAT đêm trước go-live: hardening playbook chạy xong, hai container trên docker0 vẫn ping nhau. Sáng ra, curl registry từ container timeout. Junior soi INPUT và UFW — “firewall host chặn”. Bạn hỏi: “traffic container ra ngoài đi FORWARD hay INPUT? Counter chain DOCKER-USER / DOCKER-FORWARD tăng khi thử không?” L2 bridge (bài 03) lành; path IP forward + netfilter mới gãy — policy FORWARD DROP và jump Docker bị đụng, không phải “app registry chậm”.

Bài 01 — Linux network stack đã đặt tên hook trên đường gói; bài 03 cho LAN ảo qua docker0. Bài này gắn tables/chainsrule Docker thật vào đúng hook — bản đồ đọc iptables -L / -t nat -L trước khi đào SNAT/DNAT ở Module 2.

Bài này giải thích cơ chế bên dưới netfilter + survey rule Docker (chưa deep SNAT), fade: bạn tự map path gói vào chain trước khi đối chiếu lời giải.

1. Analogy — Trạm kiểm soát trên hành lang toà nhà

Nhớ analogy bưu cục bài 01: gói đã vào toà (ring → softirq → sk_buff). Bây giờ hành lang có trạm kiểm soát:

  1. Cửa vào sảnh — quét mọi người vừa bước vào, trước khi biết họ lên tầng nội bộ hay đi xuyên sang toà bên (PREROUTING).
  2. Quầy lễ tân tầng — chỉ người đến làm việc trong toà này (INPUT).
  3. Hành lang xuyên toà — khách đi xuyên sang cửa khác, không dừng làm việc tại đây (FORWARD).
  4. Cửa ra — tem / đổi nhãn trước khi ra đường (POSTROUTING — nơi NAT “đóng gói” địa chỉ ra ngoài hay gặp).
  5. Lối ra từ phòng nội bộ — nhân viên toà xuất phát từ trong (OUTPUT).
Đời thườngNetfilter / iptables
Khung “có trạm nào trên hành lang”Netfilter hooks trong kernel
Sổ quy tắc từng trạmiptables/nft rule
Loại sổ (cho phép / đổi tem / ghi chú)Tables: filter / nat / mangle
Từng trạm có tênChains: PRE / INPUT / FORWARD / OUTPUT / POST
“Ai viết rule cho container”Docker daemon inject chain DOCKER*
💡 Cách nhớ

Hook = chỗ đứng trên đường gói. Table = loại quyết định. Chain = hàng rule tại chỗ đó. Container “không ra net” hầu như luôn là FORWARD + nat POSTROUTING, không phải INPUT chặn SSH host.

2. Netfilter vs iptables — ai làm gì?

Netfilter là framework trong kernel: tại các điểm cố định trên path IP, kernel gọi hook — danh sách callback có thể ACCEPT, DROP, QUEUE, hoặc sửa gói (NAT, mark). Gói vẫn là sk_buff trên softirq/stack path bài 01; hook cắm vào path đó.

iptables (và nftables ngày nay) là user-space + module kernel tương ứng: bạn khai báo rule, tool đẩy vào cấu trúc netfilter. Docker Engine mặc định vẫn tạo rule kiểu iptables cho bridge network (có backend nftables trên phiên bản mới — cùng ý: rule filter/NAT cho isolation, publish port, masquerade).

Netfilteriptables / nft
TầngKernel hooksFrontend + rule store
Bạn “thấy”Counter, path, dropiptables -L, nft list
Docker đụngHook PRE/FORWARD/POST…Chain DOCKER*, MASQUERADE
flowchart TB
    SKB["sk_buff on IP path"] --> HOOK["Netfilter hook"]
    HOOK --> RULES["iptables or nft rules"]
    RULES -->|"ACCEPT"| CONT["Continue stack"]
    RULES -->|"DROP / REJECT"| DROP["Packet dropped"]
    RULES -->|"NAT target"| NAT["Rewrite addr or port"]

Vì sao không “firewall process” user-space mỗi gói? Throughput và latency: quyết định nằm trên soft path kernel. User-space chỉ cấu hình. Khi debug: hỏi hook + chain + counter rule, không hỏi PID “firewall.exe”.

NAT khái niệm (SNAT/DNAT/port forward) đã có ở Foundations — NAT và port forwarding. Bài này map chỗ NAT bám trên Linux (PRE/POST + table nat), chưa đào ví dụ SNAT container sâu (→ M2 — container ra Internet SNAT).

3. Tables — filter, nat, mangle (và raw)

Một table nhóm rule theo mục đích. Cùng tên chain có thể xuất hiện ở nhiều table — thứ tự table × hook do kernel định (đừng học thuộc lòng hết; nhớ filter vs nat cho container là đủ lúc đầu).

TableViệc chínhChain hay gặp (IPv4)
filterCho phép / chặn (ACCEPT, DROP, REJECT)INPUT, FORWARD, OUTPUT
natĐổi địa chỉ/port (DNAT, SNAT, MASQUERADE, REDIRECT)PREROUTING, INPUT, OUTPUT, POSTROUTING
mangleSửa mark/TTL/… (ít “policy firewall”)Mọi hook chính
rawConnection tracking exceptions (nâng cao)PREROUTING, OUTPUT

filter trả lời: “gói này được đi tiếp không?”
nat trả lời: “địa chỉ/port trên header có cần đổi không?” (thường chỉ gói đầu của connection; conntrack nhớ flow).
mangle ít dùng khi mới đọc Docker — biết “có table này để mark” là đủ.

# List filter table (default)
sudo iptables -L -n -v

# List NAT table — Docker MASQUERADE / publish sống ở đây
sudo iptables -t nat -L -n -v

# Optional: mangle
sudo iptables -t mangle -L -n -v

-n = không reverse DNS (nhanh, ổn định lab). -v = counter packet/byte — vàng khi hỏi “rule này có dính traffic không?”.

4. Chains builtin — PRE, INPUT, FORWARD, OUTPUT, POST

4.1 Bản đồ path (nhắc bài 01, gắn chain)

flowchart LR
    IN["Packet in"] --> PRE["PREROUTING"]
    PRE --> RT{"Local or Forward?"}
    RT -->|"local"| INPUT["INPUT"]
    RT -->|"forward"| FWD["FORWARD"]
    INPUT --> LOCAL["Local process"]
    FWD --> POST["POSTROUTING"]
    POST --> OUTDEV["Egress device"]
    APP["Local app send"] --> OUTPUT["OUTPUT"]
    OUTPUT --> POST
ChainGói nàoCâu hỏi policy
PREROUTINGMới vào stack IP, trước routing decision đầy đủDNAT / publish port “đổi đích” sớm
INPUTSau routing: dành cho host nàySSH, API host, health check node
FORWARDKhông terminate trên host — đi ra interface khácContainer ↔ WAN, router, nhiều bridge
OUTPUTSinh ra từ process trên hostÍt là path container bridge cổ điển
POSTROUTINGSắp ra deviceSNAT / MASQUERADE (đổi nguồn ra ngoài)

Hai câu tách lỗi ngay:

  1. Gói tới process trong container (sau DNAT) vs gói tới daemon trên host — khác path, khác chain.
  2. Container ping peer cùng docker0 chủ yếu L2 bridge (bài 03) — có thể không đi FORWARD L3. Container ra Internet gần như chắc FORWARD + POSTROUTING nat.

4.2 Rule matching & target (đủ để đọc)

Trong một chain, rule được duyệt từ trên xuống (first match wins với target kết thúc như ACCEPT/DROP; một số target kiểu LOG không dừng).

Khái niệmÝ nghĩa ngắn
MatchĐiều kiện: iface (-i/-o), IP/port, protocol, state/conntrack…
Target / jump-j ACCEPT, -j DROP, -j MASQUERADE, hoặc jump sang chain khác (-j DOCKER-USER)
PolicyDefault khi hết rule: ACCEPT hoặc DROP (iptables -P FORWARD DROP)
CounterCột pkts/bytes với -v — rule “chết” = 0 dù traffic đáng lẽ khớp
# Policy of built-in chains (filter)
sudo iptables -S | head
# Example lines you may see:
# -P FORWARD DROP
# -A FORWARD -j DOCKER-USER

4.3 Fade — tự map path trước lời giải

Thử điền — map chain (fade)

Tự viết ra trước khi xem §4.4. Với mỗi kịch bản, ghi table + chain builtin (và nếu đoán được: custom Docker chain) gói chắc chắn đi qua, và chain nào không liên quan:

  1. SSH vào daemon sshd trên host (không container).
  2. Container trên docker0 curl 1.1.1.1 (ra Internet qua NAT host).
  3. Client ngoài curl host:8080 trong khi container publish 8080→80 (chỉ tên hook/chain; chưa cần cú pháp DNAT).

Gợi ý định hướng: hỏi “gói terminate trên host process nào?” và “có đổi IP nguồn/đích không?” — không mở man page target NAT trước khi map xong.

4.4 Lời giải — map kịch bản

Kịch bảnPath chínhKhông phải chỗ “chặn container” đầu tiên
(1) SSH hostPREROUTING (thường ít rule) → routing localfilter INPUTFORWARD (gói không forward)
(2) Container → InternetTừ netns container → veth → bridge → IP forward host: filter FORWARD (+ jump DOCKER*) → nat POSTROUTING (MASQUERADE)filter INPUT (không phải path “vào sshd”)
(3) Ngoài → publish portnat PREROUTING (DNAT đổi đích → IP container) → forward → filter FORWARD / DOCKER* → vào bridge/veth container; response chiều ngược + SNAT/MASQUERADE theo conntrackNhầm “chỉ cần mở INPUT 8080” mà quên Docker đã DNAT + FORWARD

Cơ chế: routing decision sau PREROUTING tách local vs forward. Publish port đổi địa chỉ đích sớm (DNAT) nên gói “tưởng vào host:8080” có thể không còn là local-delivery thuần — vì thế rule UFW trên INPUT dễ bị bypass cảm giác: traffic đã rẽ sang path NAT/forward trước khi bạn tưởng tượng “firewall host cổ điển”. Chi tiết DNAT publish → Module 2 bài 05; ở đây chỉ cần đúng hook.

5. Docker survey — chain và rule tự thêm

Phạm vi bài: đọc và định vị rule Docker bridge. Không viết lại toàn bộ graph NAT; không tắt iptables daemon option nếu chưa hiểu hệ quả (docs Docker: tắt dễ gãy masquerade / isolation).

5.1 filter — FORWARD và họ DOCKER*

Trên host có Docker bridge, bạn thường thấy (tên chain có thể thêm bớt theo version Engine — đọc máy bạn):

Chain (filter)Vai trò survey
DOCKER-USERChỗ bạn được khuyến nghị gắn policy thêm; xử lý trước phần lớn rule Docker khác trên path forward
DOCKER-FORWARD / DOCKER / …Rule Engine: established, port publish, isolation bridge…
FORWARD (builtin)Thường có jump sang DOCKER-USER rồi các chain Docker; policy hay là DROP

Docker docs: khi Engine bật IP forwarding, nó có thể set default FORWARD policy DROP và chỉ ACCEPT những gì rule Docker (và DOCKER-USER) cho qua — host không tự thành router mở cho mọi iface trừ khi bạn chủ động cho phép.

# Survey filter path (host with Docker)
sudo iptables -L FORWARD -n -v
sudo iptables -L DOCKER-USER -n -v
sudo iptables -L DOCKER -n -v 2>/dev/null || true
# Newer engines may show DOCKER-FORWARD, DOCKER-BRIDGE, ...
sudo iptables -L -n -v | head -80

Vì sao append -A FORWARD ... của bạn “không chạy”?
Nếu đầu chain FORWARD đã -j DOCKER-USER và gói ACCEPT/DROP xong trong nhánh Docker, rule bạn append cuối FORWARD không bao giờ thấy gói. Đúng chỗ policy user: DOCKER-USER (insert/append trong chain đó), không phải “cuối FORWARD cho chắc”.

5.2 nat — MASQUERADE và chain DOCKER

sudo iptables -t nat -L -n -v
sudo iptables -t nat -L POSTROUTING -n -v
sudo iptables -t nat -L DOCKER -n -v 2>/dev/null || true
Quan sát điển hìnhÝ nghĩa
MASQUERADE (hoặc SNAT) trên POSTROUTING cho subnet bridge (vd 172.17.0.0/16)Container ra ngoài: nguồn “giả” thành IP host trên iface egress
Chain DOCKER trong natPublish port / map — survey; thao tác tay dễ lệch daemon
Không có rule nat + ip_forward=1Có forward logic nhưng đổi nguồn thiếu → WAN lạ/reply không về

Deep cơ chế SNAT/MASQUERADE từng bước + lab04 — Container ra Internet (SNAT). Deep publish DNAT05 — Publish port (DNAT).

5.3 Liên hệ bridge — khi nào cần netfilter?

TrafficBridge L2 (bài 03)Netfilter (bài này)
Hai container cùng docker0, cùng subnetPath chính: veth → FDB → vethThường không cần NAT; filter forward có thể vẫn đụng tuỳ isolation rule
Container → InternetTới gateway IP trên docker0 rồi rời L2 domainFORWARD + POSTROUTING MASQUERADE + ip_forward
Ngoài → port publishVào host ifacePREROUTING DNAT + FORWARD vào bridge/veth
flowchart TB
    C["Container"] -->|"veth"| BR["docker0 bridge L2"]
    BR -->|"same subnet peer"| C2["Other container"]
    BR -->|"IP to gateway host"| L3["Host IP forward"]
    L3 --> FW["filter FORWARD + DOCKER*"]
    FW --> POST["nat POSTROUTING MASQUERADE"]
    POST --> WAN["Outside"]

6. Pitfall tổng hợp

Nhầm 1: “Chặn/cho phép container bằng filter INPUT.”
✅ Path container bridge xuyên host là FORWARD (sau khi rời netns). INPUT là gói local host. Hardening chỉ soi INPUT → half path container.

Nhầm 2: “Thêm rule -A FORWARD cuối chain là đủ kiểm soát Docker.”
✅ Docker jump sớm sang DOCKER-USER / chain riêng. Gói ACCEPT/DROP trong đó không rơi xuống rule append cuối. Dùng DOCKER-USER cho policy thêm (và nhớ: sau DNAT, match theo IP nội bộ container trừ khi dùng conntrack original dest — nâng cao).

Nhầm 3: “FORWARD policy DROP = Docker hỏng hoàn toàn.”
✅ DROP default là bình thường khi Docker quản forward: rule ACCEPT có chọn lọc phải tồn tại. Hỏng khi mất jump/rule Docker (flush tay, firewall khác ghi đè) hoặc DOCKER-USER DROP nhầm — không phải “thấy DROP là gỡ Docker”.

Nhầm 4: “Ping cùng bridge OK ⇒ netfilter ổn.”
✅ Ping L2/L3 nội bộ không chứng minh MASQUERADE/FORWARD egress. Checklist tách: L2/FDB (bài 03) → ip_forwardiptables -t nat -L -v + FORWARD -v.

Nhầm 5: “iptables: false cho sạch rule host.”
✅ Docs Docker: tắt tạo rule dễ gãy masquerade/isolation/publish. Muốn siết policy: DOCKER-USER, không “tắt netfilter Docker” nếu chưa có thay thế đầy đủ.

⚠️ Rule order + FORWARD DROP

Ba bước trước khi đổ lỗi registry/DNS:

  1. Đúng chain: egress container → FORWARD / DOCKER-USER, không chỉ INPUT.
  2. Counter: iptables -L -n -v — rule/policy nào tăng khi reproduce?
  3. NAT còn không: -t nat -L POSTROUTING -n -v còn MASQUERADE (hoặc SNAT) cho subnet bridge?

Policy FORWARD DROP có chủ đích vẫn OK nếu rule Docker ACCEPT đúng path. Flush nhầm / rule user che jump mới là outage UAT đầu bài.

📚 Deep Dive — man page & docs

📚 Deep Dive — tài liệu gốc

Spec / reference:

Ghi chú version: Engine mới có thể dùng nftables backend — survey bằng nft list ruleset cùng mental model table/chain/hook. Bài lab lệnh dùng iptables vì vẫn phổ biến trên host Docker hiện tại.

Liên hệ các bài khác

Tóm tắt

  • Netfilter = hook kernel trên path IP; iptables/nft = frontend khai rule.
  • Tables: filter (allow/deny), nat (đổi addr/port), mangle (mark) — container hằng ngày: filter + nat.
  • Chains: PREROUTING → routing → INPUT | FORWARD → POSTROUTING; OUTPUT cho gói local origin.
  • Docker bridge: jump FORWARD → DOCKER-USER → …; nat DOCKER + MASQUERADE POSTROUTING — đọc counter trước khi sửa tay.
  • Pitfall: nhầm INPUT, append FORWARD sau jump Docker, tưởng policy DROP = bug, tin ping L2 đủ chứng minh egress.
  • Fade: tự map 3 kịch bản SSH / container→WAN / publish → đối chiếu bảng §4.4.
  • SNAT/DNAT sâu = Module 2; Module 1 chỉ cần bản đồ đúng chỗ.

7. Tự kiểm tra

Tự kiểm tra
Q1
Netfilter và iptables khác nhau chỗ nào? DROP gói xảy ra ở tầng nào?
Netfilter là framework hook trong kernel trên path IP/`sk_buff`. iptables (hoặc nftables) là frontend khai báo và nạp rule vào hook đó. DROP/ACCEPT/NAT execute trên path kernel — không phải một process user-space “bắt từng gói” như firewall cổ điển tưởng tượng.
Q2
Ba table filter, nat, mangle mỗi cái trả lời câu hỏi gì trên đường gói?
filter: cho phép hay chặn (ACCEPT/DROP/REJECT). nat: có đổi địa chỉ/port không (DNAT/SNAT/MASQUERADE). mangle: sửa metadata/mark (ít dùng khi mới survey Docker). Container hằng ngày chủ yếu đọc filter + nat.
Q3
Gói SSH vào sshd trên host đi chain filter nào? Gói từ container bridge ra Internet đi chain nào — vì sao không phải cùng một chỗ?
SSH host: sau routing local → filter INPUT. Container ra Internet: gói forward qua host → filter FORWARD (và jump Docker) rồi thường nat POSTROUTING MASQUERADE. Khác vì một gói terminate trên process host, gói kia xuyên host sang iface egress.
Q4
PREROUTING và POSTROUTING khác nhau về 'sớm/muộn' thế nào so với quyết định routing? DNAT publish và MASQUERADE gắn gần chain nào?
PREROUTING chạy sớm, trước routing decision đầy đủ — chỗ DNAT/publish cổ điển đổi đích. POSTROUTING chạy khi gói sắp ra device — chỗ SNAT/MASQUERADE đổi nguồn. Routing ở giữa tách local vs forward.
Q5
DOCKER-USER dùng để làm gì? Vì sao rule iptables -A FORWARD ... của bạn có thể 'không bao giờ khớp' dù policy FORWARD là DROP?
DOCKER-USER là chain Docker để user gắn policy thêm, thường được jump tới trước phần lớn rule Docker khác trên path forward. Rule append cuối builtin FORWARD nằm sau jump: nếu gói đã ACCEPT/DROP trong nhánh Docker/DOCKER-USER, rule cuối không thấy gói — kể cả khi default policy là DROP.
Q6
Hai container cùng docker0 ping nhau OK nhưng không ra Internet. Bridge/FDB lành. Bạn survey netfilter theo thứ tự nào?
Ping nội bộ chứng minh L2/LAN (bài 03), chưa chứng minh egress. Kiểm ip_forward, iptables -L FORWARD -n -v (jump Docker, counter), rồi iptables -t nat -L POSTROUTING -n -v tìm MASQUERADE/SNAT subnet bridge. Đừng bắt đầu bằng filter INPUT của host.
Q7
Fade: client ngoài truy cập host:8080 publish vào container. Bạn map nhầm chỉ 'mở INPUT 8080 là đủ'. Thiếu hook/table nào trong mental model?
Publish cổ điển cần nat PREROUTING (DNAT) đổi đích vào IP/port container, rồi path FORWARD / chain Docker vào bridge/veth — không dừng ở “mở INPUT như service host thuần”. RESPONSE chiều về phụ thuộc conntrack + SNAT/MASQUERADE. Chi tiết lab DNAT ở Module 2; bài này chỉ yêu cầu đúng chỗ trên bản đồ.

Bài tiếp theo: Tổng kết module — Linux network internals

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Tổng kết module Linux Network Internals