Skip to main content

5.2. Networking: Firewall, port redirection và NAT

Mục lục​


1. Packet filtering firewall trên Linux​

Một kẻ tấn công phát hiện ra rằng có thể dựng một packet dữ liệu mạng đặc biệt khai thác được lỗ hổng của SSH daemon đang chạy trên server. Nếu SSH daemon nhận được packet đó, kẻ tấn công vào được máy và gây thiệt hại. Dựng một packet filtering firewall chặn trước là cách ngăn kịch bản này: bộ lọc packet xem dữ liệu mạng vừa nhận và từ chối thẳng, nên packet độc hại không bao giờ tới được đích của nó là SSH daemon.

Đúng như cái tên, firewall dựng lên một bức tường và chặn những dữ liệu mạng mà server không nên xử lý. Bức tường đó chặn được cả hai chiều: dữ liệu đi vào, và cả dữ liệu server tự gửi ra.

Có nhiều loại firewall khác nhau. Một application firewall theo dõi từng ứng dụng rồi quyết định cho phép hay từ chối traffic dựa trên rule đặt riêng cho mỗi ứng dụng, kiểu như cơ chế trong hệ điều hành Windows: cho dữ liệu mạng chạy qua một app như Chrome thì hợp lý, còn chặn traffic cho Windows Calculator cũng hợp lý. Linux thì có sẵn ngay trong hộp một packet filtering firewall, nghĩa là nó quyết định dựa trên chính các network packet, không dựa trên ứng dụng.

Tài liệu này đi đúng phạm vi mà kỳ thi LFCS yêu cầu: dùng UFW cho firewall, và dùng iptables cho port redirection cùng NAT. Repo có sẵn một loạt bài đi sâu hơn nhiều về chính các công cụ này, đọc thêm khi cần vận hành thật:


2. Bật UFW và mở port SSH​

Cách dễ nhất để đặt rule packet filtering trên Ubuntu là dùng một tool tên UFW, viết tắt của Uncomplicated Firewall. Mặc định UFW đang tắt, kiểm tra bằng:

sudo ufw status

# Output:
Status: inactive

Trước khi làm bất cứ việc gì khác, phải thêm ít nhất một rule cho phép traffic SSH. Bỏ qua bước này rồi bật UFW lên là mất luôn đường đăng nhập SSH vào server. Lý do nằm ở mặc định của UFW: khi active, nó chặn toàn bộ dữ liệu đi vào. Cách làm này gọi là whitelist, nghĩa là mọi thứ đều bị cấm trừ những gì được cho phép tường minh trong danh sách trắng. Bức tường phòng thủ lúc mới bật là tuyệt đối, không gì đi qua được; thêm rule vào mới mở ra những gì cần dùng. Dân vận hành hay gọi việc này là đục lỗ trên bức tường.

SSH daemon chờ kết nối ở port 22, nên rule đầu tiên là:

sudo ufw allow 22

Lệnh này cho phép kết nối trên cả protocol TCP lẫn UDP. SSH dùng TCP, nên muốn chính xác hơn thì chỉ rõ đúng một protocol bằng cách thêm dấu gạch chéo và tên protocol:

sudo ufw allow 22/tcp

Giờ mới bật UFW:

sudo ufw enable

# Output:
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

UFW nhắc rằng lệnh này có thể làm đứt kết nối SSH đang có. Rule allow đã được thêm từ trước nên gõ y rồi Enter là xong.

Xem lại trạng thái với output chi tiết hơn:

sudo ufw status verbose

# Output (minh họa):
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To Action From
-- ------ ----
22 ALLOW IN Anywhere
22 (v6) ALLOW IN Anywhere (v6)

Hai rule ở cuối là một cặp: một cho phép kết nối đi vào port 22 trên IPv4, một cho IPv6 cùng port đó. Dòng Default phía trên cho biết ba chính sách mặc định:

  • Dữ liệu đi vào bị từ chối hết, trừ những gì rule bên dưới cho phép tường minh.
  • Dữ liệu đi ra được phép, để server gửi được dữ liệu tới bất cứ nơi nào nó cần.
  • Packet được route qua máy thì chưa được phép. Chính sách này sẽ phải đổi khi dùng tới port redirection và NAT ở mục 9. NAT là viết tắt của Network Address Translation.

3. Giới hạn theo IP nguồn và thứ tự xử lý rule​

Rule vừa thêm còn khá chung chung: bất cứ ai cũng nối được tới port 22 của máy này, vì lệnh chỉ nói port chứ không nói gì thêm. Trên một hệ thống thật thì phải thu hẹp quyền truy cập đó lại.

Trước tiên, xem các kết nối TCP đang hoạt động để biết mình đang vào từ IP nào:

ss -tn

# Output (minh họa):
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 10.0.0.100:22 10.0.0.192:51234

Kết nối tới port 22 đang đến từ source IP 10.0.0.192, tức IP của máy cá nhân đang dùng để kết nối vào. Từ đó dựng một rule chỉ cho phép traffic đi vào port 22 nếu đúng IP này gửi:

sudo ufw allow from 10.0.0.192 to any port 22

Từ khóa any ở đây là bắt buộc. Khi đã chỉ rõ IP của bên gửi qua from, UFW cũng muốn biết IP của bên nhận qua to. Gõ any nghĩa là chấp nhận traffic tới bất kỳ IP nào của máy này. Một server có nhiều card mạng và nhiều IP, nên muốn chỉ nhận traffic trên một IP cụ thể thì thay any bằng đúng địa chỉ đó.

Rule mới không tự vô hiệu hóa rule cũ​

Thêm rule hẹp vào rồi, nhưng bộ rule hiện tại lại có vấn đề. Hiện nó ra bằng cách xem rule theo đúng thứ tự xử lý:

sudo ufw status numbered

# Output (minh họa):
Status: active

To Action From
-- ------ ----
[ 1] 22 ALLOW IN Anywhere
[ 2] 22 (v6) ALLOW IN Anywhere (v6)
[ 3] 22 ALLOW IN 10.0.0.192

Rule chung chung cho phép traffic tới port 22 từ bất cứ đâu vẫn còn hiệu lực. Vì vậy việc thêm rule chỉ nhận từ 10.0.0.192 chẳng thay đổi gì: rule số 1 trong danh sách vẫn cho mọi IP khác nối vào port 22.

Cách sửa là xóa rule số 1 bằng chính số thứ tự của nó:

sudo ufw delete 1

Vẫn còn rule cho phép truy cập port 22 từ mọi địa chỉ IPv6. Xóa rule còn một cách thứ hai: thay vì số thứ tự, chỉ ra chính nội dung rule. Rule đó được thêm bằng sudo ufw allow 22, nên bản thân rule là allow 22, và lệnh xóa là:

sudo ufw delete allow 22

# Output (minh họa):
Could not delete non-existent rule
Rule deleted (v6)

UFW phàn nàn rằng không tìm được một trong hai rule, nhưng đó chỉ là rule IPv4 vừa bị xóa ở bước trước. Rule IPv6 thì nó tìm thấy và xóa được. Kiểm tra lại thì chỉ còn đúng một rule cho phép traffic đi vào port 22 từ IP cụ thể, không địa chỉ nào khác nối được nữa.

Dải IP và rule deny​

Thay cho một IP cố định, chỉ ra cả một dải cũng được. Muốn cho phép mọi IP từ 10.0.0.0 tới 10.0.0.255 thì dùng CIDR notation /24 cho dải đó:

sudo ufw allow from 10.0.0.0/24 to any port 22
sudo ufw allow from 10.0.0.0/24

Lệnh thứ hai cho phép toàn bộ traffic từ dải đó, không giới hạn port. Đi xa hơn một bước: nhận traffic từ mọi IP trong dải, trừ đúng một địa chỉ. Chẳng hạn cần cấm traffic đến từ 10.0.0.37. Thay vì rule allow, thêm một rule deny:

sudo ufw deny from 10.0.0.37

Và ở đây lại sinh ra vấn đề. Nhìn lại danh sách rule hiện tại, giả sử đã xóa hai rule đầu để cho gọn:

sudo ufw delete 1
sudo ufw delete 1
sudo ufw status numbered

# Output (minh họa):
Status: active

To Action From
-- ------ ----
[ 1] Anywhere ALLOW IN 10.0.0.0/24
[ 2] Anywhere DENY IN 10.0.0.37

Chú ý hai lệnh delete 1 liên tiếp: sau khi rule số 1 bị xóa, rule số 2 trở thành rule số 1, rule số 3 trở thành rule số 2, cả danh sách dịch lên một bậc. Nên muốn xóa hai rule đầu thì gọi delete 1 hai lần, không phải delete 1 rồi delete 2.

Với danh sách như trên, traffic đi vào từ 10.0.0.37 lại được cho phép. Nguyên nhân là firewall xử lý rule từ trên xuống dưới, từ rule 1 tới rule cuối cùng. Packet đến từ 10.0.0.37 khiến chuỗi sau xảy ra: firewall xem rule đầu tiên, thấy 10.0.0.37 nằm trong dải 10.0.0.0/24, nên rule khớp và traffic được cho qua. Hết. Nó không đi tới rule deny số 2, vì rule đầu đã cho phép traffic đi qua rồi.

UFW reads rules top to bottom, first match winsPacket from 10.0.0.37ufw status numbered1. DENY from 10.0.0.372. ALLOW from 10.0.0.0/243. ALLOW 22 from anywhereFirst match wins, the rest is skippedDENIEDPut the deny rule first with ufw insert 1, every rule below shifts down

Cách sửa là đổi thứ tự, đặt rule deny lên đầu. Xóa rule deny đi trước, rồi thêm lại ở đúng vị trí số 1. Lệnh ufw allow và ufw deny quen dùng luôn thêm rule vào cuối danh sách, nên cần một lệnh khác. Tra nhanh danh sách lệnh:

sudo ufw help

Trong đó có lệnh insert, cho phép chỉ định số thứ tự cho rule. Thêm rule deny vào vị trí số 1:

sudo ufw insert 1 deny from 10.0.0.37

Khi gán số cho một rule như vậy, mọi rule bên dưới nó bị đẩy xuống một bậc: chèn vào vị trí 3 thì rule số 3 cũ thành số 4, số 4 thành số 5, và tiếp tục như vậy. Kiểm tra lại danh sách sẽ thấy rule deny nằm đầu, và lần này mọi thứ chạy đúng ý: firewall thấy traffic đi vào từ 10.0.0.37, thấy nó khớp rule deny, chặn lại, và không xử lý những rule còn lại.

Rule số 2 về lý thuyết vẫn cho phép traffic từ 10.0.0.37 vì địa chỉ đó nằm trong dải, nhưng theo thiết kế thì rule firewall được xử lý tuần tự: cái khớp đầu tiên là cái được thực thi, bất kể nó là allow hay deny. Đây là thứ phải cân khi thiết kế logic firewall.


4. Rule theo network interface và theo chiều traffic​

Một server có thể có nhiều card mạng: một card xử lý traffic bên ngoài đi tới hoặc đi từ Internet, card khác xử lý traffic với mạng nội bộ trong office. Muốn áp rule cho đúng một card thì phải biết tên interface đó:

ip -c link

Giả sử nó là enp0s3. Thử một lượt trước khi chặn, ping một trong các DNS server của Google và dùng -c để chỉ ping bốn lần:

ping -c 4 8.8.8.8

# Output (rút gọn):
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=12.4 ms
--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss

Chạy được như mong đợi. Giờ chặn traffic đi ra thay vì đi vào:

sudo ufw deny out on enp0s3 to 8.8.8.8

Từ khóa out chỉ rõ rule này áp cho packet đi ra, và on kèm tên interface giới hạn rule vào đúng một card mạng. Thử lại ping -c 4 8.8.8.8 thì không còn phản hồi, đúng như mong đợi.

Hai cột to và from đổi nghĩa theo chiều traffic​

Xem lại bộ rule sẽ thấy một điểm khác biệt giữa rule đi ra và rule đi vào, nằm ở hai trường to và from.

Khi lọc dữ liệu đi vào, to chỉ một đích trên chính máy này, tức traffic đang đi tới IP nào của máy, và from chỉ bên gửi, tức IP của máy bên ngoài đã gửi dữ liệu tới.

Khi lọc dữ liệu đi ra, hai vai này đảo lại. Cột to không còn chỉ IP của máy này mà chỉ IP của một máy khác, còn from lại chỉ IP của chính máy này.

💡 Hình dung: giống như gửi và nhận email. Khi nhận email, ô "to" là địa chỉ email của chính mình, do người khác điền vào. Khi gửi email, ô "to" lại là địa chỉ của người khác. Ở đây y như vậy: với vai người nhận, to chỉ máy này; với vai người gửi, to chỉ một máy khác.


5. Cú pháp UFW đầy đủ​

Vài lệnh UFW ở trên ngắn tới mức chỉ cần nêu một số port, phần còn lại UFW tự điền. Nó tự hiểu rằng rule áp cho cả protocol TCP và UDP, từ mọi địa chỉ tới mọi địa chỉ, trên mọi network interface. Nguyên tắc chung: bỏ qua chi tiết nào trong lệnh UFW thì nó giả định là muốn tất cả ở chỗ đó. Không nêu port thì rule áp cho mọi port; không nêu IP thì rule áp cho mọi IP.

Càng nêu nhiều chi tiết thì rule càng được tinh chỉnh chặt. Dựng lệnh UFW đầy đủ nhất gồm mọi thành phần đã học. Trước tiên lấy IP của máy này, vì nó sẽ xuất hiện trong lệnh:

ip -c a

# Output (rút gọn):
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 10.0.0.100/24 brd 10.0.0.255 scope global enp0s3

Địa chỉ của máy là 10.0.0.100. Lệnh cho traffic đi vào:

sudo ufw allow in on enp0s3 from 10.0.0.192 to 10.0.0.100 port 80 proto tcp

Đọc từng đoạn: in on enp0s3 nói rule áp cho dữ liệu đi vào trên enp0s3; from 10.0.0.192 chỉ IP bên gửi; to 10.0.0.100 cho phép traffic trên đúng IP này của máy; cuối cùng là port và protocol.

Riêng phần port có một bất ngờ về cú pháp: không viết được 80/tcp như lệnh ngắn ở các mục trước. Khi đã tinh chỉnh IP ở from và to, phải dùng cách khác, là nêu port 80 rồi theo sau bằng proto tcp.

Với traffic đi ra, chỉ cần đổi từ khóa in thành out. Nhưng vì lúc này máy đóng vai bên gửi chứ không phải bên nhận, hai phần from và to phải đảo chỗ cho nhau:

sudo ufw allow out on enp0s3 from 10.0.0.100 to 10.0.0.192 port 80 proto tcp

Kiểm tra lại bằng sudo ufw status numbered sẽ thấy cả hai rule đã được thêm. Nhớ được cú pháp dài này thì dựng được gần như mọi rule cần tới. Quên thì manual của UFW có sẵn rất nhiều lệnh ví dụ, dùng như một bản nhắc nhanh hoặc một bản hướng dẫn cho đủ loại mục đích:

man ufw

6. Port redirection và NAT hoạt động thế nào?​

Có một nhóm server nằm trên mạng nội bộ riêng, và mạng đó không truy cập được trực tiếp từ Internet. Thêm vào giữa một server có địa chỉ công khai, nối với cả hai phía: mạng nội bộ ở một bên và Internet ở bên kia. Các server riêng tư giờ về lý thuyết là tiếp cận được từ Internet, miễn là kết nối đi vào phải đi qua server công khai ở giữa.

Nhưng chưa làm thêm bước cấu hình nào thì ngay lập tức có vấn đề. Một thiết bị nào đó từ Internet liên hệ server công khai, xin kết nối ở port 80 để xem một trang web. Server công khai lúc này bối rối, vì nó không biết phải chuyển tiếp kết nối tới port 80 này đi đâu. Server riêng tư nào trên mạng nội bộ phải xử lý kết nối port 80, server một, hai, hay ba?

Port redirection, còn gọi là port forwarding, giải quyết đúng chuyện này. Tạo rule nói cho server công khai biết phải chuyển hướng các kết nối đi vào port 80, 993 và 3306 tới đâu: mọi thứ vào port 80 chuyển tới server một, kết nối port 993 chuyển tới server hai, kết nối port 3306 chuyển tới server ba.

Port redirection through a public serverInternet client203.0.113.1Public server 1.2.3.4enp1s0 receivesPREROUTING chainDNAT 8080 to 80enp6s0 sends outPrivate networkServer 1, port 80Server 2, port 993Server 3, port 3306PREROUTING rewrites the destination before routing happensPOSTROUTING masquerades the source so replies can come back

NAT sửa gì trong header của packet​

Dữ liệu gửi qua mạng được đóng gói vào các network packet. Xem một video trên YouTube thì dữ liệu được gửi về thành rất nhiều packet nhỏ, mỗi packet chứa một đoạn của video đó. Ngoài dữ liệu, mỗi packet còn mang theo source IP address và destination IP address, cho biết ai gửi và ai nên nhận.

Một packet IPv4 chứa khá nhiều trường, phần lớn là thứ mà máy tính và chương trình cần để hiểu được chúng gửi và nhận gì. Hai trường quan trọng với chủ đề này là source address và destination address. Dữ liệu trên Internet được chuyển từ server sang server, router sang router, switch sang switch, cho tới khi tới đích. Nhờ hai địa chỉ nằm sẵn trong packet mà mọi thiết bị trong chuỗi đó biết phải chuyển tiếp dữ liệu đi đâu, và cũng biết phải gửi phản hồi về đâu.

Quay lại ví dụ ba server riêng tư và một server nối cả Internet lẫn mạng nội bộ. Một thiết bị trên Internet có IP 203.0.113.1 gửi dữ liệu tới server công khai, nên 203.0.113.1 được ghi làm source address trong packet gửi ra, còn destination address là IP của server công khai, giả sử 1.2.3.4.

Server công khai nhận packet ở port 80, biết rằng phải chuyển tiếp nó cho server riêng tư số một, nên nó đổi destination address: 1.2.3.4 được thay bằng 10.0.0.5. Server một thấy packet khớp đúng IP của mình ở trường destination nên nhận packet.

Tới đây server công khai mới chỉ đổi destination address, nhưng nó còn phải đi thêm một bước. Thiết bị từ Internet có IP 203.0.113.1, và địa chỉ đó vẫn đang nằm ở trường source address. Server một nhận packet thành công, nhưng khi nó tìm cách gửi dữ liệu trả lại thì thất bại: nó gửi về đúng source address 203.0.113.1, trong khi nó không nối Internet nên không gửi được tới IP đó.

Cách giải quyết là server công khai đổi luôn source address. Thay vì giữ 203.0.113.1, nó điền IP của chính mình, 1.2.3.4. Nhờ vậy khi server một phản hồi lại packet được chuyển tiếp, nó thấy phải gửi về server công khai tại 1.2.3.4, và vì nó tiếp cận được server đó nên lần này thao tác thành công. Thêm chút phép thuật NAT nữa thì server công khai còn trả được phản hồi này về cho người gửi ban đầu.

What NAT rewrites in the packet header1. Packet arrives at the public serversrc 203.0.113.1 and dst 1.2.3.4 on port 80802. DNAT in PREROUTING changes the destinationsrc 203.0.113.1 and dst 192.168.0.5 on port 803. MASQUERADE in POSTROUTING changes the sourcesrc becomes the public server IP so the reply can get backThe server keeps the mapping, so the reply reaches the original sender

Khi một máy sửa source IP kiểu đó, nó đang giả vờ là một ai khác. Vì vậy kỹ thuật này còn có tên masquerading. Ở đây, thay 203.0.113.1 bằng IP của chính nó là 1.2.3.4, nó đang giả vờ làm thiết bị từ Internet gửi tin tới server riêng tư. Về phía server riêng tư, nó không có cách nào biết ai là người gửi gốc. Nhưng nhờ NAT làm ở cả hai chiều, server công khai nhớ ai đã gửi gì và packet cần đi đâu.

Router ở nhà cũng làm đúng việc này. Dữ liệu đi vào router, và nó biết cái gì phải gửi về smartphone, cái gì phải gửi về laptop, cũng bằng network address translation và masquerading.


7. Bật IP forwarding​

Việc đầu tiên cần làm trước khi đặt rule redirect là bật IP forwarding trên máy, vì port redirection cần máy có khả năng chuyển tiếp network packet, mà khả năng này thường bị tắt mặc định.

Trên Ubuntu bật nó bằng cách sửa một trong hai file: /etc/sysctl.conf hoặc /etc/sysctl.d/99-sysctl.conf. Nếu được thì chọn file thứ hai. Sửa /etc/sysctl.conf nhiều rủi ro hơn, vì bản update của system package sau này có thể sửa file đó và thay đổi vừa đặt bị mất trắng.

sudo vim /etc/sysctl.d/99-sysctl.conf

Trong file có hai dòng cần quan tâm. Uncomment dòng đầu để bật forwarding cho IPv4, uncomment dòng thứ hai để bật cho IPv6:

net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

Lưu file xong phải reload lại toàn bộ file cấu hình sysctl:

sudo sysctl --system

Tham số --system nói với lệnh sysctl rằng hãy tìm giá trị cấu hình trong tất cả file và thư mục mặc định của hệ thống. Kiểm tra thay đổi đã có hiệu lực hay chưa, và cả hai giá trị phải bằng 1:

sysctl net.ipv4.ip_forward net.ipv6.conf.all.forwarding

# Output:
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1

Cơ chế kernel parameter của sysctl nói chi tiết hơn ở Part 3.3: Lập lịch task, kernel parameter và quản lý package.


8. DNAT và masquerade bằng iptables​

Mọi dữ liệu mạng đều được Linux kernel xử lý trước tiên. Nghĩa là mọi firewall rule, mọi redirect, mọi network address translation cũng do kernel xử lý, hay chính xác hơn là do một phần của nó tên Netfilter framework. Có nhiều lệnh khác nhau để nói với framework này cách xử lý packet.

Lệnh hiện đại để tương tác với Netfilter là nft. Khổ nỗi những lệnh nft cần dùng cho việc này khó nhớ tới mức rất ít ai gõ đúng mà không tra Google. Nên ví dụ dưới đây dùng một lệnh khác là iptables, tiền thân của nft nhưng vẫn chạy được trên các bản phân phối hiện đại. Thực tế phần lớn bản phân phối được thiết lập sao cho rule iptables tự động được dịch sang rule nft, nên dùng lệnh cũ thì mọi thứ vẫn hoạt động đúng như mong đợi.

Xác định interface vào và interface ra​

Kịch bản cụ thể: chuyển hướng mọi request đi vào từ dải CIDR 10.0.0.0/24. Tất cả dữ liệu từ mạng đó tới port 8080 trên máy này phải được chuyển hướng tới port 80 trên một server ở xa có IP 192.168.0.5.

Trước khi dựng rule, cần biết network interface nào chịu trách nhiệm cho việc chuyển hướng. Giả sử có hai card mạng dính vào: một card xử lý traffic đi vào từ mạng 10.0.0.0/24, card kia xử lý traffic đi ra tới 192.168.0.5, vốn nằm trên một mạng khác.

ip a

# Output (minh họa):
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 10.0.0.1/24 brd 10.0.0.255 scope global enp1s0
3: enp6s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 192.168.0.1/24 brd 192.168.0.255 scope global enp6s0

Vậy enp6s0 xử lý traffic đi ra sẽ được chuyển hướng tới 192.168.0.5, còn enp1s0 xử lý traffic đi vào từ 10.0.0.0.

Với một đích không nằm trong dải nào trong số đó, chẳng hạn chuyển hướng tới 1.2.3.4, thì tra default route:

ip route

# Output (rút gọn):
default via 10.0.0.1 dev enp1s0 proto static

Mọi packet đi tới mạng bên ngoài được gửi qua default gateway này, và thiết bị được dùng là enp1s0. Vậy enp1s0 sẽ là interface gửi dữ liệu tới 1.2.3.4 khi cấu hình rule redirect cho đích đó.

Table và chain trong iptables​

iptables dùng hai cấu trúc gọi là table và chain. Một packet đi vào hay đi ra sẽ đi qua một số chain nhất định, và được xử lý theo các rule đã thêm vào chain đó. Chẳng hạn ngay trước khi một packet đi ra qua card mạng, nó đi qua chain POSTROUTING.

Cách hiểu gọn: chain nói về thời điểm packet được xử lý, còn table là cách nhóm các rule theo mục đích. Một table tham chiếu được nhiều chain, nhưng các chain đó chỉ được kích hoạt tại đúng một thời điểm nhất định trong hành trình của packet.

Where PREROUTING and POSTROUTING sitPacket inon enp1s0PREROUTINGnat table, DNATRoutingdecisionPOSTROUTINGnat, MASQUERADEOutenp6s0In prerouting name the interface that receives the packetIn postrouting name the interface that sends the packet out

Rule port redirect​

sudo iptables -t nat -A PREROUTING -i enp1s0 -s 10.0.0.0/24 -p tcp --dport 8080 -j DNAT --to-destination 192.168.0.5:80

Giải nghĩa từng tùy chọn:

  • -t là table. Với -t nat, rule được thêm vào table NAT. Khi một port được chuyển hướng, network address translation được dùng để đổi IP và port đích ban đầu sang IP và port khác, nên hễ cần NAT là phải thêm rule vào table nat.
  • -A là tùy chọn append. Với -A PREROUTING, rule được nối thêm vào chain PREROUTING, chain lo việc chỉnh sửa packet ngay khi chúng vừa được nhận. Vì IP sẽ bị sửa, việc sửa đó phải xảy ra trước giai đoạn routing; nếu không, packet bị route tới chỗ sai dựa trên IP cũ thay vì IP mới.
  • -i là input. Với -i enp1s0, interface đầu vào được chọn là enp1s0, nên rule chỉ áp khi packet được nhận trên đúng interface đó.
  • -s chọn source IP, nên rule chỉ áp nếu source IP khớp bất cứ địa chỉ nào trong mạng 10.0.0.0.
  • -p chọn protocol, và nó bắt buộc với một rule port redirect. Không dùng -p thì nhận lỗi --dport is an unknown option: dùng --dport thì phải có -p. Muốn rule áp cho cả hai protocol thì copy nguyên lệnh thành hai bản, một bản dùng -p tcp, bản kia dùng -p udp, mọi tùy chọn còn lại giữ nguyên.
  • --dport đặt destination port là 8080, nghĩa là việc redirect chỉ xảy ra khi máy nhận packet ở port 8080 của mình.
  • -j là chỗ phép thuật diễn ra. Nó nói với rule rằng hãy nhảy packet này tới một target, theo cách gọi của iptables. Xem nó như việc gửi packet tới một chương trình con hay một module con. Ở đây packet được gửi tới một extension tên DNAT, viết tắt của destination NAT, chính là extension sửa packet để nó được chuyển hướng tới IP và port mới.
  • --to-destination là tùy chọn truyền cho extension DNAT, nên nó phải đứng sau -j DNAT. Ở đây nó yêu cầu DNAT chuyển hướng packet tới IP 192.168.0.5 và port 80 của máy đó.

Tới đây, mọi packet từ bên ngoài đi vào port 8080 của máy này sẽ được chuyển tiếp tới server ở xa trên port 80. Hai điểm cần biết khi muốn tự thử:

  • Redirect chỉ hoạt động khi kết nối từ một máy khác. Kết nối từ chính máy đó thì không chạy, vì chain PREROUTING chỉ áp cho packet nhận từ thế giới bên ngoài.
  • Kết nối từ máy khác thì thấy kết nối được khởi tạo nhưng rồi treo. Cũng hợp lý: packet đã được chuyển tiếp nhưng không có đường về.

Vì sao không có đường về? Một IP như 10.0.0.9 gửi gì đó tới server này ở port 8080, packet tới được 192.168.0.5 port 80. Nhưng 192.168.0.5 lại thấy rằng 10.0.0.9 đã gửi cái này. Lúc nó tìm cách gửi dữ liệu trả lại, nó không tới được 10.0.0.9, trong khi nó biết cách liên hệ server trung gian ở 192.168.0.1 vì hai bên chung một mạng.

Rule masquerade​

sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o enp6s0 -j MASQUERADE

Vẫn -t nat để làm việc với table NAT, nhưng lần này -A không nối vào chain PREROUTING mà nối vào chain POSTROUTING, chain lo việc chỉnh sửa packet ngay trước khi chúng rời khỏi mạng. -s làm đúng việc như trước, chỉ áp rule nếu source IP gửi packet nằm trong mạng 10.0.0.0/24.

Chỗ khác biệt đáng chú ý: lần này là -o, chọn interface đầu ra, không phải interface đầu vào như ở rule redirect. Cụ thể là enp6s0, interface dùng để gửi packet đi.

💡 Hình dung: cách nhớ khác biệt này là gắn mỗi chain với một cánh cửa. Ở PREROUTING, cửa packet vừa bước vào, nên ghi interface nhận packet. Ở POSTROUTING, cửa packet sắp bước ra, nên ghi interface gửi packet đi.

Cuối cùng là -j để nhảy packet tới extension MASQUERADE. Không cần nêu IP của server: extension tự làm phép NAT và thay source IP bằng IP của chính máy một cách động. Nó tự xem phải dùng IP nào để gửi packet này ra rồi thay source IP ban đầu bằng địa chỉ đó.

Lệnh iptables kiểu này đã khá phức tạp, nhưng so với nft thì vẫn là lựa chọn dễ hơn. Liệt kê rule nft hiện có để thấy rõ:

sudo nft list ruleset

Hai điểm rút ra từ output đó: dựng cấu trúc nftables từ con số không mà không tra Internet thì khó hơn nhiều, và các tool của Ubuntu đã tự dịch rule iptables vừa thêm thành rule nft.


9. Lưu rule vĩnh viễn và siết chặt forwarding​

Các rule vừa thêm chỉ là tạm thời, mất sau khi reboot. Muốn chúng vĩnh viễn thì cài một package:

sudo apt install iptables-persistent

Lúc cài, trình cài đặt hỏi có muốn lưu rule hiện tại hay không, chọn mặc định là yes. Sau khi package này có mặt, mỗi lần đổi rule iptables hay nft thì lưu lại bằng:

sudo netfilter-persistent save

Một điểm hữu dụng nữa: các tùy chọn -i, -o và -s đều là tùy chọn, bỏ được. Nghĩa là không nhất thiết phải chỉ ra network interface đầu vào hay đầu ra, cũng không nhất thiết phải nêu source IP hay dải IP. Với mục đích thử nghiệm thì bỏ chúng cũng được. Nhưng trên hệ thống thật thì nên dùng để giới hạn ai forward được cái gì; không có chúng, máy này trở thành món đồ cho bất cứ ai trên Internet lợi dụng, vì nó forward dữ liệu mạng vô điều kiện.

Bỏ -i và -o nghĩa là rule redirect áp bất kể interface nào nhận hay gửi packet. Không lý tưởng cho một hệ thống thật, nhưng đủ dùng cho mục đích thử nghiệm.

Đi xa hơn nữa thì thêm firewall rule để giới hạn việc forward mạng và chặn lạm dụng từ các thiết bị không được phép. Khi được bật, UFW mặc định không cho phép bất kỳ kiểu forwarding nào, nên cần thêm một rule dạng route:

sudo ufw allow 22
sudo ufw enable
sudo ufw route allow from 10.0.0.0/24

Ba lệnh theo thứ tự: cho phép traffic SSH tới port 22, bật firewall, rồi thêm rule route cho phép packet được forward từ mạng 10.0.0.0/24.

Tra cứu và dọn dẹp rule iptables​

Quên cú pháp iptables thì có một chỗ nhắc nhanh ngay trên máy: manual của uncomplicated firewall framework của Ubuntu.

man ufw-framework

Cuộn tới phần full example sẽ thấy hai dòng phản chiếu đúng lệnh redirect ở mục trước. Trong đó dấu backslash escape ký tự newline, hay nói gọn là cả đoạn vốn là một danh sách tùy chọn duy nhất nhưng bị tách thành hai dòng bởi dấu backslash đó. Bỏ dấu backslash đi thì gộp hết tùy chọn về một dòng được.

Từ ví dụ trong manual, dựng lại lệnh thật theo bốn bước: thêm sudo iptables -t nat vào đầu dòng đó, chuyển dòng --to-destination lên cùng một dòng, thêm tùy chọn -s vào trước extension DNAT để chọn source IP cho rule, rồi sửa interface ở -i, sửa --dport và sửa IP cùng port ở --to-destination thành giá trị cần dùng. Làm tương tự với dòng ví dụ thứ hai: thêm sudo iptables -t nat vào đầu rồi sửa dải source IP và output interface.

Liệt kê rule hiện có trong table nat của iptables:

sudo iptables -t nat -L

Output phải có mặt các rule PREROUTING và POSTROUTING vừa thêm. Làm sai ở đâu thì xóa trắng table này đi rồi làm lại từ đầu:

sudo iptables -t nat -F

Option -F là viết tắt của --flush, và khi không nêu tên chain nào phía sau thì nó xóa rule của mọi chain trong table được chọn bởi -t.

Bài giảng gốc đọc lệnh này thành sudo iptables flush table nat. Đó không phải cú pháp của iptables, gõ vào sẽ nhận lỗi. Dạng flush table <tên> thuộc về nft, công cụ thế hệ sau. Lệnh ở trên là dạng iptables đúng.

Cấu hình mạng và firewall thường là thứ người mới gặp khó nhất, và cũng dễ hiểu, vì đây là một chủ đề phức tạp và rộng. Phạm vi ở đây dừng đúng ở mức kỳ thi LFCS yêu cầu. Muốn đi sâu vào kiến trúc Netfilter bên dưới, cách đọc một ruleset thừa hưởng từ người khác, hay cách chặn brute force tự động, thì đọc tiếp loạt bài đã dẫn ở mục 1.

Mục tiếp theo của domain Networking là đặt một lớp trung gian khác giữa người dùng và web server, ở Part 5.3: Reverse proxy và load balancer.


Nguồn tham khảo​

Nguồn gốc: Khóa "Prep Course - Linux Foundation Certified System Administrator (LFCS) Certification" — phần "5. Networking" (cấu hình packet filtering firewall bằng UFW, port redirection và network address translation với iptables), nền tảng KodeKloud. Giảng viên: Jeremy Morgan và Alexandru Andrei.

Fact-check:

  • Lệnh xóa trắng table nat của iptables là sudo iptables -t nat -F; -F/--flush xóa rule của mọi chain trong table khi không nêu tên chain. Bài giảng gốc đọc thành iptables flush table nat, không phải cú pháp hợp lệ của iptables. Nguồn: man 8 iptables, 2026-10-24.