#firefox #quic #ietf #mozilla #stream #server #http-3

mozilla/neqo-http3

Neqo, an implementation of QUIC written in Rust

30 releases (5 breaking)

0.10.0 Oct 24, 2024
0.9.0 Sep 4, 2024
0.8.1 Jul 22, 2024
0.7.2 Mar 13, 2024
0.5.6 Dec 3, 2021

#13 in #ietf

1,847 stars & 37 watchers

MIT/Apache

740KB
16K SLoC

The HTTP/3 protocol

This crate implements RFC9114.

The implementation depends on:

Features

Both client and server-side HTTP/3 protocols are implemented, although the server-side implementation is not meant to be used in production and its only purpose is to facilitate testing of the client-side code.

WebTransport (draft version 2) is supported and can be enabled using Http3Parameters.

Interaction with an application

Driving HTTP/3 session

The crate does not create an OS level UDP socket, it produces, i.e. encodes, data that should be sent as a payload in a UDP packet and consumes data received on the UDP socket. For example, std::net::UdpSocket or mio::net::UdpSocket could be used for creating UDP sockets.

The application is responsible for creating a socket, polling the socket, and sending and receiving data from the socket.

In addition to receiving data HTTP/3 session’s actions may be triggered when a certain amount of time passes, e.g. after a certain amount of time data may be considered lost and should be retransmitted, packet pacing requires a timer, etc. The implementation does not use timers, but instead informs the application when processing needs to be triggered.

The core functions for driving HTTP/3 sessions are:

  • On the client-side :
  • process_output used for producing UDP payload. If a payload is not produced this function returns a callback time, e.g. the time when process_output should be called again.
  • process_input used consuming UDP payload.
  • process combines the 2 functions into one, i.e. it consumes UDP payload if available and produces some UDP payload to be sent or returns a callback time.
  • On the server-side only process is available.

An example interaction with a socket:

let socket = match UdpSocket::bind(local_addr) {
Err(e) => {
eprintln!("Unable to bind UDP socket: {}", e);
}
Ok(s) => s,
};
let mut client = Http3Client::new(...);

...

// process_output can return 3 values, data to be sent, time duration when process_output should
// be called, and None when Http3Client is done.
match client.process_output(Instant::now()) {
Output::Datagram(dgram) => {
// Send dgram on a socket.
socket.send_to(&dgram[..], dgram.destination())

}
Output::Callback(duration) => {
// the client is idle for “duration”, set read timeout on the socket to this value and
// poll the socket for reading in the meantime.
socket.set_read_timeout(Some(duration)).unwrap();
}
Output::None => {
// client is done.
}
};

...

// Reading new data coming for the network.
match socket.recv_from(&mut buf[..]) {
Ok((sz, remote)) => {
let d = Datagram::new(remote, *local_addr, &buf[..sz]);
client.process_input(d, Instant::now());
}
Err(err) => {
eprintln!("UDP error: {}", err);
}
}

HTTP/3 session events

Http3Client and Http3Server produce events that can be obtain by calling next_event. The events are of type Http3ClientEvent and Http3ServerEvent respectively. They are informing the application when the connection changes state, when new data is received on a stream, etc.

...

while let Some(event) = client.next_event() {
match event {
Http3ClientEvent::DataReadable { stream_id } => {
println!("New data available on stream {}", stream_id);
}
Http3ClientEvent::StateChange(Http3State::Connected) => {
println!("Http3 session is in state Connected now");
}
_ => {
println!("Unhandled event {:?}", event);
}
}
}

Dependencies

~4–5.5MB
~95K SLoC