Các cổng tập trung VPN quy mô lớn và các nút biên bảo mật thường xử lý đồng thời hai luồng lưu lượng khác biệt: luồng kết thúc đường hầm cục bộ (như các phiên mã hoá WireGuard hoặc IPsec chuyển đến các socket mở tại máy chủ) và luồng dữ liệu trung chuyển (các gói tin người dùng đã giải mã di chuyển qua các giao diện mạng để ra mạng diện rộng WAN). Trong ngăn xếp mạng Linux, quy trình phân loại gói tin và tra cứu bảng định tuyến quyết định trực tiếp tới khả năng mở rộng của toàn hệ thống.

Cơ chế IP Early Demux (ip_early_demux) được đưa vào nhân Linux nhằm loại bỏ các bước tra cứu cây định tuyến FIB (Forwarding Information Base) lặp đi lặp lại trên các luồng kết nối đã thiết lập. Tuy nhiên, đối với các máy chủ đảm nhiệm đồng thời cả hai vai trò: điểm cuối mã hoá đường hầm và trạm định tuyến chuyển tiếp lưu lượng cao, việc cấu hình Early Demux đòi hỏi phải nắm vững các điểm cân bằng kiến trúc chuyên sâu.


Bài toán định tuyến: Tra cứu FIB Trie đối chiếu với Bộ đệm Socket

Để hiểu rõ sự cần thiết của IP Early Demux, hãy phân tích đường đi truyền thống của một gói tin IPv4 sau khi rời khỏi trình điều khiển card mạng (NIC):

  1. Tiếp nhận gói tin: Gói tin đi vào tầng mạng (Layer 3) qua hàm ip_rcv() và chuyển tiếp đến ip_rcv_finish_core().
  2. Tra cứu định tuyến tiêu chuẩn: Theo mặc định, nhân Linux gọi ngay hàm ip_route_input_noref() để truy vấn cây định tuyến FIB trie. Quá trình tra cứu này kiểm tra địa chỉ IP đích, IP nguồn, nhãn định tuyến chính sách (fwmark) và giao diện tiếp nhận để xác định gói tin dành cho máy chủ cục bộ (RTN_LOCAL) hay cần chuyển tiếp (RTN_UNICAST).
  3. Phân phối về socket cục bộ: Nếu kết quả định tuyến là RTN_LOCAL, luồng xử lý chuyển sang ip_local_deliver(). Tại đây, tầng truyền vận (TCP hoặc UDP) thực hiện tra cứu bảng băm socket (inet_lookup_established hoặc bảng băm cổng UDP) để xác định cấu trúc struct sock tiếp nhận.
[ Gói tin IPv4 đi vào ]
          │
          ▼
[ Luồng truyền thống: ip_rcv_finish_core() ]
          │
          ▼
[ Tra cứu bảng FIB Trie: ip_route_input_noref() ] ──► (Tiêu tốn chu kỳ CPU)
          │
          ├─► Nếu gửi nội bộ (RTN_LOCAL) ──► ip_local_deliver() ──► Bảng băm Socket
          └─► Nếu chuyển tiếp (RTN_UNICAST) ──► ip_forward()

Trên một gateway xử lý 5 triệu gói tin mỗi giây phân bổ qua hàng nghìn đường hầm VPN đồng thời, việc duyệt qua cây FIB trie cho từng gói tin riêng lẻ gây áp lực lớn lên băng thông bộ nhớ và làm xáo trộn bộ nhớ đệm CPU L2/L3.


Kiến trúc vận hành: Cách thức Early Demux đi tắt qua FIB

IP Early Demux đảo ngược trình tự xử lý đối với các luồng kết nối đang hoạt động. Trước khi nhân hệ điều hành kích hoạt hệ thống con định tuyến FIB, cơ chế này thực hiện một bước tìm kiếm socket dự đoán trực tiếp ngay trong ip_rcv_finish_core().

[ Gói tin IPv4 đi vào ]
          │
          ▼
[ Early Demux: ip_rcv_finish_core() ]
          │
          ▼
[ Tra cứu Socket sớm: tcp_v4_early_demux() / udp_v4_early_demux() ]
          │
          ├─► Tìm thấy khớp (sk->sk_rx_dst hợp lệ):
          │         │
          │         ├─► Gán trực tiếp đích đệm (skb_dst_set_noref)
          │         └─► Chuyển thẳng tới ip_local_deliver() [Bỏ qua FIB trie]
          │
          └─► Không khớp socket nội bộ:
                    │
                    └─► Quay lại tra cứu tiêu chuẩn qua ip_route_input_noref()

Trong mã nguồn nhân tại net/ipv4/ip_input.c, hàm kiểm soát Early Demux kích hoạt các bộ xử lý theo từng giao thức:

/* Luồng xử lý khái niệm trong net/ipv4/ip_input.c */
static int ip_rcv_finish_core(struct net *net, struct sock *sk,
                             struct sk_buff *skb, struct net_device *dev,
                             const struct sk_buff *hint)
{
    const struct iphdr *iph = ip_hdr(skb);

    if (net->ipv4.sysctl_ip_early_demux && !skb_dst(skb)) {
        const struct net_protocol *ipprot;
        int protocol = iph->protocol;

        ipprot = rcu_dereference(inet_protos[protocol]);
        if (ipprot && ipprot->early_demux)
            ipprot->early_demux(skb);
    }

    /* Nếu đích đã được gán bởi early_demux, tra cứu FIB được bỏ qua */
    if (!skb_dst(skb)) {
        int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
                                      iph->tos, dev);
        if (unlikely(err))
            goto drop;
    }
    return NET_RX_SUCCESS;
}

Cơ chế lưu đệm đích đến (sk_rx_dst)

Khi một phiên TCP được thiết lập hoặc socket UDP vận hành ở chế độ kết nối (thông qua hàm connect()), nhân Linux lưu trữ mục tiêu định tuyến đầu vào trong biến sk->sk_rx_dst.

Trong quá trình thực thi tcp_v4_early_demux(), nhân Linux bóc tách bộ 4 thông số (4-tuple) từ tiêu đề IP và TCP, định vị cấu trúc sock đã thiết lập và kiểm tra tính hợp lệ của sk->sk_rx_dst. Nếu hợp lệ, nhân gán tham chiếu bộ đệm này vào skb->_skb_refdst bằng hàm skb_dst_set_noref(skb, dst). Gói tin lập tức được chuyển lên tầng xử lý dữ liệu mà không cần chạm vào bảng định tuyến.


Sự đánh đổi hiệu năng: Điểm kết thúc đường hầm đối chiếu với Chuyển tiếp gói tin

Mặc dù Early Demux mang lại bước tiến rõ rệt về thông lượng cho các kết nối cục bộ, cơ chế này tạo ra sự đánh đổi đáng lưu ý trên các cổng VPN lai ghép:

  • Lãng phí chu kỳ trên lưu lượng chuyển tiếp: Khi gateway định tuyến lưu lượng người dùng đã giải mã ra các cổng kết nối công cộng, những gói tin đó không bao giờ hướng tới các socket cục bộ. Nếu Early Demux bật, nhân Linux vẫn thực hiện thao tác tìm kiếm bảng băm socket cho mọi gói tin chuyển tiếp, dẫn đến thất bại và buộc phải quay lại tra cứu FIB trie.
  • Rủi ro sai lệch khi chuyển tuyến bất đối xứng: Khi bảng định tuyến đa đường (ECMP) thay đổi hoặc đường hầm dự phòng kích hoạt, con trỏ sk_rx_dst được lưu trong bộ đệm có thể dẫn đường theo tham số cũ cho đến khi socket xác thực lại mục tiêu.
+-------------------------------------------------------------------------+
|                  Các kịch bản hoạt động của Gateway VPN                 |
+-------------------------------------------------------------------------+
| Kịch bản A: Thiết bị trạm kết thúc đường hầm (Đầu cuối RiceGuard)       |
| -> Gói tin hướng tới socket cục bộ lắng nghe (gói UDP đóng gói mã hoá)  |
| -> Tối ưu: Kích hoạt ip_early_demux và udp_early_demux                  |
|                                                                         |
| Kịch bản B: Máy chủ định tuyến trung chuyển thuần tuý                   |
| -> Gói tin chuyển tiếp giữa mạng WAN và các giao diện nội bộ           |
| -> Tối ưu: Tắt ip_early_demux để loại bỏ các lượt tìm kiếm băm thất bại |
+-------------------------------------------------------------------------+

Hướng dẫn thiết lập và tinh chỉnh Early Demux trên Linux

Hệ điều hành Linux cung cấp các tham số sysctl để quản trị Early Demux trên phạm vi toàn hệ thống và theo từng giao thức truyền vận.

1. Kiểm tra tham số hiện hành

Xem giá trị cấu hình đang áp dụng trên gateway VPN:

# Kiểm tra các tham số Early Demux
sysctl net.ipv4.ip_early_demux
sysctl net.ipv4.tcp_early_demux
sysctl net.ipv4.udp_early_demux

2. Tinh chỉnh cho nút máy chủ kết thúc đường hầm

Trên các gateway mà tác vụ chính là giải mã các đường hầm VPN (như các nút RiceGuard tiếp nhận hàng triệu khung đóng gói UDP), kích hoạt đồng thời cả IP và UDP early demux:

# Kích hoạt early demux tổng thể
sysctl -w net.ipv4.ip_early_demux = 1

# Kích hoạt TCP early demux cho cổng quản trị và API
sysctl -w net.ipv4.tcp_early_demux = 1

# Kích hoạt UDP early demux cho các socket đường hầm có kết nối
sysctl -w net.ipv4.udp_early_demux = 1

Để giữ nguyên cấu hình này qua các lần tái khởi động hệ thống, tạo tệp /etc/sysctl.d/99-early-demux.conf:

cat << 'EOF' > /etc/sysctl.d/99-early-demux.conf
net.ipv4.ip_early_demux = 1
net.ipv4.tcp_early_demux = 1
net.ipv4.udp_early_demux = 1
EOF
sysctl --system

3. Tinh chỉnh cho nút trung chuyển và chuyển tiếp NAT thuần tuý

Nếu máy chủ vận hành chủ yếu như một router trung chuyển NAT chuyển tiếp lưu lượng người dùng ra ngoài mà không duy trì các socket đường hầm cục bộ, tắt Early Demux để bảo toàn chu kỳ CPU:

# Tắt early demux trên máy chủ chuyển tiếp trung chuyển
sysctl -w net.ipv4.ip_early_demux = 0

4. Giám sát hiệu quả lưu đệm với công cụ nstat

Theo dõi tỷ lệ tận dụng bộ đệm định tuyến và kiểm tra các bộ đếm thống kê bằng nstat:

# Kiểm tra số lượt trúng socket sớm và thống kê gói tin định tuyến
nstat -az | grep -E "IpEarlyDemux|IpInReceives|IpForwDatagrams"

So sánh cấu trúc: Bật và Tắt cơ chế Early Demux

Tác động thực tế của Early Demux có sự khác biệt rõ rệt phụ thuộc vào đích đến của luồng dữ liệu:

Chỉ số đánh giá Bật Early Demux (Socket nội bộ) Tắt Early Demux (Đường dẫn gốc) Bật Early Demux trên máy chuyển tiếp
Gánh nặng FIB Trie Bỏ qua hoàn toàn trên luồng sẵn có Tra cứu trên từng gói tin riêng rẽ Tra cứu FIB cộng thêm lần thử băm hỏng
Dấu chân Cache L2/L3 Tập trung vào bảng băm socket Trải rộng trên các nhánh cây FIB Tăng cao do phải tra cứu hai bảng
Tốc độ gói tin PPS Đạt mức ổn định 7.8 Mpps mỗi lõi Duy trì ở mức 5.9 Mpps mỗi lõi Giảm nhẹ khoảng ~4% thông lượng
Độ trễ chuyển tiếp Tương đương (chuyển sang FIB) Độ trễ ổn định chuẩn mực Tăng thêm ~3-5% độ trễ trên gói nhỏ
Rủi ro sai lệch định tuyến Rất thấp (nhờ bước kiểm định dst) Hoàn toàn không (luôn tính tuyến mới) Không xảy ra (không khớp socket)

Ứng dụng thực tế trong RiceGuard: Tách biệt kiến trúc định tuyến

Trên mạng lưới phân phối của VPNRice, các nút dịch vụ RiceGuard tách rời khâu tiếp nhận đường hầm với quy trình trung chuyển lưu lượng:

  1. Giao diện tiếp nhận mặt trước: Các cổng kết nối mạng WAN tiếp nhận khung UDP mã hoá của khách hàng kích hoạt net.ipv4.ip_early_demux = 1 và net.ipv4.udp_early_demux = 1. Các gói tin WireGuard được đối chiếu ngay với cấu trúc socket kết nối sẵn, chuyển thẳng vào luồng giải mã RiceGuard mà không chịu tổn hao từ tầng định tuyến.
  2. Giao diện trung chuyển mặt sau: Không gian tên mạng (network namespace) phụ trách chuyển tiếp các gói tin IPv4/IPv6 đã giải mã vận hành với net.ipv4.ip_early_demux = 0. Điều này giúp các động cơ định tuyến thực thi tra cứu FIB trie thông suốt mà không gặp lỗi tìm kiếm băm lãng phí.
  3. Cô lập kênh điều khiển và phân giải tên miền: Các socket quản trị TCP và máy chủ phân giải DNS an toàn hoạt động trên vùng mạng chuyên biệt, duy trì tcp_early_demux = 1 nhằm xử lý nhanh các truy vấn và bảo vệ tuyệt đối cơ chế chống rò rỉ DNS.