Khi đánh giá các giao thức đường hầm bảo mật, các kỹ sư thường tập trung vào độ bền của thuật toán mật mã và tính xác thực dữ liệu. Các thuật toán hiện đại như ChaCha20-Poly1305 (RFC 8439) bảo đảm kẻ nghe lén không thể đọc trộm hay giả mạo dữ liệu đường truyền. Dẫu vậy, bản thân việc mã hóa nội dung không giải quyết được một mối nguy cơ tiềm ẩn gắn liền với bản chất phi kết nối của mạng IP: tấn công phát lại gói tin (replay attack).

Kẻ tấn công theo dõi đường truyền mạng không cần phá khóa mã hóa để gây tổn hại cho hệ thống. Bằng cách ghi lại các gói tin UDP hợp lệ đã mã hóa rồi truyền lại sau đó, kẻ xấu có thể làm rối loạn phiên làm việc, lặp lại các giao dịch tài chính hoặc khai thác lỗ hổng máy trạng thái ở các tầng ứng dụng. Đối với các thuật toán mã hóa luồng như ChaCha20, việc tái sử dụng giá trị khởi tạo (nonce) trên hai gói tin khác nhau dưới cùng một khóa đối xứng là điều cấm kỵ, vì thao tác cộng loại trừ XOR đơn giản sẽ bộc lộ trực tiếp dòng khóa gốc.

Để triệt tiêu hoàn toàn nguy cơ phát lại nhưng vẫn duy trì thông lượng tối đa trên các mạng có độ trễ thất thường, WireGuard triển khai kiến trúc chống phát lại dựa trên tiêu chuẩn RFC 6479. Sự kết hợp giữa bộ đếm truyền tải 64-bit và cửa sổ trượt dạng mảng bit giúp hệ thống vừa miễn nhiễm với tấn công phát lại, vừa không làm rơi các gói tin bị đảo thứ tự khi truyền dẫn.

+-----------------------------------------------------------------------------+
|             CƠ CHẾ CỬA SỔ TRƯỢT DẠNG MẢNG BIT WIREGUARD RFC 6479            |
|                                                                             |
|      Bị loại bỏ (Quá cũ)             Cửa sổ mảng bit hợp lệ (2048 bit)      |
|  ◄───────────────────────────┤──────────────────────────────────────────►   |
|   Bộ đếm < (T - 2048)        │               (T - 2048) đến T            │  |
|                              │                                           │  |
|  ... [Gói k] [Gói k+1] ...   │ [Bit 0] [Bit 1] ... [Bit i] ... [Bit 2047]│  |
|                              │    │       │           │           │      │  |
|                              │   Cũ nhất  │       Gói hiện tại    │      │  |
|                              │ trong vùng │      (Kiểm tra & đặt) │      │  |
|                              └────────────┴───────────┬───────────┴──────┘  |
|                                                       │                     |
|                                                       ▼                     |
|                                            Nếu Bit == 1: HỦY (Gói lặp lại)  |
|                                            Nếu Bit == 0: NHẬN & Đặt bit = 1 |
|                                            Nếu Bộ đếm > T: TRƯỢT CỬA SỔ     |
+-----------------------------------------------------------------------------+

Nguy cơ tái dùng Nonce và bộ đếm truyền tải 64-bit

Mô hình mã hóa xác thực ChaCha20-Poly1305 yêu cầu ba thành phần đầu vào: khóa đối xứng 256-bit, dữ liệu gốc cần mã hóa và một giá trị ngẫu nhiên dùng một lần (nonce). WireGuard sử dụng trực tiếp một số nguyên 64-bit tuần tự làm nonce cho ChaCha20, kết hợp với thẻ xác thực Poly1305 dài 16 byte đính kèm gói tin.

Tính độc nhất của nonce mang ý nghĩa sống còn trong mật mã học. Nếu bên gửi phát đi hai gói tin khác nhau dùng chung một cặp Khóa và Nonce:

$$C_1 = P_1 \oplus Keystream(K, N)$$ $$C_2 = P_2 \oplus Keystream(K, N)$$

