Policy-Based Routing trên Linux: Cấu hình nhiều Routing Table với ip rule và rt_tables

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Vấn đề thực tế: Bài toán đau đầu trên máy chủ Linux Multi-Homed

Khi cắm thêm card mạng (network interface) thứ hai vào máy chủ Linux, hệ thống định tuyến thường phát sinh lỗi ngay lập tức. Giả sử ta có một database node cấu hình dual-homed chạy Ubuntu 22.04: eth0 kết nối vào VLAN quản trị nội bộ dải 192.168.1.0/24, trong khi eth1 đảm nhận lưu lượng phục vụ người dùng qua đường uplink 1 Gbps riêng biệt dải 10.0.0.0/24.

Đèn link của cả hai cổng mạng đều sáng xanh. Cả hai interface đều nhận đúng IP. Các lệnh curl đi ra ngoài mạng vẫn chạy bình thường. Thế nhưng, ngay khoảnh khắc lưu lượng từ bên ngoài chạm vào eth1, kết nối của client sẽ bị treo vô thời hạn ở giai đoạn bắt tay (handshake) ban đầu. Lệnh ping từ các subnet công cộng bị rớt gói 100%, trả về lỗi Request timeout im lặng. Nếu bạn đổi default gateway của hệ thống sang eth1, vấn đề chỉ đơn giản là đảo ngược lại: eth1 phản hồi ngay lập tức, nhưng bạn sẽ mất hoàn toàn kết nối SSH qua eth0.

Đây chính là hiện tượng định tuyến bất đối xứng (asymmetric routing) kinh điển trong thực tế.

Phân tích nguyên nhân gốc rễ: Vì sao định tuyến tiêu chuẩn lại thất bại?

Theo mặc định, Linux sử dụng một routing table duy nhất dựa trên đích đến có tên là main. Khi kernel chuẩn bị gửi một gói tin đi, nó chỉ đánh giá duy nhất một thuộc tính: địa chỉ IP đích (destination IP). Nó hoàn toàn bỏ qua việc frame ban đầu được nhận vào từ interface nào.

Hãy theo dõi luồng TCP handshake đến để hiểu vì sao nó bị lỗi trong môi trường production:

  1. Client từ IP 203.0.113.50 gửi gói TCP SYN tới IP public trên eth1 của bạn (10.0.0.50:443).
  2. Ứng dụng local nhận được SYN và gửi phản hồi SYN-ACK với IP nguồn là 10.0.0.50 và IP đích là 203.0.113.50.
  3. Kernel tra cứu bảng main để tìm đường tới 203.0.113.50. Do không có route cụ thể cho host này, kernel chọn default gateway (0.0.0.0/0) đang gắn với eth0. Gói SYN-ACK bị đẩy ra ngoài qua eth0 thay vì eth1.
  4. Firewall phía upstream lập tức drop frame này. Bởi vì gói tin mang IP nguồn thuộc subnet của eth1 nhưng lại đi ra theo đường upstream của eth0, nó vi phạm cơ chế Reverse Path Filtering (RPF) và stateful connection tracking.

Việc chỉ định tuyến theo IP đích khiến kernel không nắm được ngữ cảnh lưu lượng đi vào. Gói tin gửi đi cần một cơ chế để “ghi nhớ” nguồn gốc xuất phát của chúng.

Đánh giá các giải pháp tình thế: Vì sao sửa nhanh không hiệu quả?

Các kỹ sư thường thử hai giải pháp tình thế trước khi triển khai policy routing chuẩn chỉnh:

1. Source NAT (SNAT) hoặc Masquerading

Viết lại gói tin đi vào bằng iptables -t nat -A PREROUTING để ép phản hồi quay ngược lại local interface.

  • Ưu điểm: Hoạt động ngay mà không cần thay đổi cấu hình router phía upstream.
  • Nhược điểm: Làm ẩn IP thực của client trong access log của Nginx và Envoy. Ngoài ra còn làm tăng overhead cho bảng conntrack khi chịu tải cao (chẳng hạn trên 50.000 luồng đồng thời).

2. Vô hiệu hóa Reverse Path Filtering

Thiết lập net.ipv4.conf.all.rp_filter=0 để ngăn kernel của chính bạn drop các gói tin phản hồi bất đối xứng.

  • Ưu điểm: Chỉ cần một dòng lệnh sysctl đơn giản.
  • Nhược điểm: Vẫn để lại lỗi định tuyến nguyên vẹn. Gói phản hồi vẫn đi ra sai cổng mạng vật lý, đồng nghĩa với việc ISP hoặc cloud edge gateway của bạn cũng sẽ drop chúng.

3. Policy-Based Routing (PBR)

Sử dụng Cơ sở dữ liệu Chính sách Định tuyến (RPDB) qua ip rule để khớp các route gửi ra dựa trên IP nguồn hoặc fwmark.

  • Ưu điểm: Giữ nguyên địa chỉ IP gốc của client, tránh làm đầy bảng connection-tracking, đảm bảo đường truyền đi vào và đi ra luôn đối xứng qua cùng một card mạng vật lý.
  • Nhược điểm: Đòi hỏi phải định nghĩa bảng định tuyến lưu trữ lâu dài và cấu hình subnet route vượt ra ngoài cấu hình DHCP mặc định.

