1 điểm bởi GN⁺ 2025-09-28 | 1 bình luận | Chia sẻ qua WhatsApp
  • Khoảng 20% lưu lượng HTTP của Firefox sử dụng HTTP/3, vốn hoạt động trên QUIC và UDP
  • Bằng cách thay thế lớp I/O mạng hiện có là NSPR bằng quinn-udp dựa trên Rust, Firefox tăng cường hiệu năng và độ an toàn bộ nhớ
  • Firefox chủ động tận dụng các system call hiện đại theo từng hệ điều hành (multi-message, segmentation offloading, v.v.) để tối ưu hiệu năng
  • Trên WindowsMacOS, một số tính năng bị giới hạn do vấn đề tương thích, driver và các yếu tố khác, nhưng trên Linux đã xác nhận được hiệu năng tối ưu
  • Những kinh nghiệm thử nghiệm, vấp váp và sửa lỗi liên quan đến hỗ trợ QUIC ECN và UDP I/O trên nhiều nền tảng được kỳ vọng sẽ hữu ích cho các dự án tương lai và hệ sinh thái mã nguồn mở

Động lực

  • Khoảng 20% lưu lượng HTTP của Firefox sử dụng HTTP/3, công nghệ này hoạt động thông qua QUIC, và được triển khai trên UDP
  • Về mặt lịch sử, Firefox dùng thư viện NSPR cho I/O mạng, nhưng các tính năng liên quan đến UDP I/O đã cũ và hạn chế (các hàm chính là PR_SendTo, PR_RecvFrom)
  • Gần đây, các hệ điều hành đã cung cấp các system call đa thông điệp (ví dụ sendmmsg, recvmmsg) và các tối ưu mạng như segmentation offload (GSO, GRO)
  • Những kỹ thuật này có thể cải thiện đáng kể hiệu năng của UDP I/O
  • Firefox đã tìm cách xem liệu có thể thay thế stack UDP I/O hiện có bằng các system call hiện đại để tận dụng các lợi ích này hay không

Tổng quan

  • Dự án bắt đầu vào giữa năm 2024, với mục tiêu xây dựng lại stack QUIC UDP I/O của Firefox bằng các system call hiện đại trên mọi hệ điều hành được hỗ trợ
  • Bên cạnh cải thiện hiệu năng, dự án cũng hướng tới tăng cường bảo mật bằng cách sử dụng Rust bảo đảm an toàn bộ nhớ cho UDP I/O
  • Bản thân QUIC đã được triển khai bằng Rust, nên việc phát triển được thực hiện trên thư viện quinn-udp dựa trên Rust
  • Sự khác biệt về system call giữa các hệ điều hành làm tăng độ khó phát triển, nhưng nhờ quinn-udp mà tốc độ phát triển được cải thiện đáng kể
  • Tính đến giữa năm 2025, việc triển khai đang được áp dụng cho phần lớn người dùng Firefox, và benchmark hiệu năng cho thấy mức tăng lớn, lên tới 4Gbit/s

Cấu trúc UDP I/O và các phương thức tối ưu hiện đại

Truyền một datagram đơn

  • Cách làm cũ dùng sendtorecvfrom, chỉ gửi và nhận một UDP datagram mỗi lần
  • Chi phí chuyển đổi giữa user space và kernel space phải trả theo từng datagram, nên trong môi trường lưu lượng lớn là kém hiệu quả
  • Ví dụ: để gửi các gói nhỏ hơn 1500 byte ở mức hàng trăm Mbit/s mỗi giây sẽ phát sinh overhead đáng kể

Truyền theo lô nhiều datagram

  • Linux và một số hệ điều hành khác hỗ trợ các system call đa thông điệp như sendmmsgrecvmmsg
  • Có thể gửi và nhận nhiều datagram trong một lần, từ đó giảm mạnh overhead

Một datagram phân đoạn cỡ lớn duy nhất

  • Các công nghệ offload như GSO (gửi), GRO (nhận) cho phép tự động chia một UDP datagram lớn thành nhiều phần tại hệ điều hành hoặc NIC để truyền đi
  • Giao diện mạng xử lý việc tách theo từng gói, tính checksum và gắn header
  • Nhờ đó, phía ứng dụng có thể xử lý nhiều gói thực tế chỉ với một system call duy nhất
  • Khi bật GSO, một số công cụ mạng như Wireshark hỗ trợ phân tích gói chưa tốt

Quá trình thay thế NSPR trong Firefox

  • Trước tiên, Firefox thay thế NSPR bằng quinn-udp trong cấu trúc gửi và nhận datagram đơn
  • Pipeline xử lý datagram của triển khai QUIC được refactor để hỗ trợ gửi/nhận theo lô và segmentation
  • Cả lời gọi multi-message lẫn segmentation offload đều được sử dụng tùy theo tình huống
  • Đồng thời bổ sung xử lý ngoại lệ theo từng nền tảng và nhiều cải tiến I/O khác nhau

Chi tiết theo từng nền tảng

Windows

  • Windows cung cấp WSASendMsg/WSARecvMsg, hỗ trợ datagram theo kích thước MTU truyền thống hoặc datagram phân đoạn cỡ lớn
  • Tương ứng với GSO/GRO trên Linux là USO (gửi) / URO (nhận) trên Windows
  • Ban đầu chỉ dùng lời gọi datagram đơn nên không có vấn đề, nhưng khi bật URO thì ở một số môi trường cụ thể (ví dụ Windows on ARM + WSL) đã xuất hiện lỗi tải trang thất bại do không thể xác định độ dài gói QUIC
  • Firefox cũng đã dùng USO/gửi, nhưng phát hiện tác dụng phụ như tăng mất gói và crash driver mạng trong môi trường cài đặt Firefox trên Windows
  • Hiện tại Firefox đang giữ URO/USO ở trạng thái vô hiệu hóa và tiếp tục debug thêm

MacOS

  • Trên MacOS, Firefox đưa quinn-udp vào bằng cách dùng sendmsg/recvmsg thay cho sendto/recvfrom
  • Tính năng segmentation offload không được bật
  • Thay vào đó, sendmsg_x/recvmsg_x không được tài liệu hóa chính thức hỗ trợ truyền theo lô, và đã được áp dụng không chính thức vào quinn-udp
  • Do Apple có thể loại bỏ các lời gọi này trong tương lai, tính năng này không được bật mặc định, chỉ thử nghiệm và không được đưa vào bản phát hành thực tế

Linux

  • Hỗ trợ đầy đủ sendmmsg/recvmmsgGSO/GRO, và quinn-udp mặc định ưu tiên GSO khi gửi
  • Firefox dùng socket UDP riêng cho từng kết nối để tăng cường quyền riêng tư (phân biệt theo 4-tuple)
  • Trong cấu trúc này, lợi ích của segmentation offload được tối đa hóa, còn lợi ích truyền chéo của sendmmsg/recvmmsg thì bị hạn chế
  • Ngoài các thay đổi nhỏ như sandbox mạng và kiểm tra hỗ trợ GSO lúc chạy, việc đưa vào đã thành công mà không gặp khó khăn lớn

Android

  • Khác với Linux, Android có đường xử lý system call và bộ lọc bảo mật (ví dụ: seccomp) khác biệt
  • Tồn tại nhiều vấn đề tương thích như hỗ trợ các nền tảng rất cũ như Android 5 dựa trên x86, обход socketcall, xử lý lỗi, v.v.
  • Trong một số môi trường, lời gọi gửi khi bật bit ECN gây lỗi (EINVAL), nên quinn-udp áp dụng chiến lược thử lại và tắt tùy chọn
  • Nhờ nhiều cải tiến khác nhau từ cộng đồng Quinn, Firefox cũng có thể tự động hưởng lợi từ các cải tiến đó

