Mạng Linux & Container/Container ra Internet — SNAT và MASQUERADE
11/21
Bài 11 / 21~13 phútContainer NetworkingMiễn phí lượt xem

Container ra Internet — SNAT và MASQUERADE

Container private IP ra Internet: bật ip_forward, MASQUERADE vs SNAT, đường gói container → bridge → host → NAT → WAN. Pitfall FORWARD DROP và quên masquerade.

TL;DR: Container mang IP private trên bridge (ví dụ 10.10.0.10) không tự “thấy” Internet. Host phải forward gói giữa bridge và NIC ra ngoài (net.ipv4.ip_forward=1), cho phép path filter FORWARD, rồi rewrite nguồn bằng MASQUERADE hoặc SNAT trên nat POSTROUTING — server ngoài chỉ thấy IP host, conntrack nhớ mapping để reply về đúng container. Đường gói: container → veth → bridge → host L3 → NAT → WAN. Pitfall kinh điển: FORWARD policy DROP (LAN ảo sống, Internet chết) hoặc quên masquerade (gói ra với source private → reply không về).

Sprint review staging: bridge lab kiểu mini-challenge vừa xong — c1 ping c2 mượt. Product owner demo “service trong container gọi payment API”. Host curl API OK. Trong netns c1, curl timeout. Junior đã iptables -L INPUT và bảo “firewall host chặn”. Bạn hỏi ba câu: “sysctl net.ipv4.ip_forward?” · “gói container ra WAN đi FORWARD hay INPUT?” · “iptables -t nat -L POSTROUTING -n -v còn MASQUERADE/SNAT cho subnet bridge không?” Bridge L2 lành; path forward + NAT egress mới thiếu — không phải payment “chậm”.

Khái niệm SNAT/MASQUERADE đã có ở Foundations — NAT & port forwarding; bản đồ hook/chain ở M1 — iptables & netfilter. Bài này trace end-to-end trên topology container/bridge và implement egress bằng tay.

Bài này faded: lab worked dựng ip_forward + MASQUERADE; bạn tự điền rule FORWARD khi policy DROP và tự chọn SNAT tĩnh trước khi đối chiếu đáp án.

1. Analogy — Toà nhà ra đường: cổng hành lang + tem địa chỉ

Nhớ netns = căn hộbridge = switch hành lang. Container gọi Internet giống nhân viên phòng nội bộ gọi ra ngoài:

  1. Cổng hành lang xuyên toà phải mở — host cho gói đi xuyên từ bridge sang NIC WAN (ip_forward + chain FORWARD).
  2. Tem địa chỉ ra đường — số phòng 10.10.0.10 không route trên Internet. Lễ tân dán số toà (IP host) lên phong bì đi ra, ghi sổ cuộc gọi (NAT table / conntrack).
  3. Tem dán lúc ra cửa — đúng chỗ POSTROUTING (sau khi đã biết “ra iface nào”), không phải lúc còn trong phòng.
  4. Reply về số toà + “nhánh” (port) → sổ cuộc gọi trả về đúng phòng.
Đời thườngLinux / container
Cho phép đi xuyên toànet.ipv4.ip_forward=1 + filter FORWARD ACCEPT
Dán số toà lên thư đi raSNAT / MASQUERADE (đổi source)
Sổ “cuộc này của phòng X”conntrack NAT mapping
Tem dán lúc ra cửanat POSTROUTING
Chỉ soi quầy lễ tân nội bộNhầm INPUT — mù path egress container
💡 Cách nhớ

Ping cùng bridge = hai phòng nói trong hành lang — không chứng minh đã ra đường. Ra Internet = forward + tem SNAT/MASQUERADE. Foundations dạy tem; bài này dán tem trên host Docker-style.

2. Đường gói: container → bridge → host → NAT → out

2.1 Topology giả định (tiếp mini-challenge)

Dùng lại plan IP quen thuộc (hoặc topology Docker 172.17.0.0/16 — cùng mental model):

