Khi thiết lập mạng riêng ảo theo mô hình chuyển tiếp toàn bộ lưu lượng (0.0.0.0/0 và ::/0), mọi gói tin phát sinh từ thiết bị đều phải đi qua đường hầm mã hóa. Thiết kế này tạo ra một nghịch lý kiến trúc cốt lõi được gọi là định tuyến đệ quy (routing recursion). Dữ liệu sau khi mã hóa cần được gửi ra mạng vật lý thông qua một socket UDP ra bên ngoài. Nếu hệ điều hành chuyển tiếp tất cả lưu lượng IP vào giao diện đường hầm ảo, gói tin UDP bọc ngoài sẽ bị quay ngược lại chính driver đường hầm, dẫn đến vòng lặp đóng gói vô tận.

Các giao thức truyền thống như OpenVPN hay IPsec trước đây cố gắng khắc phục bằng cách can thiệp vào bảng định tuyến chính thông qua tuyến tĩnh trỏ thẳng đến địa chỉ IP máy chủ VPN. Cách tiếp cận này bộc lộ nhiều điểm yếu khi thiết bị chuyển đổi mạng (như từ Wi-Fi sang mạng di động 4G/5G), khi hệ thống dùng DNS động hoặc khi máy chủ cân bằng tải qua Anycast.

WireGuard giải quyết triệt để nguy cơ lặp tuyến trên Linux nhờ sự kết hợp giữa nhãn tường lửa (fwmark), tùy chọn socket SO_MARK và cơ sở dữ liệu chính sách định tuyến (RPDB).

Nghịch lý định tuyến đệ quy trong đường hầm mã hóa

Để hiểu rõ sự cần thiết của định tuyến theo chính sách, hãy xem xét vòng đời của một gói tin mạng trên máy trạm Linux chạy giao diện WireGuard wg0.

Khi một ứng dụng gửi dữ liệu TCP tới máy chủ web tại địa chỉ 203.0.113.50, hệ thống mạng trong nhân Linux cần chọn giao diện đầu ra. Nếu người quản trị áp dụng cấu hình định tuyến thông thường như ip route add default dev wg0, nhân Linux sẽ xác định wg0 là chặng kế tiếp. Driver WireGuard tiếp nhận gói tin, đối chiếu bảng Cryptokey Routing, mã hóa tải trọng bằng ChaCha20-Poly1305 và đặt dữ liệu vào gói tin UDP gửi đến máy chủ tại 198.51.100.10:51820.

Gói tin ứng dụng (Đích: 203.0.113.50)
        │
        ▼
   Bảng định tuyến nhân: default dev wg0
        │
        ▼
Driver WireGuard (wg0): Đóng gói UDP ra ngoài (Đích: 198.51.100.10:51820)
        │
        ▼
   Bảng định tuyến nhân: Tiếp tục khớp default dev wg0! ──> VÒNG LẶP ĐỆ QUY

Khoảnh khắc socket UDP của WireGuard chuyển giao gói tin này cho ngăn xếp mạng Linux, nhân hệ điều hành lại tra cứu bảng Forwarding Information Base (FIB) một lần nữa. Do tuyến mặc định vẫn trỏ về wg0, gói tin UDP ngoài cùng lại bị đưa ngược vào wg0. Giao diện đường hầm cố gắng mã hóa một gói tin vốn đã mã hóa, hàng đợi socket phình to, vượt quá kích thước MTU và nhân buộc phải hủy gói tin. Đường truyền lập tức tê liệt.

Cơ sở dữ liệu chính sách định tuyến RPDB và cấu trúc hai bảng định tuyến

Hệ điều hành Linux hỗ trợ các cấu trúc mạng phức tạp thông qua Cơ sở dữ liệu chính sách định tuyến (RPDB), quản lý bằng lệnh ip rule. Thay vì chỉ phụ thuộc vào một bảng định tuyến tĩnh duy nhất, RPDB cho phép nhân đánh giá nhiều bảng độc lập dựa trên địa chỉ nguồn, cổng mạng, loại dịch vụ hoặc siêu dữ liệu gói tin.

Cấu hình mặc định của Linux bao gồm ba bảng quy tắc:

  1. local (bảng 255): Xử lý địa chỉ vòng lặp cục bộ (loopback) và địa chỉ quảng bá (broadcast).
  2. main (bảng 254): Chứa các tuyến đường tiêu chuẩn do DHCP và quản trị viên chỉ định.
  3. default (bảng 253): Dành cho các chính sách dự phòng.

Thay vì ghi đè cổng mặc định trong bảng main, công cụ quản lý WireGuard (như wg-quick) giữ nguyên bảng 254. Thay vào đó, hệ thống tạo một bảng định tuyến riêng biệt—thường mang mã số 51820—và đặt tuyến mặc định trỏ qua wg0 trong bảng này:

# Tạo tuyến mặc định trong bảng riêng biệt
ip route add default dev wg0 table 51820
ip -6 route add default dev wg0 table 51820

Nhờ tách biệt tuyến đường hầm khỏi cổng vật lý của bảng main, hệ điều hành duy trì hai không gian định tuyến song song. Thách thức kỹ thuật đặt ra là đưa lưu lượng của ứng dụng vào bảng 51820 nhưng cho phép socket UDP của WireGuard tra cứu bảng main (254).

Gắn nhãn socket qua SO_MARK và cơ chế lọc fwmark

Cầu nối giúp socket đường hầm thoát ra mạng vật lý là nhãn tường lửa (fwmark). Trong nhân Linux, mỗi cấu trúc bộ đệm gói tin (struct sk_buff) chứa một trường 32-bit mang tên skb->mark. Nhãn này chỉ tồn tại trong bộ nhớ nhân, không xuất hiện trên dây mạng và không thay đổi cấu trúc tiêu đề IP.

Khi nhân WireGuard khởi tạo socket UDP truyền tải, mã nguồn kích hoạt tùy chọn SO_MARK thông qua giao diện lập trình POSIX socket:

int fwmark = 51820;
setsockopt(sock_fd, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark));

Mọi gói tin UDP phát xuất từ socket này đều tự động mang giá trị nhãn 51820. Để điều phối lưu lượng, hệ thống thiết lập các quy tắc chính sách định tuyến tương ứng:

# Quy tắc chuyển hướng cho lưu lượng không có nhãn
ip rule add not fwmark 51820 table 51820
ip rule add table main suppress_prefixlength 0

Quy trình đánh giá diễn ra rành mạch và chuẩn xác:

  1. Lưu lượng ứng dụng: Trình duyệt gửi gói tin tới 203.0.113.50. Socket của trình duyệt không có nhãn, do đó skb->mark bằng 0. Nhân đối chiếu quy tắc not fwmark 51820. Vì 0 != 51820, quy tắc thỏa mãn. Gói tin được chuyển vào bảng 51820 và đi vào giao diện wg0.
  2. Lưu lượng truyền tải WireGuard: Sau khi đóng gói dữ liệu ứng dụng, WireGuard phát gói tin UDP ra ngoài từ socket đã gắn nhãn (skb->mark = 51820). Nhân đối chiếu quy tắc not fwmark 51820. Vì nhãn trùng khớp với 51820, quy tắc bị bỏ qua. Nhân chuyển sang quy tắc kế tiếp theo thứ tự ưu tiên trong RPDB, tức bảng main (254). Tại đây, cổng vật lý eth0 hoặc wlan0 chuyển gói tin thẳng tới bộ định tuyến cục bộ.

Giải pháp hai nhánh này bảo đảm gói tin mã hóa ra ngoài an toàn qua mạng vật lý mà không bị hút ngược vào wg0.

Lọc đường truyền ngược và bài toán kiểm tra rp_filter

Dù lưu lượng đi ra được điều hướng trơn tru, cấu hình VPN chuyển tiếp toàn bộ lưu lượng vẫn có thể gặp sự cố nếu cơ chế bảo mật chiều vào hủy bỏ các gói phản hồi hợp lệ. Rào cản lớn nhất là cơ chế Lọc đường truyền ngược (Reverse Path Filtering - RPF), được quy định trong RFC 3704 và kiểm soát bởi biến nhân rp_filter.

RPF giúp hệ thống ngăn chặn hành vi giả mạo địa chỉ IP. Khi nhận một gói tin trên một giao diện, nhân kiểm tra xem nếu gửi gói tin trả lời về địa chỉ nguồn đó thì giao diện đầu ra có trùng khớp với giao diện vừa nhận hay không.

Linux hỗ trợ ba chế độ RPF:

  • 0 (Tắt): Không kiểm tra địa chỉ nguồn.
  • 1 (Chế độ nghiêm ngặt): Gói tin đến phải thuộc đúng giao diện mà bảng định tuyến lựa chọn cho chiều về.
  • 2 (Chế độ linh hoạt): Địa chỉ nguồn chỉ cần có đường đi hợp lệ trong bất kỳ bảng định tuyến nào, không bắt buộc trùng khớp giao diện.

Trong chế độ nghiêm ngặt (rp_filter = 1), sự bất đối xứng của định tuyến theo chính sách khiến các gói tin bị từ chối. Khi gói tin phản hồi từ internet đi vào giao diện vật lý eth0, nhân thực hiện tra cứu chiều ngược về địa chỉ nguồn máy chủ từ xa. Do truy vấn này không mang nhãn fwmark của WireGuard, RPDB trỏ truy vấn vào bảng 51820, nơi khẳng định mọi lưu lượng ngoài mạng đều thuộc về wg0. Gói tin nhận ở eth0 nhưng chiều ngược lại chỉ vào wg0, khiến nhân xếp gói tin vào diện giả mạo và hủy ngay tại tầng liên kết dữ liệu.

Nhằm loại bỏ hiện tượng rớt gói tin ngầm này, triển khai WireGuard tự động cấu hình chế độ linh hoạt cho các cổng mạng liên quan:

sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.eth0.rp_filter=2
sysctl -w net.ipv4.conf.wg0.rp_filter=2

Đồng thời, chỉ thị suppress_prefixlength 0 được gắn kèm vào quy tắc tra cứu mặc định để bảo đảm các mạng con cục bộ trong bảng main không bị chiếm quyền bởi tuyến mặc định /0 của đường hầm.

Hộp cát di động so với máy tính bàn: Android VpnService và Network Namespaces

Tùy chọn socket SO_MARK yêu cầu quyền hạn quản trị viên hệ thống CAP_NET_ADMIN. Trên các bản phân phối Linux máy trạm hoặc máy chủ định tuyến, người dùng có toàn quyền thiết lập nhãn socket và bảng RPDB. Tuy nhiên, hệ điều hành di động áp dụng cơ chế hộp cát bảo mật nghiêm ngặt nhằm ngăn ứng dụng can thiệp vào bảng chính sách nhân.

Trên Android, ứng dụng WireGuard không thể gọi trực tiếp setsockopt(SO_MARK). Thay vào đó, kiến trúc Android cung cấp lớp android.net.VpnService. Khi ứng dụng mở socket UDP truyền tải, tiến trình gọi hàm VpnService.protect(int socketFd) để bàn giao bộ mô tả tệp cho hệ thống. Tiến trình mạng của Android trực tiếp gắn socket này vào mạng vật lý ở tầng dưới, hoàn toàn không chuyển tiếp qua thiết bị tun.

Linux Desktop:       Socket -> SO_MARK (51820) -> Quy tắc RPDB (not fwmark) -> Bảng main
Di động Android:     Socket -> VpnService.protect() -> Khóa trực tiếp vào mạng vật lý
Network Namespaces:  Giao diện eth0 thuộc netns0 <-> wg0 thuộc init_net (tách biệt hoàn toàn)

Tại các môi trường máy chủ đòi hỏi tính cô lập nghiêm ngặt, không gian tên mạng (Network Namespaces - ip netns) cung cấp phương án ngăn chặn vòng lặp độc đáo. Bằng cách di chuyển cổng mạng vật lý eth0 sang một namespace riêng và giữ giao diện wg0 ở namespace chính, ranh giới định tuyến được phân tách tuyệt đối mà không cần sử dụng nhãn tường lửa.

Kiến trúc chính sách đa bảng và lá chắn chống rò rỉ DNS trong RiceGuard

Trong môi trường thực tế, ứng dụng VPN cần xử lý mượt mà quá trình chuyển vùng mạng giữa các điểm truy cập Wi-Fi công cộng, đổi mới địa chỉ DHCP và các tiến trình phân giải DNS cục bộ như systemd-resolved chạy tại 127.0.0.53.

RiceGuard tích hợp công cụ định tuyến theo chính sách chuyên sâu nhằm loại trừ triệt để tình trạng phân mảnh định tuyến:

  1. Chuỗi quy tắc phân cấp theo thứ tự ưu tiên: RiceGuard thiết lập các chuỗi RPDB kiểm tra lưu lượng vòng lặp nội bộ, mạng cục bộ và địa chỉ phát đa hướng trước khi áp dụng tuyến đường hầm ảo.
  2. Khóa chốt an toàn ở tầng tường lửa (Fail-Closed): RiceGuard đồng bộ hóa quy tắc định tuyến với chuỗi lọc gói nftables. Nếu giao diện mạng biến động, tường lửa sẽ hủy mọi gói tin chưa được gắn nhãn hợp lệ thoát ra giao diện vật lý, ngăn chặn rò rỉ dữ liệu chưa mã hóa.
  3. Cô lập socket cho bộ phân giải hệ thống: Toàn bộ truy vấn tên miền phát sinh từ hệ điều hành được chuyển tiếp trực tiếp vào đường hầm mã hóa, bảo đảm không rò rỉ địa chỉ IP ra cổng vật lý ngoài ý muốn.

Sự kết hợp đồng bộ giữa nhãn gói tin, bảng chính sách định tuyến và cơ chế kiểm tra đường truyền giúp hạ tầng mạng vận hành bền bỉ, an toàn và tối ưu hiệu suất.

Tải VPNRice