Hỗ trợ ECN (Explicit Congestion Notification)

  • Nhờ áp dụng các system call hiện đại, việc gửi và nhận ancillary data (dữ liệu phụ trợ) được hỗ trợ, từ đó có thể hỗ trợ QUIC ECN
  • Dù có một số lỗi nhỏ, trên Firefox Nightly hơn một nửa các kết nối QUIC hoạt động qua đường outbound ECN
  • Khi các công nghệ mới như L4S ngày càng được chú ý, tầm quan trọng và khả năng ứng dụng của hỗ trợ ECN cũng tăng lên

Tóm tắt kết luận

  • Firefox đã thay thế lớp QUIC UDP I/O bằng triển khai Rust dựa trên quinn-udp, đồng thời đạt được cả hiệu năng lẫn bảo mật
  • Thay vì các system call cũ, Firefox tận dụng các system call I/O hiện đại theo từng hệ điều hành, qua đó cải thiện thông lượng và hỗ trợ ECN
  • Một số tính năng tối ưu trên Windows và các nền tảng khác vẫn cần cải thiện thêm do vấn đề tương thích
  • Khi tỷ lệ sử dụng QUIC tiếp tục tăng, hỗ trợ ở cấp hệ điều hành/driver cũng sẽ tiếp tục phát triển trong tương lai

