Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements a dual sender Zerocopy strategy for QUIC V1 and VReverso #5

Open
wants to merge 17 commits into
base: protocol_reverso
Choose a base branch
from

Conversation

frochet
Copy link
Owner

@frochet frochet commented Dec 20, 2024

This PR adds a dual zerocopy sending optimization capability to both QUIC V1 and QUIC VReverso. There are two optimizations which can be used independently, with pros and cons.

The first implementation integrates quiche's opened PR cloudflare/quiche#1673 to quiceh.

This code optimizes out a copy happening in the stream_send() API call. To benefit from it, the Application has to use the new function stream_send_zc

     pub fn stream_send_zc(
         &mut self, stream_id: u64, buf: F::Buf, len: Option<usize>, fin: bool,
     ) -> Result<(usize, Option<F::Buf>)>
     where
         F::Buf: BufSplit,

With F implementing the trait BufFactory, and Buf implementing BufSplit:

/// A trait for providing internal storage buffers for [`RangeBuf`].
/// The associated type `Buf` can be any type that dereferences to
/// a slice, but should be fast to clone, eg. by wrapping it with an
/// [`Arc`].
pub trait BufFactory: Clone + Default + Debug {
    /// The type of the generated buffer.
    type Buf: Clone + Debug + AsRef<[u8]>;

    /// Generate a new buffer from a given slice, the buffer must contain the
    /// same data as the original slice.
    fn buf_from_slice(buf: &[u8]) -> Self::Buf;
}

/// A trait that enables zero-copy sends to quiche. When buffers produced
/// by the `BufFactory` implement this trait, quiche and h3 can supply the
/// raw buffers to be sent, instead of slices that must be copied first.
pub trait BufSplit {
    /// Split the buffer at a given point, after the split the old buffer
    /// must only contain the first `at` bytes, while the newly produced
    /// buffer must containt the remaining bytes.
    fn split_at(&mut self, at: usize) -> Self;
}

The second optimization uses BoringSSL's scatter encryption to assemble a packet containing Stream data and QUIC control frames without copying the Stream data. The code behaves slightly differently depending on whether this is a QUIC V1 connection or QUIC VReverso connection.

On QUIC V1:

The control information is written inside the destination buffer, with the Stream header frame being the last control. The stream data is the extra_in. The scatter encryption encrypts and glue together control and data. The stream Frame is then always the last frame of the packet.

On QUIC VReverso:

The control information is written inside the destination buffer, at its expected offset. The first bytes of the control information holds the reversed Stream frame header, also called data footer in VReverso. The in_buf data is a pointer to the Stream Frame data. The encryption is a partial aliasing encrypting the stream Frame data into the destination with in-place encryption of the controls as an extra_in. The result holds a packet for which the reversed stream frame is always the first frame, which is a necessary condition to have contiguous zero-copy on the receiver in VReverso.

For both QUIC V1 and QUIC VReverso, this capability is controlled by a Config option, and is global to all streams.

config.enable_hidden_copy_for_zc_sender(true);

Enabling this option performs scatter encryption without distinction to the usage of stream_send() or stream_send_zc().

This option has however the consequence to make the packet trace different, and more dependent on Application behavior. For example, assume we send two images each of 1500 bytes within the same stream using stream_send_zc(). If this option is disabled, the network will see 3 packets flying (two large ones, and a last one the size of the remaining bytes). If this option is enabled, the network will see 4 QUIC packets flying, 2 for each image. 1 large, 1 small, 1 large, 1 small.

The demonstration HTTP/3 client server supports now sending HTTP/3 response in zero-copy, internally using stream_send_zc(). An option is further added to the CLI to enable the scatter encryption using flag --enable-hidden-copy

We recommend enabling this option typically if large application objects are sent, and to avoid it in case many small independent objects are flushed into the same stream. The impact such an optimization may have on website fingerprinting remains to be studied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants