12 điểm bởi GN⁺ 2025-08-02 | 1 bình luận | Chia sẻ qua WhatsApp
  • Bản vá đầu tiên để tích hợp chính thức giao thức QUIC vào nhân Linux đã được gửi lên
  • Mục tiêu là khắc phục các hạn chế của TCP hiện tại như độ trễ, head-of-line blocking, và sự xơ cứng giao thức do các thiết bị trung gian gây ra
  • QUIC dựa trên UDP, hỗ trợ đa luồng và mã hóa end-to-end; nếu được đưa vào nhân, khả năng tận dụng trên nhiều nền tảng và phần cứng sẽ cao hơn
  • Hiệu năng của bản triển khai ban đầu trong nhân được đo là thấp hơn TCP hiện có và kernel TLS, nhưng có thể kỳ vọng cải thiện nhờ hardware offloading và tối ưu hóa trong tương lai
  • Hiện đang có nhiều thảo luận sôi nổi về hỗ trợ trong Samba, SMB/NFS dựa trên nhân, curl, v.v., nhưng có vẻ sẽ còn mất thời gian trước khi được hợp nhất vào mainline

Bối cảnh ra đời của giao thức QUIC và các giới hạn của TCP

  • QUIC được tạo ra nhằm giải quyết nhiều vấn đề vốn có của TCP trên Internet hiện nay
  • Độ trễ do bắt tay 3 bước trong quá trình thiết lập kết nối của TCP, việc hỗ trợ đa luồng còn hạn chế, cùng hiện tượng head-of-line blocking khi mất gói đều làm suy giảm trải nghiệm web
  • Metadata của TCP được truyền đi mà không mã hóa, tạo ra rủi ro rò rỉ thông tin; đồng thời các middlebox (thiết bị trung gian) trên Internet lọc lưu lượng dựa trên thông tin kết nối, từ đó dẫn đến sự xơ cứng giao thức (ossification)
  • Ngay cả các nỗ lực cải tiến TCP (ví dụ: Multipath TCP) cũng rơi vào tình trạng không thể hoạt động bình thường nếu không ngụy trang thành TCP hiện có

Đặc điểm và ưu thế kỹ thuật của QUIC

  • QUIC hoạt động trên UDP và có thể thiết lập kết nối nhanh mà không cần quy trình bắt tay 3 bước riêng như TCP
  • Thiết kế truyền tải đa luồng được đưa vào để việc mất gói không ảnh hưởng đến toàn bộ luồng dữ liệu
  • Dữ liệu truyền tải liên quan đến QUIC luôn được mã hóa đầu-cuối (dựa trên TLS), nên thiết bị trung gian không thể truy cập thông điệp bên trong
  • Nếu môi trường mạng cho phép các gói UDP đi qua, QUIC cũng có thể hoạt động bình thường

Tổng quan về bản vá tích hợp QUIC trong nhân Linux

  • Bản vá được gửi lên giới thiệu một kiểu giao thức mới là IPPROTO_QUIC, cho phép tận dụng lời gọi hệ thống socket() hiện có
  • Tương tự TCP, có thể dùng các lời gọi như bind(), connect(), listen(), accept(), nhưng cách xử lý về sau có những điểm khác biệt
  • Quản lý phiên TLS và quá trình xác thực/mã hóa được xử lý ở không gian người dùng; sau khi kết nối, bắt tay TLS phải hoàn tất ở mỗi bên thì mới có thể gửi nhận dữ liệu
  • Sau kết nối ban đầu, có thể cache kết quả thương lượng TLS để tăng tốc đáng kể khi hai hệ thống kết nối lại
Quảng cáo

Thách thức về hiệu năng và triển vọng

  • Bản triển khai QUIC trong nhân được gửi lên hiện vẫn yếu hơn kernel TLS và TCP hiện có về mặt hiệu năng
    • Thông lượng thấp hơn tới dưới 3 lần so với TLS trong nhân; ngay cả khi tắt mã hóa, thông lượng vẫn có thể thấp hơn TCP tới 4 lần
  • Các nguyên nhân được chỉ ra gồm chưa hỗ trợ segmentation offloading, phát sinh thêm sao chép dữ liệu trên đường truyền gửi, và quá trình mã hóa header
  • Trong tương lai, nếu được bổ sung hỗ trợ hardware offloading và tối ưu hóa bản triển khai trong nhân, hiệu năng được kỳ vọng sẽ cải thiện

Tình hình áp dụng và triển vọng sắp tới

  • Nhiều dự án như máy chủ/khách Samba, hệ thống tệp SMB và NFS trong nhân, curl, v.v. đang thảo luận tích cực về hỗ trợ QUIC trong nhân
  • Bản vá có quy mô khoảng 9.000 dòng và hiện mới chỉ bao gồm mã hỗ trợ mức thấp. Toàn bộ triển khai sẽ được bổ sung trong các bản vá tiếp theo
  • Việc review mã và thảo luận hợp nhất mới chỉ ở giai đoạn khởi đầu, nên có vẻ sẽ còn cần thêm thời gian trước khi đi vào sử dụng thực tế
    • Xét tiền lệ gần đây khi giao thức Homa cần 11 lần gửi trong suốt 9 tháng mới được hợp nhất vào nhân, QUIC cũng được dự đoán chỉ có thể vào mainline sau năm 2026

1 bình luận

 
GN⁺ 2025-08-02
Ý kiến trên Hacker News
  • Gần đây tôi đã thêm ssl_preread_server_name vào cấu hình NGINX để proxy_pass các yêu cầu của một tên miền cụ thể sang một instance NGINX khác
    Instance đầu tiên chỉ chuyển tiếp luồng TLS thô (kèm proxy_protocol), còn instance thứ hai mới thực sự đảm nhiệm việc kết thúc TLS
    Cách này khá hiệu quả khi triển khai failover - nếu đường đi mặc định của máy chủ bị down thì chỉ cần cập nhật bản ghi DNS A sang NGINX của máy failover, và instance đó sẽ định tuyến các yêu cầu của tên miền cụ thể theo một đường riêng về backend gốc
    Khá tiện vì không cần sao chép toàn bộ cấu hình TLS
    Tuy nhiên, cách này không áp dụng được cho HTTP/3
    HTTP/3 dựa trên QUIC, chạy trên UDP, và mã hóa SNI trong quá trình handshake nên không thể định tuyến theo tên miền bằng ssl_preread_server_name
    Tôi muốn biết có giải pháp nào hỗ trợ định tuyến dựa trên SNI cho HTTP/3 hay không, hay nếu cần tính năng này thì vẫn phải giữ HTTP/1.1 hoặc HTTP/2 over TLS
    • Phần lớn client hỗ trợ QUIC cũng hỗ trợ bản ghi DNS HTTPS, nên có thể thêm một bản ghi ưu tiên thấp làm phương án failover và để client tự xử lý
      Trên thực tế, cách hoạt động còn tùy vào implementation của client (xem tình trạng hỗ trợ bản ghi HTTPS của Chromium trong issue này), nhưng khi kết nối QUIC thất bại thì client thường tự động fallback sang HTTP/1.1/2, đồng thời vẫn tôn trọng header Alt-Svc
      Nếu là failover có kế hoạch thì cũng có thể ngừng gửi header Alt-Svc và chờ cho client timeout sang instance thay thế
      Nếu thực sự cần định tuyến QUIC, may là thông tin SNI luôn nằm trong gói đầu tiên nên có thể định tuyến bằng cách kiểm tra packet
      udpgrm của Cloudflare có thể là tài liệu tham khảo hữu ích, và cách này dùng được khi không có ECH (Encrypted Client Hello)
      Nếu có ECH thì router phải có khóa giải mã mới có thể đưa ra quyết định định tuyến, và ở cấp giao thức cũng có thể thiết kế failover dạng cascade
      Có thể xem phần hiện thực mã cụ thể trong ví dụ udpgrm
    • Việc kết thúc TLS ngay tại edge (ví dụ NGINX) thực ra không phải là điểm rủi ro lớn trong môi trường letsencrypt hiện nay
      Nếu kẻ tấn công truy cập được máy chủ đó thì việc xin lại chứng chỉ SSL cũng dễ thôi, nên thay vì nghĩ đến một hệ thống failover phức tạp thì kết thúc TLS trực tiếp có lẽ hợp lý hơn
      Cá nhân tôi chưa bao giờ tự tái hiện được lợi ích về hiệu năng hay độ tin cậy của QUIC
      Tôi đã thử đi thử lại nhiều năm nhưng cuối cùng hầu như luôn tắt nó đi vì lý do hiệu năng
      Failover dựa trên DNS cũng phải mất vài phút mới phản ánh thật sự, còn các client đơn giản như trình duyệt thì failover cũng không diễn ra tốt
      Vì vậy tôi dùng cách viết trực tiếp onerror handler để tải đường dẫn thứ hai
      Ví dụ, tôi dùng kiểu mã này cho mục đích theo dõi quảng cáo, và cũng bọc fetch API theo cùng một cách
      Cách này hiệu quả hơn rất nhiều so với mọi nỗ lực khác tôi từng thử
    • Nếu là môi trường failover thì tôi sẽ không bận tâm đến failover cho QUIC
      Ngay cả khi QUIC được quảng bá trong DNS, nếu trình duyệt không kết nối được bằng QUIC thì nó vẫn tự động fallback sang HTTP/1 hoặc HTTP/2 over TLS, nên có thể dùng nguyên cách failover hiện có
    • Vấn đề này thuộc kiểu “không phải bug”
      Một đặc điểm thiết kế của HTTP/3 là không để lộ thông tin endpoint xuống tới tầng TLS
      Cá nhân tôi xem đây là một ưu điểm
      HAProxy có thể làm raw TLS proxy nhưng không thể định tuyến theo hostname
      Cloudflare tunnel có một khả năng đặc biệt là định tuyến theo hostname mà không cần kết thúc TLS, nhưng muốn dùng thì DNS cũng phải trỏ qua Cloudflare
      Có thể xem hình dung này qua truyện tranh xkcd liên quan
    • Câu hỏi khá thú vị
      Tôi cũng tự hỏi liệu trong môi trường TCP+TLS, khi dùng Encrypted Client Hello thì có tồn tại hạn chế tương tự hay không
      Tôi nghĩ câu trả lời gần như sẽ giống hệt
  • Tôi đã nhớ bài viết này từ lâu về các nhược điểm của QUIC
    Cuộc thảo luận lần này cho tôi cảm giác là đang từng bước giải quyết những vấn đề như vậy
    Sau này cũng có thể còn mở ra khả năng hỗ trợ bằng phần cứng từ card mạng
    • QUIC không thật sự phù hợp lắm với lưu lượng giữa máy với máy
      Nhưng phần lớn lưu lượng Internet hiện nay là giữa thiết bị di động và máy chủ, và ở đoạn đó QUIC cùng HTTP/3 mới thể hiện rõ giá trị
      Với các mục đích khác thì vẫn có thể tiếp tục dùng TCP
  • Tôi tò mò socket API cho multi-stream sẽ trông như thế nào
    Có lẽ bên ngoài vẫn như nhiều connection riêng rẽ nhưng bên trong được cache lại
    Cá nhân tôi thích kiểu nhận một đối tượng connection rõ ràng rồi mở stream riêng từ đó hơn, nhưng hiện tại thì cách bây giờ tôi cũng chấp nhận được
    Xem thảo luận liên quan thì nếu đây không phải extension, phía server cũng có thể tạo stream mới sau khi connection đã được thiết lập
    Với phía client, thứ thực tế là stream nhưng lại khó tách abstraction như một “connection”, nên có vẻ về bản chất cần một lớp API hoàn toàn mới
    Tôi đoán có thể sẽ là cấu trúc nhận file descriptor qua recvmsg cho mỗi stream mới
    • Tính năng đa luồng này đã có trong socket API của SCTP, nên có thể tham khảo interface của SCTP
  • Bất kể có hiện thực trong kernel hay không, tôi rất muốn OpenSSH có hỗ trợ QUIC
    Tôi muốn thứ gì đó chống chịu sự cố mạng tốt như Mosh nhưng vẫn dùng nguyên toàn bộ tính năng của OpenSSH như SFTP, SOCKS, port forwarding, quản lý trạng thái, roaming, v.v.
    Tôi cũng tò mò liệu OpenSSH có thể tận dụng được hỗ trợ từ kernel hay không
    Xem Mosh
    • SSH sẽ phải thay thế hoàn toàn lớp mã hóa và multiplexing của nó bằng QUIC, nên đây sẽ là một khối lượng công việc rất lớn
      Có lẽ làm hẳn một giao thức đăng nhập riêng dựa trên QUIC còn hợp lý hơn
      Hiện đã có một số hướng tiếp cận đang ở giai đoạn prototype
    • OpenSSH là dự án của OpenBSD nên có thể họ sẽ không quá quan tâm đến Linux API
  • Người ta nói nút thắt của TCP là handshake, nhưng tôi cứ nghĩ connection reuse hay multiplexing đã giải quyết được chuyện đó rồi
    Trong khi đó lại có thông tin rằng hiện thực QUIC trong kernel hiện chậm hơn Linux khoảng 3-4 lần, dù khoảng cách hiệu năng này có thể sớm thu hẹp
    Nếu tốc độ là ưu điểm của QUIC mà thực tế lại chậm hơn, vậy lý do để dùng QUIC là gì?
    Theo chính tác giả của PR thì một phần suy giảm hiệu năng đến từ thiết kế giao thức, vậy có phải TCP còn có những vấn đề khác cần xử lý riêng không?
    • Bài viết cũng nhắc khá nhiều đến nguyên nhân QUIC chậm hơn
      Tóm lại phần lớn là vì “chưa tối ưu”
      Ví dụ như chưa hỗ trợ segment offload, có thêm bước copy dữ liệu trên đường truyền, overhead do mã hóa header, và phần lớn đều có khả năng khắc phục
      Bài benchmark ở đây được chạy trong một môi trường cực kỳ lý tưởng
      Ngoài đời, môi trường di động có biến động mạng lớn nên giới hạn cấu trúc của TCP bộc lộ rõ hơn
      Trên thực tế, cũng có rất nhiều trường hợp đã hiện thực những tính năng tương tự QUIC trên nền TCP như HTTP/2
      Xét cho cùng, QUIC là một stack mạng tổng hợp hoạt động từ lớp 5 OSI trở lên, còn TCP là một engine gần lớp 3, nên về cấu trúc vốn đã khó so sánh trực tiếp
      Quan trọng hơn cả, ưu điểm của QUIC là thiết lập và tái thiết lập connection nhanh hơn, đồng thời đảm bảo tính liên tục của session ngay cả khi IP thay đổi
      Cấu trúc multiplexing và stream không chặn giúp đơn giản hóa mạnh việc thiết kế các giao thức tầng trên
      Nếu cấu trúc này đi vào kernel thì dư địa tối ưu hiệu năng sẽ còn rất lớn
      Về sau, thay vì cứ xây các giải pháp nhiều tầng đè lên giới hạn của TCP, có lẽ chúng ta nên dùng thường xuyên hơn những nền tảng tiên tiến như QUIC
    • Nút thắt của TCP không chỉ là handshake
      Khi có packet loss, toàn bộ phần truyền sau đó sẽ bị trì hoãn cho đến khi khôi phục xong, tức giới hạn mang tính cấu trúc của HOL blocking là rất lớn
    • Không thể tái sử dụng một “connection không tồn tại”, nên nhiều cuộc thảo luận tập trung vào việc giảm latency
      Đây không đơn thuần là câu chuyện tốc độ, mà là cải thiện độ trễ
    • Lợi thế của QUIC là khả năng theo dõi dựa trên connection ID do server cung cấp
      Có thể tham khảo tài liệu giải thích kỹ thuật
  • Tôi hơi bối rối giữa một bên là thông điệp “đưa stack mạng lên userspace để tăng hiệu năng”, còn bên kia là cuộc thảo luận lần này lại muốn đưa nó về phía kernel
    • Phần lớn stack QUIC chạy trên UDP trong kernel
      Việc chuyển ngữ cảnh giữa kernel và userspace là nút thắt lớn
      Mạng userspace (ví dụ truy cập trực tiếp NIC) loại bỏ việc vào kernel
      Ngược lại, cung cấp tính năng trong không gian kernel (ví dụ sendfile, in-kernel TLS, NIC offloading, DMA trực tiếp từ đĩa sang NIC) cũng giúp giảm tổng số lần chuyển ngữ cảnh và copy dữ liệu
      Hiện tại các stack QUIC lại không tận dụng trọn vẹn lợi ích của cả hai phía
      I/O packet vẫn dựa trên syscall và vẫn không tránh được copy dữ liệu
      Dùng batch I/O như io_uring có thể giảm số lần chuyển ngữ cảnh, nhưng không làm giảm bản thân việc copy
    • Đúng là như vậy
      Có hai hướng: kernel bypass + DMA, hoặc loại userspace ra khỏi đường đi dữ liệu như mô hình sendfile/ktls
      Hiện thực QUIC trong kernel hiện chưa có được lợi thế trọn vẹn của cả hai hướng
    • Cuối cùng thì vẫn phải chuyển dữ liệu vào buffer của NIC
      Nếu có thể ghi trực tiếp vào NIC bằng DMA, hoặc phải chuyển qua syscall của kernel, chênh lệch hiệu năng là rất lớn
      Vì vậy, mạng userspace chỉ thật sự thuyết phục khi có kiểu chuyển privilege và kiến trúc DMA như vậy
    • Việc userspace truy cập trực tiếp dữ liệu mạng, có lẽ không cần syscall, với phần mềm người dùng thông thường thì không quá có ý nghĩa
      Chủ yếu chỉ các công ty cực lớn như MOFAANG mới dùng
      Về lý thuyết, nhiều người kỳ vọng io_uring sẽ phổ cập những lợi ích đó, nhưng hiện vẫn chưa đến giai đoạn dùng thực tế rộng rãi
    • Việc chuyển ngữ cảnh khi truy cập phần cứng quá chậm
      Đó là lý do TCP/IP vẫn nằm trong kernel ở các hệ điều hành chính
  • “Vì sao lại đưa ngày càng nhiều tính năng vào kernel?”
    Tôi từng nghĩ vai trò của kernel là quản lý bộ nhớ, phần cứng và tác vụ, vậy các giao thức nằm trên IP có phải nên xử lý ở userland mới đúng không?
    • Nếu xử lý networking, routing, VPN, v.v. trong không gian kernel thì với một số trường hợp sử dụng sẽ có cải thiện hiệu năng
      Ngược lại, tách những stack đó ra userspace cũng có thể mang lại lợi ích hiệu năng trong một số trường hợp khác
    • Khi đi vào kernel thì có lợi thế về hardening, hỗ trợ LTS, và tối ưu ở cấp kernel lẫn mạng
    • Lý do chính là tối ưu DMA transfer và NIC offloading
    • Nếu để toàn bộ giao thức trên IP ở userspace thì sẽ không thể dùng theo kiểu multiprocess
      TCP/UDP hiện do kernel điều phối việc định tuyến socket theo cổng, nên nhiều chương trình có thể dùng TCP/UDP đồng thời
      QUIC chạy trên UDP, vì vậy trọng tâm tranh luận ở đây vẫn có lý
      Điểm cần nhấn mạnh là chỉ những giao thức ngay trên IP mới khó đặt hoàn toàn ở userspace
  • Tôi nghĩ QUIC là một thay đổi mang tính đột phá với nhiều người
    Hy vọng Internet sau này sẽ nhanh hơn một chút
    Trong môi trường truyền dẫn như 5G thì có thể khó cảm nhận khác biệt, nhưng đây vẫn là một bước tiến đáng giá
    Việc có một cấu trúc handshake liên kết riêng là điều khá thú vị
    Trước đây tôi cứ nghĩ QUIC tự nhúng luôn TLS bên trong, hóa ra không hẳn như vậy
  • Lý do chính khiến web chậm đi là vì website ngày càng nặng hơn
    Dù vậy, tôi nghĩ công nghệ này có thể giúp giảm thêm latency cho game
    • Nghịch lý Jevons cũng áp dụng ở đây
      Khi tài nguyên tính toán và hiệu quả mạng tăng lên thì chính nhu cầu cũng tăng theo
      Với game hay tính toán khoa học thì điều đó không sao vì đổi lại là “kết quả tốt hơn”
      Nhưng trên web thì thường phản tác dụng vì quảng cáo, theo dõi và JavaScript ngày càng nhiều
  • Bài viết nói rằng QUIC cũng thiết lập connection bằng bind(), connect(), listen(), accept() như TCP, nhưng sau đó cấu trúc thay đổi sang dùng syscall sendmsg()recvmsg()
    Tôi ước gì cũng có giải thích vì sao lại chọn cách tiếp cận này, thay vì tạo ra các system call riêng phù hợp hơn với QUIC