Bên nghe lén chỉ cần tính tổng XOR giữa hai bản mã sẽ triệt tiêu hoàn toàn dòng khóa:

$$C_1 \oplus C_2 = P_1 \oplus P_2$$

Phép toán này phơi bày mối quan hệ giữa các bản rõ cho phân tích thống kê và cho phép kẻ xấu giả mạo thẻ xác thực Poly1305. Nhằm ngăn chặn triệt để kịch bản này, bên gửi trong WireGuard tăng dần bộ đếm 64-bit cho từng gói tin vận chuyển, bắt đầu từ giá trị 0 ngay khi phiên kết nối được thiết lập.

Không gian số của bộ đếm 64-bit lên tới hơn $1.8 \times 10^{19}$ gói tin, trên thực tế là không thể cạn kiệt trong một phiên làm việc. Ngay cả khi truyền tải liên tục với tốc độ 100 Gbps, hệ thống phải mất nhiều thập kỷ mới dùng hết dải số này. Tuy nhiên, giao thức Noise IKpsk2 trong WireGuard vẫn định kỳ tạo khóa mới sau mỗi vài phút, đưa bộ đếm trở lại 0 với một khóa phiên hoàn toàn mới.

Thách thức đảo thứ tự gói tin trên hạ tầng mạng

Dù bên gửi tăng bộ đếm theo thứ tự nghiêm ngặt ($0, 1, 2, 3, \dots$), hạ tầng mạng Internet lại xử lý từng gói tin UDP một cách độc lập. Trên thực tế, các gói tin thường gặp phải:

  • Định tuyến đa đường (ECMP): Các thiết bị chuyển mạch tại trung tâm dữ liệu phân tán luồng gói tin qua các tuyến cáp có độ trễ truyền dẫn khác nhau.
  • Card mạng xử lý đa hàng đợi: Cơ chế Receive Side Scaling phân chia các khung dữ liệu đến các nhân vi xử lý khác nhau, tạo ra độ lệch xử lý nhỏ.
  • Độ trễ dao động trên sóng vô tuyến: Mạng di động 5G khi chuyển trạm phát sóng hoặc Wi-Fi khi đổi kênh tần số đều tạo ra những khoảng đệm làm chậm gói tin.

Hệ quả là các gói tin thường tới đích không theo đúng thứ tự gửi:

$$\text{Phát đi:} \quad [1, 2, 3, 4, 5, 6]$$ $$\text{Nhận về:} \quad [1, 3, 2, 5, 4, 6]$$

Nếu bên nhận áp dụng quy tắc tăng dần tuyệt đối (chỉ chấp nhận gói tin khi số thứ tự lớn hơn gói tin lớn nhất đã thấy), gói tin số 2 sẽ bị loại bỏ không thương tiếc vì đến sau gói số 3. Việc hủy bỏ các gói tin chỉ vì đến muộn vài mili-giây sẽ gây thảm họa cho các luồng TCP bên trong đường hầm.

Khi TCP bị rơi gói, nó sẽ gửi liên tiếp các phản hồi báo nhận trùng lặp, kích hoạt thuật toán truyền lại nhanh, giảm một nửa cửa sổ nghẽn và làm sụt giảm nghiêm trọng tốc độ truyền tải. Do đó, cơ chế chống phát lại buộc phải chấp nhận các gói tin bị đảo thứ tự hợp lệ trong khi vẫn phải loại bỏ triệt để các gói tin phát lại giả mạo.

+-----------------------------------------------------------------------------+
|                     MA TRẬN QUYẾT ĐỊNH XỬ LÝ GÓI TIN ĐẾN                    |
+--------------------------+----------------------------+---------------------+
| Bộ đếm gói tin ($C$)     | Vị trí tương đối ở cửa sổ  | Hành động của nhân  |
+--------------------------+----------------------------+---------------------+
| $C > T_{\text{đỉnh}}$     | Vượt lên phía trước cửa sổ | Đẩy cửa sổ tiến lên |
|                          |                            | Đánh dấu bit trong  |
+--------------------------+----------------------------+---------------------+
| $T_{\text{đỉnh}} - W \le$| Nằm trong phạm vi cửa sổ   | Nếu bit == 0: NHẬN  |
| $C \le T_{\text{đỉnh}}$  | hợp lệ hiện thời           | Nếu bit == 1: HỦY   |
+--------------------------+----------------------------+---------------------+
| $C < T_{\text{đỉnh}} - W$| Nằm sau phạm vi cửa sổ     | HỦY NGAY LẬP TỨC    |
|                          |                            | (Gói quá hạn/phát lại)|
+--------------------------+----------------------------+---------------------+

Thuật toán cửa sổ trượt RFC 6479: Mảng bit không dịch chuyển bộ nhớ

Để giải quyết mâu thuẫn này, các tiêu chuẩn IPsec thời kỳ đầu (RFC 4303) đã đưa ra ý tưởng cửa sổ trượt dạng mảng bit với kích thước 32 hoặc 64 bit. Khi có gói tin mới với số thứ tự lớn hơn xuất hiện, hệ thống sẽ dịch chuyển toàn bộ mảng bit sang trái một khoảng tương ứng.

Tuy nhiên, trên các đường truyền tốc độ cao xử lý hàng triệu gói tin mỗi giây, việc dịch chuyển một mảng bit lớn (như 1024 hay 2048 bit) liên tục trong bộ nhớ sẽ gây lãng phí chu kỳ xử lý của vi xử lý và làm mất dữ liệu trong bộ nhớ đệm CPU. WireGuard đã áp dụng giải pháp tối ưu được định nghĩa trong RFC 6479.

Biểu diễn toán học và lập chỉ mục xoay vòng

Thay vì dịch chuyển các bit trong bộ nhớ, RFC 6479 duy trì một mảng xoay vòng cố định gồm các từ máy. Trong WireGuard, cửa sổ chống phát lại có độ rộng $W = 2048$ bit, được tạo thành từ một mảng gồm 32 số nguyên không dấu 64-bit ($32 \times 64 = 2048$ bit):

#define COUNTER_WINDOW_SIZE 2048
#define COUNTER_BITS_PER_WORD 64
#define COUNTER_WORDS (COUNTER_WINDOW_SIZE / COUNTER_BITS_PER_WORD) // 32 từ

struct noise_replay_counter {
    u64 counter;
    unsigned long backtrack[COUNTER_WORDS];
    spinlock_t lock;
};

Khi một gói tin đến mang bộ đếm $C$, thuật toán xác định vị trí từ 64-bit quản lý bit đó thông qua phép chia lấy phần dư:

$$\text{Chỉ mục từ} = \left(\frac{C}{64}\right) \pmod{32}$$ $$\text{Độ lệch bit} = C \pmod{64}$$

Nhờ kích thước mảng là lũy thừa của 2, phép tính lấy phần dư được chuyển đổi thành phép toán logic AND cực nhanh trên thanh ghi:

unsigned long word_index = (counter / 64) & (COUNTER_WORDS - 1);
unsigned long bit_mask = 1UL << (counter & (COUNTER_BITS_PER_WORD - 1));

Cơ chế trượt cửa sổ linh hoạt

Khi gói tin đến, bên nhận phân loại nó vào một trong ba tình huống:

  1. Nằm trong cửa sổ hiện tại ($T - W < C \le T$): Nhân hệ thống kiểm tra bit tại vị trí tương ứng. Nếu bit đã mang giá trị 1, gói tin là bản sao chép lặp lại và bị hủy bỏ ngay. Nếu bit mang giá trị 0, gói tin được chấp nhận xử lý và bit đó được đánh dấu lên 1.
  2. Vượt qua đỉnh hiện tại ($C > T$): Gói tin mang số thứ tự lớn nhất từ trước đến nay. Cửa sổ cần tiến lên phía trước. Thay vì dịch chuyển bộ nhớ, thuật toán xóa trắng các từ nằm giữa đỉnh cũ và đỉnh mới về 0, cập nhật đỉnh mới ($T = C$) và đánh dấu bit tương ứng.
  3. Nằm ngoài phạm vi cửa sổ ($C \le T - W$): Gói tin mang số thứ tự đã quá cũ so với phạm vi 2048 gói tin gần nhất. Hệ thống hủy gói tin ngay lập tức mà không cần tốn năng lực giải mã.

