English | 中文
The QUIC protocol is an important infrastructure for the next generation Internet, and gm-quic
is a native asynchronous Rust implementation of the QUIC protocol, an efficient and scalable RFC 9000 implementation with excellent engineering quality. The implementation of gm-quic
tries its best to maintain the original concepts of RFC 9000, including variable and structure naming, and strives to be consistent with RFC 9000, so gm-quic
is also of great learning value , RFC 9000 and its related RFCs are excellent introductory documents for understanding gm-quic
.
The QUIC protocol is a rather complex, IO-intensive protocol, making it extremely fit for asynchronous programming.
The basic events in asynchronous IO are read, write, and timers. However, throughout the implementation of the QUIC protocol, the internal events are intricate and dazzling.
If you look at the protocol carefully, you will found that certain structures become evident, revealing that the core of the QUIC protocol is driven by layers of underlying IO events progressively influencing the application layer behavior.
For example, when the receiving data of a stream is contiguous, it constitutes an event that awakens the corresponding application
layer to read;
similarly, when the Initial data exchange completes and the Handshake keys are obtained, this is another event that awakens the task processing the Handshake data packet.
These events illustrate the classic Reactor pattern.
gm-quic
refines and encapsulates these various internal Reactors of QUIC, making each module more independent, clarifying the cooperation between the system's modules, and thereby making the overall design more user-friendly.
It is noticeable that the QUIC protocol has multiple layers. In the transport layer, there are many functions such as opening new connections, receiving, sending, reading, writing, and accepting new connections, most of which are asynchronous.
Here, we call these functions as various functors with each layer having its own functor.
With these layers in place, it becomes clear that the Accept Functor
and the Read Functor
, or the Write Functor
, do not belong to the same layer, which is quite interesting.
- qbase: Core structure of the QUIC protocol, including variable integer encoding (VarInt), connection ID management, stream ID, various frame and packet type definitions, and asynchronous keys.
- qrecovery: The reliable transport part of QUIC, encompassing the state machine evolution of the sender/receiver, and the internal logic interaction between the application layer and the transport layer.
- qcongestion: Congestion control in QUIC, which abstracts a unified congestion control interface and implements BBRv1. In the future, it will also implement more transport control algorithms such as Cubic and others.
- qconnection: Encapsulation of QUIC connections, linking the necessary components and tasks within a QUIC connection to ensure smooth operation.
- quic: The top-level encapsulation of the QUIC protocol, including interfaces for both the QUIC client and server.
- qudp: High-performance UDP encapsulation for QUIC. Ordinary UDP incurs a system call for each packet sent or received, resulting in poor performance. qudp optimizes UDP performance to the extreme using techniques like GSO (Generic Segmentation Offload) and GRO (Generic Receive Offload). The performance test results for sending are as follows:
> # sendmmsg with gso
> strace -c -e trace=%net ../target/debug/examples/sender --gso
sent 1200000000 bytes
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.191681 660 290 sendmmsg
0.00 0.000002 0 3 getsockname
0.00 0.000001 0 2 getsockopt
0.00 0.000000 0 3 socket
0.00 0.000000 0 3 bind
0.00 0.000000 0 1 socketpair
0.00 0.000000 0 11 2 setsockopt
------ ----------- ----------- --------- --------- ----------------
100.00 0.191684 612 313 2 total
> # sendmmsg without gso
> strace -c -e trace=%net ../target/debug/examples/sender
sent 1200000000 bytes
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 5.670731 362 15625 sendmmsg
0.00 0.000118 10 11 2 setsockopt
0.00 0.000046 15 3 socket
0.00 0.000028 9 3 bind
0.00 0.000016 5 3 getsockname
0.00 0.000014 14 1 socketpair
0.00 0.000008 4 2 getsockopt
------ ----------- ----------- --------- --------- ----------------
100.00 5.670961 362 15648 2 total
gm-quic
provides user-friendly interfaces for creating client and server connections, while also supporting additional features that meet modern network requirements.
The QUIC client not only offers configuration options specified by the QUIC protocol's Parameters but also includes additional options such as reuse_connection
and enable_happy_eyeballs
enabling the IPv6-preferred Happy Eyeballs algorithm. More advanced features allow the QUIC client to set its own certificates as its ID for server verification and manage the Tokens issued by servers for future connections with these servers.
let quic_client = QuicClient::builder()
.reuse_connection()
// The QUIC version negotiation mechanism prioritizes using the earlier versions,
// currently only supporting V1.
.prefer_versions([1u32])
// .with_parameter(&client_parameters) // If not set, the default parameters will be used
// .with_token_sink(token_sink) // Manage Tokens issued by various servers
.with_root_certificates(root_certificates)
// .with_webpki_verifier(verifier) // More advanced ways to verify server certificates
.without_cert() // Generally, clients do not need to set certificates
.build();
let quic_client_conn = quic_client
.connect("localhost", "127.0.0.1:5000".parse().unwrap())
.unwrap();
The QUIC server provides SNI(Server Name Indication) support in TLS, allowing the configuration of multiple server names and certificates. Additionally, gm-quic
provides a custom load balancing interface that let developers determine how to return Retry packets based on the Initial packet of a new connection. This interface leverages the inherent features of QUIC to schedule across multiple hosts balancely.
let quic_server = QuicServer::builder()
.with_supported_versions([1u32])
// for load balancing
// .with_load_balance(move |initial_packet: &InitialPacket| -> Option<RetryPacket> {
// ...
// })
.without_cert_verifier() // Generally, client identity is not verified
.enable_sni()
.add_host("www.genmeta.net", www_cert, www_key)
.add_host("developer.genmeta.net", dev_cert, dev_key)
.listen(&[
"[2001:db8::1]:8080".parse().unwrap(),
"127.0.0.1:8080".parse().unwrap(),
][..]);
while let Ok(quic_server_conn) = quic_server.accept().await? {
// The following is a demonstration
tokio::spawn(handle_quic_conn(quic_server_conn));
}
There is an asynchronous interface for creating unidirectional or bidirectional QUIC streams from a QUIC Connection, or for listening to incoming streams from the other side of a QUIC Connection. This interface is almost identical to the one in hyperium/h3
.
We also implement the interface defined by hyperium/h3
in h3-shim
crate to facilitate with other crates integrated. We have a frok of reqwest
that use gm-quic
as the transport layer, you can find it here.
As for reading and writing data from a QUIC stream, the tokio's AsyncRead
and AsyncWrite
interfaces are implemented for QUIC streams, making it very convenient to use.
gm-quic
has entered the final testing phase. Next, we will further improve the documentation and add the qlog module. Stay tuned.
While gm-quic
is not yet complete, its documentation will not be uploaded to crate.io
.
Please refer to the documentation within the code for now!
All feedback and PRs are welcome, including bug reports, feature requests, documentation improvements, code refactoring, and more.
However, please note that gm-quic
has extremely high-quality standards for both code and documentation.
Contributions will undergo rigorous review before merging.
Contributors are kindly asked to understand and patiently address all feedback before the merge can be completed.
If you are unsure whether a feature or its implementation is reasonable, please first create an issue in the issue list for discussion. This ensures the feature is reasonable and has a solid implementation plan.
- Official Community
- chat group:send email to introduce your contribution, and we will reply to your email with an invitation link and QR code to join the group.