LSQUIC Documentation

repository·master·Indexed 23 days ago

https://github.com/litespeedtech/lsquic

An open-source implementation of QUIC and HTTP/3 protocols for client and server use cases, serving as the core technology for LiteSpeed Web Server. It supports RFC 9000, 9001, 9002, 9114, and 9204, along with extensions like WebTransport and Unreliable Datagrams. The library includes various CLI testing tools such as http_client, echo_client, echo_server, duck_client, and duck_server.

Tokens
36.1K
Snippets
55
Records
176
Agent score
81%

What's inside LSQUIC

  1. Overview of LSQUIC functionality and compliance

    master

    LiteSpeed QUIC (LSQUIC) is an open-source implementation of QUIC and HTTP/3 for both servers and clients.

    Supported QUIC Versions:

    • Enabled by default: v1, v2, and Internet-Draft version 29.
    • Supported (but not enabled by default): ID-27, Q043, Q046, and Q050.

    Standard Compliance:

    • RFC 9000 (QUIC Transport)
    • RFC 9001 (TLS for QUIC)
    • RFC 9002 (Loss Detection and Congestion Control)
    • RFC 9114 (HTTP/3)
    • RFC 9204 (QPACK)

    Implemented Extensions:

    • RFC 9368 (Compatible Version Negotiation)
    • RFC 9369 (QUIC Version 2)
    • RFC 9218 (Extensible Prioritization Scheme)
    • RFC 9221 (Unreliable Datagram Extension)
    • RFC 9287 (Greasing the QUIC Bit)
    • ACK Frequency
    • WebTransport
  2. Overview of LSQUIC features and protocol support

    master

    LSQUIC is an open-source implementation of QUIC and HTTP/3 functionality. It supports multiple QUIC versions, with v1, v2, and Internet-Draft version 29 enabled by default. Older or deprecated versions (ID-27, Q043, Q046, Q050) are supported but must be explicitly enabled.

    Core Features:

    • DPLPMTUD (Datagram Packet Loss Detection and MTU Discovery)
    • ECN (Explicit Congestion Notification)
    • Spin bits (for RTT calculation by network observers)
    • Path migration and NAT rebinding
    • TLS Key updates
    • Delayed ACKs (to improve throughput by reducing ACK frame frequency)
    • QUIC grease bit (to prevent protocol ossification)

    Supported Extensions:

    • Extensible HTTP priorities
    • Datagrams
    • Loss bits extension
    • Timestamps extension (for one-way delay calculation)
  3. Congestion Control (CC) modes in LSQUIC

    master

    The Send Controller supports two congestion controllers:

    1. Cubic
    2. BBRv1 (translated from Chromium)

    Adaptive CC Mode: By default, LSQUIC uses "adaptive CC" mode. The controller is selected after the Round Trip Time (RTT) is determined:

    • RTT < 1.5 ms (default threshold): Uses Cubic.
    • RTT >= 1.5 ms: Uses BBRv1.

    Note: Until the RTT is determined, both controllers are instantiated to ensure state is ready for the decision.

  4. How Poisoned Packets prevent ACK attacks

    master

    To thwart opportunistic ACK attacks (where a client tricks a server into sending data faster by sending ACKs for packets it hasn't actually seen), LSQUIC uses Poisoned Packets.

    • A poisoned packet is placed on the Unacked Queue with a deliberate gap in the packet number sequence.
    • If the peer lies and acknowledges the poisoned packet, the discrepancy is discovered during ACK processing.
    • For simplicity, a maximum of one poisoned packet is outstanding at any time.
  5. Use lsquic_set64 to track increasing numbers

    master

    The lsquic_set64 data structure is designed to track a set of numbers that are monotonically increasing and are not expected to contain many gaps.

    Common Use Case:

    • Stream IDs: It is used in both gQUIC and IETF QUIC full connections to manage Stream IDs.

    Implementation Note: Because the lower bits of Stream IDs often indicate the stream type, different stream types are stored in separate lsquic_set64 instances to prevent gaps from appearing in the set.

  6. Understand frame records in outgoing packets

    master

    Each frame written to the po_data buffer of an outgoing packet has an associated frame record stored in po_frecs.

    Frame records serve two primary purposes:

    1. Tracking Acknowledgments: They track the number of unacknowledged stream frames for a stream. When a packet is acknowledged, the frame records are iterated over to call lsquic_stream_acked().
    2. Packet Resizing: They record the type, position, and size of a frame to speed up resizing operations.

    In optimized use cases where a packet contains only a single frame (such as a single STREAM frame from a sender or a single ACK frame from a receiver), the frame record is stored directly in the packet struct via a union to save space.

    struct frame_rec {
        union {
            struct lsquic_stream   *stream;
            uintptr_t               data;
        }                        fe_u;
        unsigned short           fe_off, 
                                     fe_len;
        enum quic_frame_type     fe_frame_type;
    };
  7. Understand packet batching and coalescing

    master

    LSQUIC batches outgoing packets to improve efficiency. The send_packets_out function iterates over connections in the Outgoing queue, encrypts their packets, and adds them to an out_batch.

    Key Batching Concepts:

    • Fairness: Packets from different connections are interleaved in the batch (e.g., A1, B1, C1, A2, B2, C2) to ensure no single connection starves others.
    • Packet Coalescing: For IETF QUIC connections during the handshake, multiple packets can be coalesced into a single UDP datagram to reduce overhead. This is controlled by the ENG_COALESCE flag.
    • Batch Resizing: The batch size is dynamic. It grows (up to MAX_OUT_BATCH_SIZE) if all datagrams are sent successfully, and shrinks if not.
    • User Callback: When a batch is full, it is passed to the user-supplied callback. The user must call ci_packet_sent for successful packets or ci_packet_not_sent for failed ones.
  8. Understanding LSQUIC Streams

    master

    An lsquic_stream is the primary conduit for data in the library. It is a bidirectional object that represents the data flow for a specific request/response pair (e.g., an HTTP request from a client and its corresponding response from a server).

    Key Characteristics:

    • Bidirectional: Both clients and servers use the same stream object to read and write data.
    • Abstraction: The stream abstracts away the underlying QUIC protocol details. Whether using gQUIC (HANDSHAKE/HEADERS streams) or IETF QUIC (up to four HANDSHAKE streams), or HTTP/3 (Settings, QPACK encoder/decoder streams), the user interacts with the stream via the same lsquic_stream_* API.
    • Lifecycle: Streams are managed by a full connection and are identified by a unique id used for hashing.
    • Event-Driven: Streams use "on stream read" and "on stream write" callbacks. Users register interest in reading or writing, and the library dispatches these events when possible.
  9. How Stream Read and Write Events are Dispatched

    master

    LSQUIC uses an event-driven model for stream I/O. To receive notifications when a stream is ready for I/O, you must use the following mechanism:

    1. Register Interest: Call lsquic_stream_wantwrite or lsquic_stream_wantread. This places the stream on the corresponding "want to write" or "want to read" list.
    2. Connection Tick: When the connection is "ticked" (processed), it iterates through these lists.
    3. Dispatch: The connection calls internal dispatch functions (lsquic_stream_dispatch_read_events or lsquic_stream_dispatch_write_events) which trigger your registered user callbacks.

    Read Dispatch Behavior:

    • If es_rw_once is set, the "on stream read" callback is called exactly once if the stream is readable.
    • Otherwise, the callback is called in a loop as long as the stream is readable, the user still wants to read, and progress is being made.

    Write Dispatch Behavior:

    • The "on stream write" callback is called, and the library also utilizes a flushing mechanism via the "want to write" list.
  10. How IETF mini connection handles packet number history

    master

    Because IETF QUIC clients can start packet number sequences at any value in the $[0, 2^{32}-1]$ range, the mini connection must handle packet numbers larger than the 64-bit capacity of a standard bitmask.

    To manage this, the connection uses a union for received packet history:

    1. Bitmask Mode: If all received packet numbers are $\le 63$, the packno_set_t bitmask is used.
    2. Trechist Mode: If a packet number exceeds 63, the connection switches to Tiny Receive History (trechist). This transition is handled by imico_switch_to_trechist().

    Note: For testing purposes, lsquic_mini_conn_ietf_new() is implemented to use trechist unconditionally in approximately 1 in every 16 mini connections.

  11. Writing Data to an LSQUIC Stream

    master

    Writing to a stream is handled by several user-facing functions (wrappers around stream_write).

    Buffering and Packetization:

    • Small Writes: Small amounts of data are buffered in sm_buf.
    • Thresholds: If the buffered data plus the new write exceeds a specific threshold (the size of the largest STREAM frame that fits in a single packet), the data is immediately packetized.
    • Efficiency: This thresholding prevents "jagged" STREAM frames, ensuring bandwidth is used effectively by allowing larger, more efficient frames.
    • Flushing: When explicitly flushing data, the threshold is ignored, and even a 1-byte write will trigger packetization.

    HTTP/3 Specifics: In HTTP/3 mode, framing (HEADERS and DATA frames) is added transparently. The user code does not need to manually manage HTTP/3 frame headers; the library generates them on-the-fly during the packetization process.

  12. Understand Frame Types and Varint Encoding

    master

    Frame types are managed via the enum quic_frame_type. This abstraction allows the library to map on-the-wire frame types to internal enums and use bitmasks (e.g., po_frame_types, sc_retx_frames) for efficient tracking.

    IETF QUIC Varint Encoding

    In IETF QUIC, frame types are encoded as varints and must use the minimal representation (the minimum number of bytes possible). For example, the value 200 must be encoded as a two-byte varint rather than a four- or eight-byte version.

    Because of this minimal encoding requirement, parsing routines like ietf_v1_parse_frame_type() can determine exactly how many bytes to skip to reach the frame payload, allowing the library to parse the type once and then pass the remaining buffer to specific frame-parsing routines.