Thiết kế này bảo đảm thời gian thực thi không đổi $O(1)$, không cần cấp phát thêm bộ nhớ động và bảo toàn hiệu suất tối đa cho bộ nhớ đệm vi xử lý.

Mô hình bộ nhớ vật lý của mảng xoay vòng (32 từ máy):
Từ 0         Từ 1         Từ 2               Từ 31
[64 bit]     [64 bit]     [64 bit]    ...    [64 bit]
    ▲
    │
    └── Con trỏ xoay vòng linh hoạt mà không cần dịch chuyển dữ liệu

Xử lý song song đa nhân và đồng bộ hóa không khóa

Trên các máy chủ hiệu năng cao, việc giải mã hàng triệu gói tin UDP trên một nhân vi xử lý đơn lẻ sẽ tạo điểm nghẽn nghiêm trọng. Trong nhân Linux, WireGuard chia nhỏ tác vụ mật mã và phân phối đều cho tất cả các nhân CPU thông qua cơ chế hàng đợi xử lý song song (padata).

Quy trình nhận gói tin diễn ra nhịp nhàng:

  1. Tiếp nhận gói tin: Trình điều khiển card mạng đưa gói tin UDP vào hàng đợi nhận.
  2. Giải mã mật mã song song: WireGuard phân phối tác vụ giải mã ChaCha20-Poly1305 ra các nhân CPU sẵn có. Mỗi nhân kiểm tra thẻ Poly1305 và giải mã phần dữ liệu độc lập.
  3. Xác minh chống phát lại tuần tự: Do quá trình giải mã đa nhân có thể hoàn thành lệch nhau, bước kiểm tra và cập nhật cửa sổ chống phát lại được thực hiện tập trung trong hàm hoàn tất tuần tự (wg_packet_consume_data_done) với khóa đồng bộ siêu nhẹ.

Nhờ tách bạch phần xử lý mã hóa nặng nhọc sang chế độ song song và giữ phần kiểm tra bộ đếm gọn gàng, WireGuard đạt tốc độ truyền tải hàng chục gigabit mỗi giây mà không lo ngại xung đột tài nguyên.

Cân bằng giữa kích thước cửa sổ và tỷ lệ rơi gói tin

Việc lựa chọn kích thước cửa sổ 2048 bit là kết quả của sự tính toán kỹ lưỡng giữa dung lượng bộ nhớ và khả năng chịu đựng độ trễ mạng:

  • Nếu cửa sổ quá hẹp (ví dụ 64 bit), chỉ cần một gói tin bị trễ quá 64 khung hình trên đường truyền 10 Gbps là nó sẽ rơi ra ngoài cửa sổ và bị hủy oan.
  • Nếu cửa sổ quá lớn (ví dụ 65.536 bit), chi phí bộ nhớ cho mỗi người dùng kết nối sẽ tăng cao, làm giảm hiệu quả lưu tạm của vi xử lý khi quản lý hàng chục nghìn kết nối đồng thời.

Cửa sổ 2048 bit mang lại điểm cân bằng lý tưởng. Trên đường truyền 1 Gbps với kích thước gói tin MTU 1420 byte, 2048 gói tin tương đương khoảng 2,9 megabyte dữ liệu đang truyền tải—vượt xa mức độ đảo thứ tự thông thường trên các tuyến cáp quang quốc tế.

Trong kiến trúc của VPNRice, giao thức RiceGuard kế thừa và tối ưu hóa các nguyên lý chống phát lại này. Kết hợp với việc điều phối lưu lượng tầng 3 và các phân tích chuyên sâu về Kiến trúc đóng gói UDP, RiceGuard ngăn ngừa hiệu quả tình trạng phát lại dữ liệu và chống tràn nghẽn trên các mạng công cộng.

Để trải nghiệm đường truyền bảo mật vững chắc với hiệu suất xử lý đa nhân tối ưu trên mọi thiết bị, bạn có thể tải bản cài đặt mới nhất tại trang Tải VPNRice.