Hướng dẫn từng bước cấu hình PBR

Policy routing xử lý dứt điểm vấn đề ngay ở tầng kernel. Chúng ta sẽ tạo hai routing table độc lập và liên kết từng bảng với IP nguồn tương ứng.

Mô hình mạng thử nghiệm

  • Uplink chính (eth0): IP 192.168.1.50/24, Gateway 192.168.1.1
  • Uplink phụ (eth1): IP 10.0.0.50/24, Gateway 10.0.0.1

Bước 1: Đăng ký bảng định tuyến trong rt_tables

Linux cho phép tạo các routing table tùy chỉnh đánh số từ 1 đến 252. Ánh xạ các định danh số này thành tên bảng dễ nhớ trong /etc/iproute2/rt_tables. Sử dụng tee -a thay vì chuyển hướng luồng (redirect) thông thường để đảm bảo quyền sudo hoạt động chính xác:

echo "100 isp_eth0" | sudo tee -a /etc/iproute2/rt_tables
echo "200 isp_eth1" | sudo tee -a /etc/iproute2/rt_tables

Bước 2: Gán Route cho từng bảng định tuyến

Mỗi bảng cần hai đường dẫn: subnet kết nối trực tiếp và default gateway upstream của nó.

# Cấu hình routing table isp_eth0 (Bảng 100)
sudo ip route add 192.168.1.0/24 dev eth0 src 192.168.1.50 table isp_eth0
sudo ip route add default via 192.168.1.1 dev eth0 table isp_eth0

# Cấu hình routing table isp_eth1 (Bảng 200)
sudo ip route add 10.0.0.0/24 dev eth1 src 10.0.0.50 table isp_eth1
sudo ip route add default via 10.0.0.1 dev eth1 table isp_eth1

Bước 3: Định nghĩa Policy Rule trong RPDB

Yêu cầu kernel tra cứu bảng isp_eth1 mỗi khi gói tin gửi đi có địa chỉ nguồn là 10.0.0.50. Hãy gán priority cụ thể để thứ tự đánh giá luôn xác định:

sudo ip rule add from 192.168.1.50/32 table isp_eth0 priority 1000
sudo ip rule add from 10.0.0.50/32 table isp_eth1 priority 1010

Xóa cache định tuyến FIB để các thay đổi có hiệu lực ngay mà không cần khởi động lại dịch vụ mạng:

sudo ip route flush cache

Bước 4: Xác minh luồng chuyển tiếp gói tin

Chạy ip rule show để kiểm tra độ ưu tiên của các rule đang hoạt động:

ip rule show

Kết quả hiển thị các tiêu chí tra cứu được sắp xếp theo chỉ số priority:

0:      from all lookup local
1000:   from 192.168.1.50 lookup isp_eth0
1010:   from 10.0.0.50 lookup isp_eth1
32766:  from all lookup main
32767:  from all lookup default

Kiểm tra cách kernel phân giải các địa chỉ đích bên ngoài bằng lệnh ip route get:

ip route get 8.8.8.8 from 10.0.0.50

Bạn sẽ thấy gói tin được định tuyến qua bảng isp_eth1 và đi ra cổng eth1:

8.8.8.8 from 10.0.0.50 via 10.0.0.1 dev eth1 table isp_eth1 uid 0
    cache

Chạy thử lệnh ping -I eth1 8.8.8.8 và theo dõi bằng tcpdump -nn -i eth1 icmp ở một terminal khác. Giờ đây, các gói echo request và echo reply sẽ đi qua đúng cùng một interface vật lý.

Bước 5: Lưu cấu hình vĩnh viễn với Netplan

Các lệnh cấu hình qua ip rule sẽ mất đi khi khởi động lại máy. Đối với hệ điều hành Ubuntu và Debian sử dụng systemd-networkd, hãy lưu cấu hình vào /etc/netplan/01-netcfg.yaml:

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      addresses:
        - 192.168.1.50/24
      routes:
        - to: 0.0.0.0/0
          via: 192.168.1.1
          table: 100
      routing-policy:
        - from: 192.168.1.50/32
          table: 100
          priority: 1000
    eth1:
      addresses:
        - 10.0.0.50/24
      routes:
        - to: 0.0.0.0/0
          via: 10.0.0.1
          table: 200
      routing-policy:
        - from: 10.0.0.50/32
          table: 200
          priority: 1010

Kiểm tra cú pháp và áp dụng cấu hình mới:

sudo netplan try
sudo netplan apply

Giờ đây máy chủ của bạn đã có thể xử lý trơn tru lưu lượng ingress đồng thời trên cả hai card mạng. Gói tin phản hồi sẽ đi ra chính xác qua interface và gateway mà nó đã đi vào, loại bỏ hoàn toàn tình trạng rớt kết nối mà không cần đến NAT hay can thiệp sửa đổi gói tin phức tạp.

Share: