Khi trạng thái ‘idle’ không thực sự rảnh rỗi: Cách một tối ưu hóa nhân Linux trở thành lỗi nghiêm trọng trong QUIC
Khám phá câu chuyện về một lỗi logic trong thuật toán kiểm soát tắc nghẽn CUBIC, nơi một tối ưu hóa nhân Linux vô tình tạo ra 'vòng lặp tử thần' khiến kết nối QUIC bị kẹt ở băng thông tối...
CUBIC, được chuẩn hóa trong RFC 9438, là thuật toán kiểm soát tắc nghẽn (Congestion Control Algorithm – CCA) mặc định trên Linux. Nó đóng vai trò quyết định cách thức các kết nối TCP và QUIC trên Internet thăm dò băng thông, phản ứng khi có mất gói tin và phục hồi sau đó. Tại Cloudflare, thư viện QUIC mã nguồn mở quiche cũng sử dụng CUBIC làm mặc định, biến nó thành thành phần cốt lõi trong luồng xử lý lưu lượng.
Table Of Content
Bài viết này chia sẻ về một lỗi hy hữu: cửa sổ tắc nghẽn (congestion window – cwnd) của CUBIC bị ghim vĩnh viễn ở mức tối thiểu và không thể phục hồi sau khi xảy ra sự cố tắc nghẽn. Vấn đề bắt nguồn từ một thay đổi trong nhân Linux nhằm tối ưu hóa trạng thái ‘idle’, nhưng khi chuyển sang triển khai QUIC, nó đã gây ra những hành vi không mong muốn.
Triệu chứng: Khi bài kiểm tra thất bại 61%
Sự cố được phát hiện trong các bài kiểm tra tích hợp proxy đầu vào của Cloudflare. Trong các kịch bản có tỷ lệ mất gói tin cao ở giai đoạn đầu kết nối, CUBIC không thể phục hồi như kỳ vọng. Dù mất gói tin đã dừng lại sau 2 giây, cwnd vẫn bị kẹt ở mức tối thiểu (2700 bytes, tương đương 2 gói tin) và trạng thái tắc nghẽn dao động liên tục giữa recovery và congestion avoidance khoảng 999 lần trong 6,7 giây.
Điều đáng chú ý là chu kỳ dao động này (~14ms) khớp chính xác với RTT (Round Trip Time) của kết nối. Điều này cho thấy thuật toán đang hiểu sai trạng thái kết nối mỗi khi nhận được ACK.
Truy tìm nguyên nhân gốc rễ
Vấn đề xuất phát từ cách CUBIC xử lý các khoảng thời gian ‘idle’ (rảnh rỗi). Trong nhân Linux, một bản vá từ năm 2017 đã được đưa ra để ngăn chặn việc cwnd tăng vọt sau khi ứng dụng tạm dừng gửi dữ liệu. Giải pháp là dịch chuyển ‘epoch’ (cột mốc thời gian tham chiếu của CUBIC) về phía trước một khoảng bằng thời gian idle.
Tuy nhiên, khi port sang quiche (chạy ở user space), việc tính toán thời gian idle trở nên thiếu chính xác. Thuật toán sử dụng thời điểm gửi gói tin cuối cùng để tính toán delta, thay vì thời điểm nhận ACK cuối cùng. Khi cwnd ở mức tối thiểu, mỗi khi một gói tin được gửi đi và nhận ACK, hệ thống lại hiểu nhầm rằng kết nối đã trải qua một khoảng thời gian idle bằng đúng RTT. Điều này khiến epoch_start bị đẩy liên tục vào tương lai, kích hoạt cơ chế bảo vệ sai lệch và ngăn cản cwnd tăng trưởng.
Giải pháp: Đo lường chính xác
Để phá vỡ ‘vòng lặp tử thần’ này, đội ngũ kỹ thuật đã thay đổi cách đo lường thời gian idle:
- Thêm biến
last_ack_timevào trạng thái CUBIC. - Cập nhật timestamp này mỗi khi nhận được ACK.
- Sử dụng
last_ack_timethay vì thời điểm gửi gói tin cuối cùng để tính toán delta idle.
Bằng cách đo khoảng cách thực tế kể từ khi ACK cuối cùng được xử lý, ranh giới phục hồi không còn bị đẩy vào tương lai một cách sai lệch, cho phép CUBIC thoát khỏi trạng thái kẹt và khôi phục băng thông bình thường.
Nguồn tham khảo: Cloudflare Blog



No Comment! Be the first one.