ObjectVai tròVí dụ
Netns c1“Container”stack riêng (bài 01)
c1-eth0Đầu veth trong netns10.10.0.10/24, default via 10.10.0.1
veth-c1Đầu host, master bridgekhông IP
br0Bridge + gateway10.10.0.1/24
eth0 (host)Iface ra WAN / lab uplinkIP public hoặc IP LAN host
Target“Internet” hoặc peer ngoài subnet8.8.8.8, API HTTPS, …
flowchart LR
    subgraph C1["Netns c1"]
        APP["curl / app"]
        ETH["c1-eth0 10.10.0.10"]
    end
    subgraph HOST["Init / host netns"]
        V["veth-c1"]
        BR["br0 10.10.0.1"]
        FW["filter FORWARD"]
        NAT["nat POSTROUTING MASQUERADE"]
        PHY["eth0 WAN"]
    end
    WAN["Outside"]
    APP --> ETH
    ETH -->|"veth peer"| V
    V --> BR
    BR --> FW
    FW --> NAT
    NAT --> PHY
    PHY --> WAN

2.2 Từng bước L3 (request)

  1. Trong netns c1: app gửi gói src=10.10.0.10 dst=8.8.8.8. Default route → gateway 10.10.0.1 dev c1-eth0.
  2. Veth peer: frame sang veth-c1 trên host; bridge br0 nhận (L2 trong LAN ảo).
  3. Gateway = host: IP 10.10.0.1 thuộc host → kernel host route gói ra iface WAN (eth0), không deliver local process → path forward.
  4. filter FORWARD: policy/rule phải ACCEPT (hoặc jump chain cho phép). DROP ở đây = timeout dù ip route trong container đẹp.
  5. nat POSTROUTING: MASQUERADE hoặc SNAT đổi src thành IP host trên iface egress. Server ngoài thấy host, không thấy 10.10.0.10.
  6. Conntrack: kernel ghi mapping (src/dst/port) để reply reverse-NAT về 10.10.0.10 rồi đẩy lại bridge/veth vào netns.

Vì sao thiếu NAT thì “có lúc ra được” nhưng reply chết?
Một số mạng lab lỏng có thể forward gói source private; trên Internet thật, router/ISP không route 10.0.0.0/8 / 172.16/12 / 192.168/16 về bạn. Kể cả gói request “lọt”, reply không có đường về đúng host:port đã map — triệu chứng hay gặp: tcpdump thấy SYN ra, không có SYN-ACK về container.

2.3 ip_forward — công tắc router trên host

# Read current (0 = off, 1 = on)
sysctl net.ipv4.ip_forward
# or:
cat /proc/sys/net/ipv4/ip_forward

# Enable until reboot (lab)
sudo sysctl -w net.ipv4.ip_forward=1
Giá trịÝ nghĩa
0Host không forward giữa iface — bridge LAN có thể vẫn ping nội bộ; không ra WAN qua host
1Kernel cho phép forward (vẫn bị filter FORWARD và thiếu NAT siết thêm)

Docker Engine khi dùng bridge network thường bật forwarding giúp bạn. Lab from-scratch / hardening playbook / “tắt IP forward cho an toàn” hay để 0 → đúng class lỗi sprint review đầu bài.

📌 Recall Module 1

Gói xuyên host (container → WAN) đi FORWARD, không phải INPUT. Survey chain Docker và pitfall rule order: iptables & netfilter. Bài này cài rule tối thiểu cho lab tay, không survey full graph Engine.

3. MASQUERADE vs SNAT — khác nhau chỗ nào khi đổi nguồn?

Cả hai là Source NAT trên POSTROUTING (chiều egress). Foundations: SNAT/MASQUERADE.

