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ộ và 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:
- 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). - Tem địa chỉ ra đường — số phòng
10.10.0.10khô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). - 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.
- Reply về số toà + “nhánh” (port) → sổ cuộc gọi trả về đúng phòng.
| Đời thường | Linux / container |
|---|---|
| Cho phép đi xuyên toà | net.ipv4.ip_forward=1 + filter FORWARD ACCEPT |
| Dán số toà lên thư đi ra | SNAT / MASQUERADE (đổi source) |
| Sổ “cuộc này của phòng X” | conntrack NAT mapping |
| Tem dán lúc ra cửa | nat POSTROUTING |
| Chỉ soi quầy lễ tân nội bộ | Nhầm INPUT — mù path egress container |
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):
| Object | Vai trò | Ví dụ |
|---|---|---|
Netns c1 | “Container” | stack riêng (bài 01) |
c1-eth0 | Đầu veth trong netns | 10.10.0.10/24, default via 10.10.0.1 |
veth-c1 | Đầu host, master bridge | không IP |
br0 | Bridge + gateway | 10.10.0.1/24 |
eth0 (host) | Iface ra WAN / lab uplink | IP public hoặc IP LAN host |
| Target | “Internet” hoặc peer ngoài subnet | 8.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 --> WAN2.2 Từng bước L3 (request)
- Trong netns
c1: app gửi góisrc=10.10.0.10dst=8.8.8.8. Default route → gateway10.10.0.1devc1-eth0. - Veth peer: frame sang
veth-c1trên host; bridgebr0nhận (L2 trong LAN ảo). - Gateway = host: IP
10.10.0.1thuộc host → kernel host route gói ra iface WAN (eth0), không deliver local process → path forward. - filter FORWARD: policy/rule phải ACCEPT (hoặc jump chain cho phép). DROP ở đây = timeout dù
ip routetrong container đẹp. - nat POSTROUTING: MASQUERADE hoặc SNAT đổi
srcthành IP host trên iface egress. Server ngoài thấy host, không thấy10.10.0.10. - Conntrack: kernel ghi mapping (src/dst/port) để reply reverse-NAT về
10.10.0.10rồ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 |
|---|---|
0 | Host không forward giữa iface — bridge LAN có thể vẫn ping nội bộ; không ra WAN qua host |
1 | Kernel 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.
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.
| SNAT | MASQUERADE | |
|---|---|---|
| Ý | Ghim source ra một IP cố định | Lấ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 khi | IP 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 host | MASQUERADE 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:80 là bà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
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
Rule -i br0 -j ACCEPT là lab 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
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):
- 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.
- Lab tối thiểu (giống §4.3): ACCEPT từ
-i br0ra ngoài; ACCEPT-o br0choRELATED,ESTABLISHED(reply). Hoặc siết theo iface WAN cụ thể. Không cần-P FORWARD ACCEPTnế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?
Ba host. Với mỗi host, ghi MASQUERADE hay SNAT tĩnh (và vì sao):
- Laptop dev, Wi‑Fi DHCP, IP đổi mỗi quán cà phê.
- VM cloud gắn Elastic IP cố định
203.0.113.50trêneth0cả tháng. - 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.
| Host | Chọn | Vì sao |
|---|---|---|
| (1) Laptop DHCP | MASQUERADE | IP egress đổi — SNAT ghim IP cũ → gãy sau renew |
| (2) EIP cố định | SNAT (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 default | MASQUERADE | Engine 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 / runtime | Object bài này |
|---|---|
docker0 + IP gateway | br0 + 10.10.0.1/24 |
Container eth0 | c1-eth0 trong netns |
| Engine bật IP forward | sysctl net.ipv4.ip_forward=1 |
| Rule MASQUERADE subnet bridge | POSTROUTING -s <bridge-cidr> … -j MASQUERADE |
| “Không ra Internet” sau hardening | FORWARD 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
Spec / reference:
- RFC 3022 — Traditional NAT + RFC 2663 terminology — SNAT/NAPT khái niệm (Foundations NAT).
iptables(8)— tablenat, targetSNAT/MASQUERADE,FORWARD.- Netfilter NAT HOWTO — hook POSTROUTING / conntrack.
- Docker — packet filtering and firewalls — forward + tương tác firewall host.
- Docker with iptables — MASQUERADE /
DOCKER-USER(đừng sửa lung tung rule Engine).
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
- 03 — Mini-challenge bridge from scratch — LAN ảo + gateway; bài này thêm cửa ra Internet.
- 01 — Network namespaces — stack riêng; NAT rule nằm host netns, không “tự có” trong container.
- 05 — Publish port DNAT — chiều vào (đổi đích); bổ đôi với egress SNAT.
- 06 — Debug nsenter / tcpdump — bắt gói đúng netns khi NAT/forward lệch.
- M1 — iptables & netfilter — hook FORWARD/POSTROUTING + survey Docker.
- Foundations — NAT & port forwarding — SNAT/MASQUERADE/DNAT khái niệm; bài này implement trên bridge container.
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
Q1Liệ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ì?▸
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.Q2Trace 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?▸
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.Q3MASQUERADE và SNAT khác nhau chỗ nào? Khi nào bắt buộc nghiêng về MASQUERADE?▸
--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.Q4Hai 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?▸
Q5Vì 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?▸
Q6Fade: 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?▸
Q7Phâ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?▸
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
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