1 bình luận

 
GN⁺ 2025-09-28
Ý kiến trên Hacker News
  • Ý chính của bài lại nằm ẩn ở giữa

    Trong trường hợp cực đoan, ở benchmark bị giới hạn bởi CPU, hiệu năng đã tăng từ < 1Gbit/s lên 4 Gbit/s. Trên flamegraph CPU, phần lớn thời gian bị tiêu tốn ở các system call I/O và mã mã hóa
    Việc thông lượng mạng tăng 400% có nghĩa là mức sử dụng CPU cho lưu lượng mạng UDP đã giảm đi đáng kể
    Điều này đặc biệt ấn tượng ở khía cạnh cải thiện hiệu quả năng lượng trên các client di động như điện thoại và laptop
    Thường mọi người hay mặc định những chuyển đổi kiểu này lúc nào cũng tốt, nhưng bài này lại chứng minh bằng dữ liệu thực tế nên thấy khá mới mẻ

    • Tò mò không biết rồi sẽ có ngày message passing băng qua ngữ cảnh giữa chương trình người dùng và chương trình hệ thống được tăng tốc bằng phần cứng hay không
  • Cải tiến ở đây đúng là cần thiết để đạt tốc độ cực cao thực sự (trên 100Gb/s), nhưng 4Gb/s thì thật ra không nhanh lắm
    500MB/s nghĩa là ở đâu đó đang có một nút thắt cổ chai nghiêm trọng
    Chuyển ngữ cảnh kernel được nói là ở mức 1us, mà thật ra với system call thì khá cao
    Tuy vậy, chỉ cần trung bình mỗi gói cỡ 500 byte là đã có thể đạt 500MB/s, tức 4Gb/s
    Mức 1Gb/s trước đây là khi kích thước gói nhỏ hơn, và việc chỉ đẩy gói UDP vào buffer của NIC thì ngay cả tốc độ sao chép bộ nhớ cũng dư sức làm được
    Dù nói mã hóa chậm, thực tế lại không hẳn như vậy
    Ví dụ, Intel i5-6500 từng đạt tốc độ AES-128 GCM là 1729MB/s
    CPU hiện nay có thể đạt 3-5GB/s mỗi lõi, tức 25-40Gb/s, nên mức 4Gb/s được nói ở đây là quá thấp
    (liên kết tham khảo hiệu năng AES-128 GCM)

    • Có nói độ trễ system call cao, nhưng nguyên nhân có thể là do các biện pháp giảm thiểu spectre & meltdown
      TCP có path binding, còn UDP thì không, nên có khác biệt lớn trong cách thiết lập đường đi
      Nói mã hóa chậm thì đúng với PDU (đơn vị dữ liệu giao thức) nhỏ
      Phần lớn tối ưu hóa và benchmark đều dựa trên khung TCP dung lượng lớn, nên trong thực tế với gói nhỏ thì chi phí thiết lập trạng thái nổi bật hơn nhiều
      Nếu chạy microbenchmark trong tight loop thì số liệu sẽ đẹp, nhưng trong môi trường thực có tính ngẫu nhiên thì hiệu quả tận dụng cache cũng thấp hơn, và với gói dưới 1KB thì hiệu suất tụt mạnh
      Thêm vào đó còn có overhead framing bổ sung, kiểm tra tính hợp lệ của dữ liệu out-of-band, v.v., đều khá tốn kém
      Bộ nhớ buffer UDP mặc định cũng thiếu, gây nhiều vấn đề khi dùng thực tế
      Người ta đã liên tục tăng kích thước buffer khi vận hành TCP, còn UDP thì vẫn kẹt ở các giá trị bảo thủ từ thập niên 90~00
      API thực sự cần thiết là fork fd, hỗ trợ đầy đủ connect(2) và route binding, rồi sau đó là dựa trên submission queue (uring, rio, v.v.)
      Về mặt mã hóa, cách tiếp cận KDF có thể giảm đáng kể chi phí trạng thái
      Cách làm PSP được một số vendor công nhận nhưng bị từ chối khá nhiều ở IETF và nơi khác nên không phổ biến rộng rãi
      Trong các bài kiểm tra đồng thời quy mô lớn của vendor, nó cho thấy mức độ mở rộng vượt trội hơn hẳn các loại TLS truyền thống

    • Hoàn toàn không hề nhắc đến CPU dùng để benchmark thuộc phân khúc nào
      Và overhead mã hóa là chi phí xử lý của chính giao thức QUIC
      QUIC vẫn kém TCP về khả năng offload mã hóa (xử lý bằng phần cứng), trong khi TCP phần nào có thể giao cho NIC xử lý thông qua offload kTLS

  • Nội dung kỹ thuật kiểu này thật sự rất thỏa mãn
    Giá mà mọi tài liệu kỹ thuật của Mozilla đều sâu và chắc tay như thế này, do kỹ sư thực chiến viết bài bản
    Không có kiểu lạc quan sáo rỗng, rất đáng đọc

  • Không hiểu vì sao vẫn còn hỗ trợ Android 5
    Nó đã phát hành hơn 10 năm rồi, và người dùng thiết bị đó còn là dạng legacy cũ hơn nữa
    Web ngày nay quá nặng, nên ngay cả việc duyệt web bình thường trên mấy thiết bị cổ như vậy cũng đã khó, nên khá tò mò vì sao vẫn phải hỗ trợ
    Có lẽ chỉ là các hacker sửa lại mấy máy đời cũ kiểu OnePlus ngày xưa, cắm sạc cố định, không cài cả các bản ROM đại trà như LineageOS, rồi dùng Firefox qua kho ứng dụng thay thế
    Trên thực tế đây là cái giá làm chậm toàn bộ tốc độ phát triển

  • Đoạn nói rằng sau rất nhiều lần qua lại với người báo lỗi (cũng là nhân viên Mozilla), cuối cùng còn phải mua hẳn một chiếc laptop cùng mẫu, cùng màu chỉ để tái hiện lỗi
    Nó diễn tả cái hiện thực điên rồ của việc tái hiện lỗi mạng giống hệt XKCD 2259 nên rất buồn cười
    (liên kết tranh xkcd)

    • Cũng nên đọc một giai thoại thú vị liên quan trong blog phát triển Factorio có tựa đề "The map download struggle"
      (bài blog liên quan)

    • Ai từng xử lý vấn đề mạng ngoài đời thật sẽ còn đồng cảm hơn vì mấy mystery packet runts
      Phần lớn thiết bị mạng xử lý loại gói này rất kém
      Lưu lượng dựa trên UDP hoặc QUIC rất dễ bị tấn công nếu không phải ở môi trường cloud đủ lớn
      Vì thế các nhà cung cấp hosting nhỏ hoặc tự vận hành ngày càng khó sống, và chỉ còn những nơi có năng lực xử lý lưu lượng lớn tồn tại được
      Bởi vậy trong phần lớn môi trường LAN, người ta đang drop hầu hết lưu lượng UDP và chỉ xử lý phần cần thiết bằng rate limit

  • Trình theo dõi lỗi của Mozilla
    Trên macOS và Fedora, khi truy cập các trang do Cloudflare host bằng Firefox, tôi vẫn gặp hiện tượng y hệt

  • Hôm nay mới biết là trên Windows và macOS cũng có tính năng tương tự GSO/GRO (xử lý gói mạng kích thước lớn)
    Chỉ tiếc là nghe nói thực tế có rất nhiều lỗi

    • Không hiểu vì sao Microsoft và Apple lại không chú ý hơn một chút đến chất lượng stack mạng của họ
      Có cảm giác không chỉ riêng GSO/GRO mới có bug
  • Có ai giải thích được về mặt cấu trúc UDP GSO/GRO hoạt động như thế nào không?
    UDP là gói không có thứ tự, vậy khi một gói QUIC bị tách thành nhiều gói UDP mà trong header cũng không có thông tin thứ tự, thì phía nhận làm sao ghép lại đúng thứ tự?

    • Theo những gì tôi hiểu, ở cấp độ ứng dụng thì ngay cả khi GRO được bật, thực ra vẫn không nhận được các UDP datagram đã bị gộp lại
      Ý tưởng là kernel đặt nhiều datagram vào cùng một cấu trúc, nhưng vẫn giữ ranh giới giữa chúng qua các tầng khác nhau (ví dụ data fragments trong sk_buff)
      Tôi không phải chuyên gia chính xác về chuyện này, nhưng khi tìm hiểu cách nó hoạt động tôi đã tham khảo bài viết này
  • Có nhắc rằng "chúng tôi bắt đầu phát triển dựa trên quinn-udp, thư viện UDP I/O của dự án Quinn, nhờ đó tốc độ phát triển nhanh hơn rất nhiều"
    Vậy không biết họ có tài trợ cho dự án Quinn hay không
    (liên kết tài trợ Quinn)

    • Tôi đã hỏi trực tiếp về chuyện hỗ trợ tài chính, thì một Senior Principal Software Engineer của Mozilla trả lời rằng "Mozilla không có tiền"
      Nhưng họ đóng góp cực kỳ nhiều mã nguồn nên chúng tôi thật sự rất biết ơn
      (Tôi là maintainer chính của Quinn)

    • Với câu hỏi "Có tài trợ không?", có ý kiến cho rằng cách Mozilla làm là không cần tài trợ open source, mà thay vào đó chi thêm vài triệu USD cho lương CEO
      Trong khi ngay cả sản phẩm chủ lực (Firefox) cũng đang xuống dốc

    • Nếu họ có đóng góp theo cách khác như mã nguồn thì tôi cũng muốn biết

  • Việc sendmmsg/recvmmsg được gọi là “hiện đại” thật đáng ngạc nhiên
    Thật ra đây là các system call đã tồn tại khá lâu rồi
    Cũng hơi tiếc vì trong nội dung liên quan Linux lại không nhắc đến io_uring

    • io_uring không có khả năng batch thực sự để xử lý nhiều UDP datagram cùng lúc
      Cố lắm thì cũng chỉ là gửi yêu cầu nhiều sendmsg, recvmsg cùng lúc
      GSO/GRO mới là đáp án đúng
      sendmmsg/recvmmsg đã là công nghệ rất cũ, và trong số các kernel developer giờ còn có người muốn loại bỏ nó
      (thảo luận GitHub liên quan)