SNATMASQUERADE
ÝGhim source ra một IP cố địnhLấy IP hiện tại của iface egress lúc gửi
Cú pháp ý tưởng-j SNAT --to-source 203.0.113.10-j MASQUERADE (thường kèm -o eth0 hoặc ! -o br0)
Hợp khiIP public/elastic ổn định (cloud EIP gán cố định)DHCP / IP host đổi (laptop, nhà, nhiều cloud default)
Chi phíRẻ hơn một chút (không lookup addr iface mỗi gói theo cùng cách)Linh hoạt khi iface đổi IP
Docker bridge mặc địnhÍt khi hardcode IP hostMASQUERADE subnet bridge ra ngoài
# Pattern A — MASQUERADE (Docker-like): traffic from bridge subnet leaving non-bridge ifaces
sudo iptables -t nat -A POSTROUTING -s 10.10.0.0/24 ! -o br0 -j MASQUERADE

# Pattern B — SNAT static (only if you know stable egress IP)
# sudo iptables -t nat -A POSTROUTING -s 10.10.0.0/24 ! -o br0 -j SNAT --to-source 203.0.113.10

Vì sao ! -o br0?
Tránh SNAT traffic nội bộ bridge (container ↔ container) khi không cần. Chỉ rewrite khi gói rời domain bridge ra iface khác.

DNAT không phải bài này. Publish host:8080 → container:80bài 05đổi đích ở PREROUTING. Egress API call = đổi nguồn POSTROUTING.

4. Worked lab — bật egress cho một “container”

Chạy trên Linux có netns + iptables + sudo. macOS/Docker Desktop host không thay view ip netns Linux. Lab cần path ra ngoài thật (NIC/wifi host có default route) hoặc bạn tự dựng peer “WAN giả” — dưới đây giả định host đã ping 8.8.8.8 OK.

4.1 Skeleton tối thiểu (1 netns + bridge)

Nếu còn topology mini-challenge (c1/c2/br0) có thể tái dùng c1. Skeleton gọn:

# --- cleanup leftovers (safe if missing) ---
sudo ip netns del c1 2>/dev/null || true
sudo ip link del br0 2>/dev/null || true

# --- bridge + one container netns ---
sudo ip netns add c1
sudo ip link add br0 type bridge
sudo ip link set br0 up
sudo ip addr add 10.10.0.1/24 dev br0

sudo ip link add veth-c1 type veth peer name c1-eth0
sudo ip link set c1-eth0 netns c1
sudo ip link set veth-c1 master br0
sudo ip link set veth-c1 up

sudo ip netns exec c1 ip link set lo up
sudo ip netns exec c1 ip link set c1-eth0 up
sudo ip netns exec c1 ip addr add 10.10.0.10/24 dev c1-eth0
sudo ip netns exec c1 ip route add default via 10.10.0.1

# Prove L3 to gateway (LAN only — not Internet yet)
sudo ip netns exec c1 ping -c 2 10.10.0.1

Gateway OK không đồng nghĩa Internet OK.

4.2 Predict — trước khi bật NAT

Thử đoán trước khi chạy

Host đã ping -c1 8.8.8.8 OK. Trong c1 default route qua 10.10.0.1, chưa đụng ip_forward / NAT.
Đoán: sudo ip netns exec c1 ping -c 2 8.8.8.8 — success, partial, hay fail? Viết ra một lý do cơ chế (forward? source address? firewall?) trước khi chạy §4.3.

4.3 Chứng minh fail lớp forward/NAT (rồi bật từng lớp)

# Often 0 on hardened/lab hosts
sysctl net.ipv4.ip_forward

# Try from container (expect fail or no useful replies while forward/NAT missing)
sudo ip netns exec c1 ping -c 2 8.8.8.8 || true

Bật forward:

sudo sysctl -w net.ipv4.ip_forward=1

Chỉ bật forward chưa đủ nếu:

  • filter FORWARD policy DROP và không có rule ACCEPT, hoặc
  • không có MASQUERADE/SNAT.

Rule lab tối thiểu (iptables):

# Forward path: allow bridge <-> rest (lab-simple; tighten later)
sudo iptables -A FORWARD -i br0 -j ACCEPT
sudo iptables -A FORWARD -o br0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# Egress SNAT dynamic
sudo iptables -t nat -A POSTROUTING -s 10.10.0.0/24 ! -o br0 -j MASQUERADE

# Verify
sysctl net.ipv4.ip_forward
sudo iptables -L FORWARD -n -v
sudo iptables -t nat -L POSTROUTING -n -v

Thử lại từ container:

sudo ip netns exec c1 ping -c 3 8.8.8.8
# If ICMP blocked on your network, try TCP/HTTP instead when available:
# sudo ip netns exec c1 curl -sS -o /dev/null -w "%{http_code}\n" --connect-timeout 5 https://example.com || true

Đọc counter: chạy ping/curl lại, xem dòng MASQUERADE và rule FORWARD pkts tăng — bằng chứng gói đi đúng chain (cùng thói quen survey M1).

sudo iptables -t nat -L POSTROUTING -n -v
sudo iptables -L FORWARD -n -v
⚠️ Lab vs production policy

Rule -i br0 -j ACCEPTlab tối giản. Host Docker thật siết hơn (chain DOCKER-USER, isolation). Đừng copy ACCEPT rộng lên prod; học đủ mảnh (forward + NAT), rồi siết bằng policy có chủ đích (M1 iptables).

5. Fade — FORWARD DROP và SNAT tĩnh

5.1 Tự chẩn: policy DROP

Tự điền — trước khi xem đáp án

Giả sử lab §4 đang ra Internet được. Ai đó chạy hardening:

sudo iptables -P FORWARD DROP
# (không đụng POSTROUTING — MASQUERADE vẫn còn)

Triệu chứng: c1 vẫn ping 10.10.0.1 / peer cùng bridge OK; ping 8.8.8.8 fail.
Viết ra: (1) chain/table nào giết egress? (2) hai hướng rule (hoặc tương đương) bạn thêm để lab sống lại mà không đổi policy về ACCEPT toàn cục? Không mở đáp án dưới trước.

Đáp án fade (đối chiếu):

  1. filter FORWARD — policy DROP + không rule ACCEPT khớp path bridge ↔ WAN. NAT table còn cũng vô ích nếu gói đã DROP trước khi ra POSTROUTING hữu ích / hoặc chiều về bị chặn.
  2. Lab tối thiểu (giống §4.3): ACCEPT từ -i br0 ra ngoài; ACCEPT -o br0 cho RELATED,ESTABLISHED (reply). Hoặc siết theo iface WAN cụ thể. Không cần -P FORWARD ACCEPT nếu rule đủ.
# Re-check policy and counters after you add rules
sudo iptables -L FORWARD -n -v
sudo ip netns exec c1 ping -c 2 8.8.8.8

5.2 Tự chọn: MASQUERADE hay SNAT?

Tự chọn — trước đáp án

Ba host. Với mỗi host, ghi MASQUERADE hay SNAT tĩnh (và vì sao):

  1. Laptop dev, Wi‑Fi DHCP, IP đổi mỗi quán cà phê.
  2. VM cloud gắn Elastic IP cố định 203.0.113.50 trên eth0 cả tháng.
  3. Docker Engine mặc định trên docker0 (bạn survey, không redesign).

Viết xong mới đối chiếu bảng dưới.

HostChọnVì sao
(1) Laptop DHCPMASQUERADEIP egress đổi — SNAT ghim IP cũ → gãy sau renew
(2) EIP cố địnhSNAT (hoặc MASQUERADE vẫn chạy)SNAT hợp khi IP ổn định; MASQUERADE vẫn đúng nhưng SNAT tường minh hơn với EIP
(3) Docker defaultMASQUERADEEngine thường MASQUERADE subnet bridge — survey iptables -t nat -L POSTROUTING -n -v

Chuyển lab sang SNAT (khi biết IP egress host):

# Discover IPv4 on uplink (example: eth0 — use YOUR iface name)
ip -br addr show eth0

# Swap: delete MASQUERADE rule you added, add SNAT (adjust addresses)
sudo iptables -t nat -D POSTROUTING -s 10.10.0.0/24 ! -o br0 -j MASQUERADE
# Example only — replace 203.0.113.50 with your real egress IPv4:
# sudo iptables -t nat -A POSTROUTING -s 10.10.0.0/24 ! -o br0 -j SNAT --to-source 203.0.113.50

Nếu ghim sai IP (không phải IP đang có trên iface egress) → request “ra” trông lạ, reply không về host — cùng class “NAT lệch”.

6. Pitfall tổng hợp + map Docker

Nhầm 1: “Ping container↔container OK ⇒ Internet OK.”
✅ Ping cùng bridge chỉ chứng minh L2/L3 LAN ảo (lab 03). Egress cần ip_forward + FORWARD + MASQUERADE/SNAT.

Nhầm 2: “Soi iptables -L INPUT khi container không ra ngoài.”
✅ Path xuyên host là FORWARD (+ nat POSTROUTING). INPUT là gói tới process host.

Nhầm 3: “Chỉ bật ip_forward=1 là xong.”
✅ Forward bật nhưng policy FORWARD DROP hoặc thiếu NAT → vẫn chết. Ba công tắc, không một.

Nhầm 4: “MASQUERADE và SNAT là hai chiều khác nhau (ra/vào).”
✅ Cả hai là đổi nguồn egress. Chiều vào publish port là DNAT (bài 05).

Nhầm 5: “Flush iptables -F / -t nat -F cho sạch rồi container vẫn ra ngoài.”
✅ Dễ xoá MASQUERADE Docker + rule FORWARD Engine — outage hàng loạt. Sửa policy user qua chỗ đúng (DOCKER-USER trên host Docker), không flush mù.

# Cleanup THIS lesson's lab objects (iptables rules you added — delete explicitly)
sudo iptables -D FORWARD -i br0 -j ACCEPT 2>/dev/null || true
sudo iptables -D FORWARD -o br0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
sudo iptables -t nat -D POSTROUTING -s 10.10.0.0/24 ! -o br0 -j MASQUERADE 2>/dev/null || true

sudo ip netns del c1 2>/dev/null || true
sudo ip link del br0 2>/dev/null || true
# Deleting netns removes moved veth ends with it
Docker / runtimeObject bài này
docker0 + IP gatewaybr0 + 10.10.0.1/24
Container eth0c1-eth0 trong netns
Engine bật IP forwardsysctl net.ipv4.ip_forward=1
Rule MASQUERADE subnet bridgePOSTROUTING -s <bridge-cidr> … -j MASQUERADE
“Không ra Internet” sau hardeningFORWARD DROP / mất masquerade / ip_forward=0
flowchart TB
    A["Symptom: container curl WAN timeout"] --> B{"Ping gateway / peer on bridge?"}
    B -->|"no"| L2["Fix veth/bridge/IP/route in netns"]
    B -->|"yes"| C{"ip_forward == 1?"}
    C -->|"no"| FWD["sysctl ip_forward=1"]
    C -->|"yes"| D{"FORWARD counters / policy"}
    D -->|"DROP no accept"| FW["Add/fix FORWARD path"]
    D -->|"accept path OK"| E{"nat POSTROUTING MASQUERADE/SNAT?"}
    E -->|"missing"| NAT["Add MASQUERADE or SNAT"]
    E -->|"present"| APP["DNS / app / upstream — next"]

📚 Deep Dive — man page & docs

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

Spec / reference:

Ghi chú: Host dùng nftables backend: mental model table/chain/hook giữ; lệnh survey nft list ruleset. Lab bài dùng iptables cho khớp Docker docs phổ biến.

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

Tóm tắt

  • Container private ra Internet = host ip_forward=1 + filter FORWARD cho path bridge ↔ WAN + nat POSTROUTING MASQUERADE hoặc SNAT.
  • Đường gói: netns → veth → bridge → L3 host → FORWARD → rewrite source → WAN; reply nhờ conntrack.
  • MASQUERADE = SNAT theo IP iface egress (IP động / Docker default); SNAT tĩnh khi IP ổn định.
  • Ping cùng bridge không chứng minh egress; debug INPUT trước = nhìn nhầm chain.
  • Pitfall: FORWARD DROP, quên masquerade, flush nat Docker, nhầm SNAT với DNAT.
  • Fade: tự chẩn DROP + tự chọn MASQUERADE/SNAT theo kiểu host.

7. Tự kiểm tra

Tự kiểm tra
Q1
Liệt kê ba điều kiện kernel/netfilter tối thiểu để container trên bridge private IP ra Internet qua host. Thiếu một điều thì triệu chứng gần đúng là gì?
(1) net.ipv4.ip_forward=1 — host forward giữa bridge và iface WAN. (2) Path filter FORWARD ACCEPT (policy/rule) cho chiều đi và reply. (3) nat POSTROUTING MASQUERADE hoặc SNAT đổi source private → IP host. Thiếu forward: gói không router ra WAN. Thiếu FORWARD accept: DROP dù route đẹp. Thiếu NAT: source private, reply/Internet path hỏng dù có lúc request “lọt” lab lỏng.
Q2
Trace một gói curl từ container 10.10.0.10 tới 8.8.8.8: object nào lần lượt (iface/netns), và source IP server ngoài nhìn thấy sau MASQUERADE?
Trong netns container: app → c1-eth0 (src 10.10.0.10) theo default via gateway bridge → veth peer host → bridge → routing host chọn iface WAN → filter FORWARD → nat POSTROUTING MASQUERADE đổi src thành IP host trên iface egress. Server ngoài thấy IP host (và port map conntrack), không thấy 10.10.0.10.
Q3
MASQUERADE và SNAT khác nhau chỗ nào? Khi nào bắt buộc nghiêng về MASQUERADE?
Cả hai đổi source (egress SNAT). SNAT ghim --to-source cố định; MASQUERADE lấy IP hiện tại của iface egress. IP host DHCP/đổi thường xuyên → MASQUERADE (tránh ghim IP cũ). Docker bridge default thường MASQUERADE. EIP cố định có thể SNAT tường minh.
Q4
Hai container cùng br0 ping nhau OK; cả hai không curl được Internet. Host curl OK. ip_forward=1. Bạn kiểm tra gì trước — INPUT hay FORWARD/nat — và vì sao?
Ưu tiên filter FORWARD (policy/rule/counter) và nat POSTROUTING MASQUERADE/SNAT cho subnet bridge. Traffic container ra WAN forward qua host, không terminate local như curl trên host (INPUT/OUTPUT local). INPUT xanh không chứng minh path container.
Q5
Vì sao hardening đặt iptables -P FORWARD DROP có thể làm “LAN ảo sống, Internet chết” dù MASQUERADE vẫn còn trong nat table?
Ping cùng bridge chủ yếu L2/L3 trong domain bridge — không phụ thuộc forward ra WAN. Egress cần gói đi chain FORWARD; policy DROP + không rule ACCEPT khớp → gói bị chặn. Rule nat còn trong table không cứu gói đã DROP trên filter path. Fix: rule ACCEPT có chọn lọc (lab: -i br0 / established -o br0), không nhất thiết đổi policy ACCEPT toàn cục.
Q6
Fade: junior chỉ sysctl ip_forward=1, không thêm NAT. Trên mạng thật, vì sao container timeout dù tcpdump host đôi khi thấy gói ra?
Gói có thể rời host với source private (10/8, 172.16/12, 192.168/16). Internet không route reply về dải đó / ISP drop; không có conntrack SNAT map thì reply không về đúng container. Cần MASQUERADE/SNAT POSTROUTING để bên ngoài nói chuyện với IP host và kernel reverse-NAT về 10.10.0.10.
Q7
Phân biệt nhanh SNAT egress bài này với DNAT publish port bài sau — đổi field nào, chain nat nào, chiều traffic nào?
SNAT/MASQUERADE: đổi source, thường POSTROUTING, chiều container/host ra ngoài. DNAT publish: đổi destination, thường PREROUTING, chiều vào từ client ngoài tới host:port map vào IP:port container. Nhớ: ra đổi nguồn, vào đổi đích.

Bài tiếp theo: Publish port — DNAT từ host vào container

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

Publish port — DNAT và docker -p