From eea998962aeb38ee92f1f8f346210cce116c0da8 Mon Sep 17 00:00:00 2001 From: Dmitri Tikhonov Date: Wed, 31 Mar 2021 09:38:32 -0400 Subject: [PATCH] Release 2.29.6 - Documentation: describe lsquic internals ("guts"). - Two more fixes to compliance issues found by h3spec. - Truncate, don't abort, SCIDs larger than 16 bytes (PR #244). - Several small internal improvements and space optimizations. --- CHANGELOG | 7 + bin/http_server.c | 19 +- docs/conf.py | 2 +- docs/index.rst | 2 +- docs/internals.rst | 3051 ++++++++++++++++++++++++- docs/lsquic-packet-queues.png | Bin 0 -> 25056 bytes docs/rechist-linked-list.png | Bin 0 -> 8854 bytes docs/rechist-memory-layout.png | Bin 0 -> 12524 bytes docs/stream-http3-framing.png | Bin 0 -> 22699 bytes include/lsquic.h | 2 +- src/liblsquic/lsquic_conn.c | 5 +- src/liblsquic/lsquic_engine.c | 4 +- src/liblsquic/lsquic_full_conn_ietf.c | 26 +- src/liblsquic/lsquic_mini_conn.c | 4 - src/liblsquic/lsquic_mini_conn_ietf.c | 19 +- src/liblsquic/lsquic_mini_conn_ietf.h | 1 - src/liblsquic/lsquic_packet_in.h | 11 +- src/liblsquic/lsquic_qdec_hdl.c | 24 +- tools/gen-tags.pl | 1 + 19 files changed, 3129 insertions(+), 49 deletions(-) create mode 100644 docs/lsquic-packet-queues.png create mode 100644 docs/rechist-linked-list.png create mode 100644 docs/rechist-memory-layout.png create mode 100644 docs/stream-http3-framing.png diff --git a/CHANGELOG b/CHANGELOG index bff5e0c..6daaf16 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +2021-03-31 + - 2.29.6 + - Documentation: describe lsquic internals ("guts"). + - Two more fixes to compliance issues found by h3spec. + - Truncate, don't abort, SCIDs larger than 16 bytes (PR #244). + - Several small internal improvements and space optimizations. + 2021-03-17 - 2.29.5 - Fix a few issues detected by h3spec for better compliance with HTTP/3 diff --git a/bin/http_server.c b/bin/http_server.c index f839de3..3c2cc07 100644 --- a/bin/http_server.c +++ b/bin/http_server.c @@ -418,6 +418,11 @@ struct req enum { HAVE_XHDR = 1 << 0, } flags; + enum { + PH_AUTHORITY = 1 << 0, + PH_METHOD = 1 << 1, + PH_PATH = 1 << 2, + } pseudo_headers; char *path; char *method_str; char *authority_str; @@ -1829,7 +1834,16 @@ interop_server_hset_add_header (void *hset_p, struct lsxpack_header *xhdr) unsigned name_len, value_len; if (!xhdr) - return 0; + { + if (req->pseudo_headers == (PH_AUTHORITY|PH_METHOD|PH_PATH)) + return 0; + else + { + LSQ_INFO("%s: missing some pseudo-headers: 0x%X", __func__, + req->pseudo_headers); + return 1; + } + } name = lsxpack_header_get_name(xhdr); value = lsxpack_header_get_value(xhdr); @@ -1856,6 +1870,7 @@ interop_server_hset_add_header (void *hset_p, struct lsxpack_header *xhdr) req->path = strndup(value, value_len); if (!req->path) return -1; + req->pseudo_headers |= PH_PATH; return 0; } @@ -1872,6 +1887,7 @@ interop_server_hset_add_header (void *hset_p, struct lsxpack_header *xhdr) req->method = POST; else req->method = UNSUPPORTED; + req->pseudo_headers |= PH_METHOD; return 0; } @@ -1880,6 +1896,7 @@ interop_server_hset_add_header (void *hset_p, struct lsxpack_header *xhdr) req->authority_str = strndup(value, value_len); if (!req->authority_str) return -1; + req->pseudo_headers |= PH_AUTHORITY; return 0; } diff --git a/docs/conf.py b/docs/conf.py index df74224..2f2928b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ author = u'LiteSpeed Technologies' # The short X.Y version version = u'2.29' # The full version, including alpha/beta/rc tags -release = u'2.29.5' +release = u'2.29.6' # -- General configuration --------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index c632a13..edfa1b6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -65,8 +65,8 @@ Contents gettingstarted tutorial apiref - internals devel + internals faq Indices and tables diff --git a/docs/internals.rst b/docs/internals.rst index 8613260..2dd94e6 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -1,11 +1,3052 @@ -********* -Internals -********* +############ +Library Guts +############ + +.. highlight:: c + +Introduction +************ + + +lsquic inception dates back to the fall of 2016. Since that time, lsquic +underwent several major changes. Some of those had to do with making the +library more performant; others were needed to add important new +functionality (for example, IETF QUIC and HTTP/3). Throughout this time, +one of the main principles we embraced is that **performance trumps +everything else**, including code readability and maintainability. This +focus drove code design decisions again and again and it explains some +of the hairiness that we will come across in this document. + +Code Version +============ + +The code version under discussion is v2.29.6. + +High-Level Structure +******************** + +At a high level, the lsquic library can be used to instantiate an engine +(or several engines). An engine manages connections; each connection has +streams. Engine, connection, and stream objects are exposed to the user +who interacts with them using the API (see :doc:`apiref`). All other data +structures are internal and are hanging off, in one way or another, from +the engine, connection, or stream objects. + +Coding Style +************ + +Spacing and Cuddling +==================== + +lsquic follows the LiteSpeed spacing and cuddling conventions: + +- Two empty lines between function definitions + +- Four-space indentation + +- Ifs and elses are not cuddled + +Function Name Alignment +======================= + +In function definitions, the name is always left-aligned, for example: + +:: + + static void + check_flush_threshold (lsquic_stream_t *stream) + + +Naming Conventions +================== + +- Struct members usually have prefixes derived from the struct name. + For example members of ``struct qpack_dec_hdl`` begin with ``qdh_``, + members of ``struct cid_update_batch`` begin with ``cub_``, and so on. + This is done to reduce the need to memorize struct member names as + vim's autocomplete (Ctrl-P) functionality makes it easy to fill in + the needed identifier. + +- Non-static functions all begin with ``lsquic_``. + +- Functions usually begin with a module name (e.g. ``lsquic_engine_`` or + ``stream_``) which is then followed by a verb (e.g. + ``lsquic_engine_connect`` or ``stream_activate_hq_frame``). If a + function does not begin with a module name, it begins with a verb + (e.g. ``check_flush_threshold`` or ``maybe_remove_from_write_q``). + +- Underscores are used to separate words (as opposed to, for example, + theCamelCase). + +Typedefs +======== + +Outside of user-facing API, structs, unions, and enums are not +typedefed. On the other hand, some integral types are typedefed. + +List of Common Terms +******************** + +- **gQUIC** Google QUIC. The original lsquic supported only Google + QUIC. gQUIC is going to become obsolete. (Hopefully soon). + +- **HQ** This stands for "HTTP-over-QUIC", the original name of HTTP/3. + The code predates the official renaming to HTTP/3 and thus there + are many types and names with some variation of ``HQ`` in them. + +- **iQUIC** This stands for IETF QUIC. To differentiate between gQUIC + and IETF QUIC, we use ``iquic`` in some names and types. + +Engine +****** + +*Files: lsquic_engine.c, lsquic_engine_public.h, lsquic.h* + +Data Structures +=============== + +out_batch +--------- + +:: + + /* The batch of outgoing packets grows and shrinks dynamically */ + /* Batch sizes do not have to be powers of two */ + #define MAX_OUT_BATCH_SIZE 1024 + #define MIN_OUT_BATCH_SIZE 4 + #define INITIAL_OUT_BATCH_SIZE 32 + + struct out_batch + { + lsquic_conn_t *conns [MAX_OUT_BATCH_SIZE]; + struct lsquic_out_spec outs [MAX_OUT_BATCH_SIZE]; + unsigned pack_off[MAX_OUT_BATCH_SIZE]; + lsquic_packet_out_t *packets[MAX_OUT_BATCH_SIZE * 2]; + struct iovec iov [MAX_OUT_BATCH_SIZE * 2]; + }; + +The array of struct lsquic_out_specs -- outs above -- is what gets +passed to the user callback ``ea_packets_out()``. ``conns`` array corresponds +to the spec elements one to one. + +``pack_off`` records which packet in ``packets`` corresponds to which +connection in ``conns``. Because of coalescing, an element in ``outs`` can +correspond (logically) to more than one packet. (See how the batch is +constructed in `Batching packets`_.) On the +other hand, ``packets`` and ``iov`` arrays have one-to-one correspondence. + +There is one instance of this structure per engine: the whole thing is +allocated as part of `struct lsquic_engine <#lsquic-engine>`__. + + +cid_update_batch +---------------- + +:: + + struct cid_update_batch + { + lsquic_cids_update_f cub_update_cids; + void *cub_update_ctx; + unsigned cub_count; + lsquic_cid_t cub_cids[20]; + void *cub_peer_ctxs[20]; + }; + +This struct is used to batch CID updates. + +There are three user-defined CID liveness callbacks: ``ea_new_scids``, +``ea_live_scids``, and ``ea_old_scids``. These functions all have the same +signature, ``lsquic_cids_update_f``. When the batch reaches the count of +20 (kept in ``cub_count``), the callback is called. + +The new SCIDs batch is kept in `struct +lsquic_engine <#lsquic-engine>`__. Other batches are allocated on the +stack in different functions as necessary. + +20 is an arbitrary number. + +lsquic_engine_public +-------------------- + +This struct, defined in lsquic_engine_public.h, is the "public" +interface to the engine. ("Public" here means accessible by other +modules inside lsquic, not that it's a public interface like the +:doc:`apiref`.) Because there are many things in the engine object that +are accessed by other modules, this struct is used to expose those +(``public``) parts of the engine. + +``lsquic_engine_struct`` is the first member of +`lsquic_engine <#lsquic-engine>`__. The functions declared in +lsquic_engine_public.h take a pointer to lsquic_engine_public as the +first argument, which is then case to lsquic_engine. + +This is somewhat ugly, but it's not too bad, as long as one remembers +that the two pointers are interchangeable. + +lsquic_engine +------------- + +This is the central data structure. The engine instance is the root of +all other data structures. It contains: + +- Pointers to connections in several lists and hashes (see `Connection Management <#connection-management>`__) + +- Memory manager + +- Engine settings + +- Token generator + +- CID Purgatory + +- Server certificate cache + +- Transport parameter cache + +- Packet request queue + +- `Outgoing packet batch <#out-batch>`__ + +- And several other things + +Some of the members above are stored in the ``pub`` member of type +`lsquic_engine_public <#lsquic-engine-public>`__. These are accessed +directly from other parts of lsquic. + +The engine is instantiated via ``lsquic_engine_new()`` and destroyed via +``lsquic_engine_destroy()`` Connection Management ===================== -References to connections can exist in six different places in an -engine. +Lifetime +-------- + +There are several `connection types`_. All types of +connections begin their life inside the engine module, where their +constructors are called. They all also end their life here as well: this +is where the destructors are called. + +The connection constructors are all different function calls: + +- lsquic_ietf_full_conn_client_new + +- lsquic_gquic_full_conn_client_new + +- lsquic_ietf_full_conn_server_new + +- lsquic_gquic_full_conn_server_new + +- lsquic_mini_conn_ietf_new + +- lsquic_mini_conn_new + +- lsquic_prq_new_req + +- lsquic_prq_new_req_ext + +(See `Evanescent Connection`_ for information about the last two.) + +After a connection is instantiated, all further interactions with it, +including destruction, are done via the `Common Connection Interface`_. + +Refcounting Model +----------------- + +Each connection is referenced by at least one of the following data +structures: + +1. CID-to-connection hash. This hash is used to find connections in + order to dispatch an incoming packet. Connections can be hashed by + CIDs or by address. In the former case, each connection has one or + more mappings in the hash table. IETF QUIC connections have up to + eight (in our implementation) source CIDs and each of those would + have a mapping. In client mode, depending on QUIC versions and + options selected, it is may be necessary to hash connections by + address, in which case incoming packets are delivered to + connections based on the address. + +2. Outgoing queue. This queue holds connections that have packets to + send. + +3. `Tickable Queue`_. This queue holds connections + that `can be ticked now <#tickability>`__. + +4. `Advisory Tick Time Queue`_. + +5. Closing connections queue. This is a transient queue -- it only + exists for the duration of + `process_connections() <#processing-connections>`__ function call. + +6. Ticked connections queue. Another transient queue, similar to the + above. + +The idea is to destroy the connection when it is no longer referenced. +For example, a connection tick may return TICK_SEND|TICK_CLOSE. In that +case, the connection is referenced from two places: (2) and (5). After +its packets are sent, it is only referenced in (5), and at the end of +the function call, when it is removed from (5), reference count goes to +zero and the connection is destroyed. (See function ``destroy_conn``.) If +not all packets can be sent, at the end of the function call, the +connection is referenced by (2) and will only be removed once all +outgoing packets have been sent. .. image:: lsquic-engine-conns.png + +In the diagram above, you can see that the CID-to-connection hash has +several links to the same connection. This is because an IETF QUIC +connection has more than one Source Connection IDs (SCIDs), any of which +can be included by the peer into the packet. See ``insert_conn_into_hash`` +for more details. + +References from each of these data structures are tracked inside the +connection object by bit flags: + +:: + + #define CONN_REF_FLAGS (LSCONN_HASHED \ + |LSCONN_HAS_OUTGOING \ + |LSCONN_TICKABLE \ + |LSCONN_TICKED \ + |LSCONN_CLOSING \ + |LSCONN_ATTQ) + +Functions ``engine_incref_conn`` and ``engine_decref_conn`` manage setting +and unsetting of these flags. + + +Notable Code +============ + +Handling incoming packets +------------------------- + +Incoming UDP datagrams are handed off to the lsquic library using the +function ``lsquic_engine_packet_in``. Depending on the engine mode -- +client or server -- the appropriate `packet +parsing <#parsing-packets>`__ function is selected. + +Because a UDP datagram can contain more than one QUIC packet, the +parsing is done in a loop. If the first part of packet parsing is +successful, the internal ``process_packet_in`` function is called. + +There, most complexity is contained in ``find_or_create_conn``, which gets +called for the server side. Here, parsing of the packet is finished, now +via the version-specific call to ``pf_parse_packet_in_finish``. If +connection is not found, it may need to be created. Before that, the +following steps are performed: + +- Check that engine is not in the cooldown mode + +- Check that the maximum number of mini connections is not exceeded + +- Check that the (D)CID specified in the packet is not in the `CID Purgatory`_ + +- Check that the packet can be used to create a mini conn: it contains + version information and the version is supported + +- Depending on QUIC version, perform token verification, if necessary + +Only then does the mini connection constructor is called and the +connection is inserted into appropriate structures. + +Processing connections +---------------------- + +Connections are processed in the internal function +``process_connections``. There is the main connection processing loop and +logic. + +All connections that the iterator passed to this function returns are +processed in the first while loop. The ``ci_tick()`` call is what causes +the underlying connection to do all it needs to (most importantly, +dispatch user events and generate outgoing packets). The return value +dictates what lists -- global and local to the function -- the +connection will be placed upon. + +Note that mini connection promotion happens inside this loop. Newly +created full connections are processed inside the same while loop. For a +short time, a mini and a full connection object exist that are +associated with the same logical connection. + +After all connections are ticked, outgoing packets, if there are any, +`are sent out <#batching-packets>`__. + +Then, connections that were closed by the first while loop above are +finally closed. + +Connections that were ticked (and not closed) are either: + +- Put back onto the ``tickable`` queue; + +- Added to the `Advisory Tick Time Queue`_; or + +- Left unqueued. This can happen when both idle and ping timer are + turned off. (This should not happen for the connections that we + expect to process, though.) + +And lastly, CID liveness updates are reported to the user via the +optional SCIDs callbacks: ``ea_new_scids`` etc. + +Tickable Queue Cycle +-------------------- + +When a connection is ticked, it is removed from the `Tickable +Queue <#tickable-queue>`__ and placed onto the transient Ticked Queue. +After outgoing packets are sent and some connections are closed, the +Ticked Queue is examined: the engine queries each remaining connection +again whether it's tickable. If it is, back onto the Tickable Queue it +goes. This should not happen often, however. It may occur when RTT is +low and there are many connections to process. In that case, once all +connections have been processed, the pacer now allows to send another +packet because some time has passed. + +Batching packets +---------------- + +Packet-sending entry point is the function ``send_packets_out``. The main +idea here is as follows: + +Iterate over connections that have packets to send (those are on the +Outgoing queue in the engine). For each connection, ask it for the next +outgoing packet, encrypt it, and place it into the batch. When the batch +is full, `send the batch <#sending-a-batch>`__. + +The outgoing packets from all connections are interleaved. For example, +if connections A, B, and C are on the Outgoing queue, the batch will +contain packets A1, B1, C1, A2, B2, C2, A3, B3, C3, … and so on. This is +done to ensure fairness. When a connection runs out of packets to send, +it returns NULL and is removed from the iterator. + +The idea is simple, but the devil is in the details. The code may be +difficult to read. There are several things going on: + +Conns Out Iterator +^^^^^^^^^^^^^^^^^^ + +This iterator, ``conns_out_iter``, sends packets from connections on the +Outgoing queue and packets on the Packet Request queue. (The latter +masquerade as `Evanescent Connections <#evanescent-connection>`__ so that they +are simple to use.) First, the Outgoing queue (which is a min-heap) is +drained. Then, packets from the Packet Request queue are sent, if there +are any. Then, remaining connections from the first pass are returned in +the round-robin fashion. + +After sending is completed, the connections that still have outgoing +packets to send are placed back onto the Outgoing queue. + + +Packet Coalescing +^^^^^^^^^^^^^^^^^ + +Some IETF QUIC packets can be coalesced. This reduces the number of UDP +datagrams that need to be sent during the handshake. To support this, if +a packet matches some parameters, the same connection is queried for +another packet, which, if it returns, is added to the current batch +slot's iov. + +:: + + if ((conn->cn_flags & LSCONN_IETF) + && ((1 << packet_out->po_header_type) + & ((1 << HETY_INITIAL)|(1 << HETY_HANDSHAKE)|(1 << HETY_0RTT))) + && (engine->flags & ENG_COALESCE) + && iov < batch->iov + sizeof(batch->iov) / sizeof(batch->iov[0])) + { + const struct to_coal to_coal = { + .prev_packet = packet_out, + .prev_sz_sum = iov_size(packet_iov, iov), + }; + packet_out = conn->cn_if->ci_next_packet_to_send(conn, &to_coal); + if (packet_out) + goto next_coa; + } + batch->outs [n].iovlen = iov - packet_iov; + +*With some debug code removed for simplicity* + +Also see the description of the batch in `out_batch`_. + +Note that packet coalescing is only done during the handshake of an IETF +QUIC connection. Non-handshake and gQUIC packets cannot be coalesced. + +Sending and Refilling the Batch +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When the batch is sent inside the while loop, and the whole batch was +sent successfully, the batch pointers are reset, the batch potentially +grows larger, and the while loop continues. + +Batch Resizing +^^^^^^^^^^^^^^ + +When all datagrams in the batch are sent successfully, the batch may +grow -- up to the hardcoded maximum value of ``MAX_OUT_BATCH_SIZE``. When +not all datagrams are sent, the batch shrinks. The batch size survives +the call into the library: when packets are sent again, the same batch +size is used to begin the sending. + +Deadline Checking +^^^^^^^^^^^^^^^^^ + +This is a rather old safety check dating back to the summer of 2017, +when we first shipped QUIC support. The way we send packets has changed +since then -- there is high possibility that this code can be removed +with no ill effect. + +Sending a batch +--------------- + +When the batch is filled, it is handed off to the function ``send_batch``, +which calls the user-supplied callback to send packets out. The +high-level logic is as follows: + +- Update each packet's ``sent`` time + +- Call the "send packets out" callback + +- For packets that were sent successfully, call ``ci_packet_sent`` + +- For packets that were not sent, call ``ci_packet_not_sent``. This is + important: all packets returned by ``ci_next_packet_to_send`` must + be returned to the connection via either these two calls above or + via ``ci_packet_too_large`` (see below). + +- Return the number of packets sent + +Because of support for coalescing, we have to map from outgoing spec to +packets via ``batch->pack_off``. This is done in several places in this +function. + +To handle the case when a PMTU probe is too large (stuff happens!), the +code checks for EMSGSIZE and returns the packet back to the connection +via ``ci_packet_too_large``. Because this error is of our own making, this +does not count as inability to send. The too-large packet is skipped and +sending of the datagrams in the batch continues. + +Growing min-heaps +----------------- + +The Outgoing and Tickable connection queues are actually min-heaps. The +number of elements in these min-heaps never exceeds the number of +connections. As optimization, allocation of the underlying arrays is +done not in the min-heap module itself but in the engine module in the +function ``maybe_grow_conn_heaps``. The engine knows how many connections +there are and it grows the arrays as necessary. + +As an additional optimization, the two arrays use a single memory region +which is allocated once. + +The min-heap arrays are never shrunk. + +Connection +********** + +*Files: lsquic_conn.h, lsquic_conn.c -- others are covered in dedicated +chapters* + +The connection represents the QUIC connection. Connections are `managed +by the engine <#connection-management>`__. A connection, in turn, +manages `streams <#stream>`__. + +Connection Types +================ + +lsquic supports two different QUIC protocols: Google QUIC and IETF QUIC. +Each of these has a separate implementation, which includes connection +logic, parsing/generating mechanism, and encryption. + +Each of the QUIC connection types on the server begin their life as a +``mini`` connection. This connection type is used while handshake is +proceeding. Once the handshake has completed, the mini connection is +``promoted`` to a ``full`` connection. (See `Mini vs Full +Connection <#mini-vs-full-connections>`__ for more.) + +In addition to the above, an "evanescent" connection type is used to +manage replies to incoming packets that do not result in connection +creation. These include version negotiation, stateless retry, and +stateless reset packets. + +Each of the five connection types above are covered in their own +dedicated chapters elsewhere in this document: + +- `Mini gQUIC Connection <#mini-gquic-connection>`__ + +- `Full gQUIC Connection <#connection-public-interface>`__ + +- `Mini IETF QUIC Connection <#mini-ietf-connection>`__ + +- `Full IETF QUIC Connection <#mini-ietf-connection>`__ + +- `Evanescent Connection <#evanescent-connection>`__ + +lsquic_conn +=========== + +All connection types expose the same connection interface via a pointer +to ``struct lsquic_conn``. (This is the same type pointer to which is +exposed to the user, but the user can only treat the connection as an +opaque pointer.) + +This structure contains the following elements: + +Pointers to Crypto Implementation +--------------------------------- + +The crypto session pointer, ``cn_enc_session``, points to a type-specific +(gQUIC or iQUIC) instance of the encryption session. This session +survives `connection promotion <#connection-promotion>`__. + +The two types of crypto session have a set of common functionality; it +is pointed to by ``cn_esf_c`` (where ``c`` stands for ``common``). Each of +them also has its own, type-specific functionality, which is pointed to +by ``cn_esf.g`` and ``cn_esf.i`` + +Pointer to Common Connection Interface +-------------------------------------- + +``cn_if`` points to the set of functions that implement the Common +Connection Interface (`see below <#common-connection-interface>`__). + +Pointer to Parsing Interface +---------------------------- + +The parsing interface is version-specific. It is pointed to by ``cn_pf``. + +Various list and heap connectors +-------------------------------- + +A connection may be pointed to by one or several queues and heaps (see +"\ `Connection Management <#connection-management>`__\ "). There are +several struct members that make it possible: all the \*TAILQ_ENTRYs, +``cn_attq_elem``, and ``cn_cert_susp_head``. + +Version +------- + +``cn_version`` is used to make some decisions in several parts of the +code. + +Flags +----- + +The flags in ``cn_flags`` specify which lists the connection is on and +some other properties of the connection which need to be accessible by +other modules. + +Stats +----- + +``cn_last_sent`` and ``cn_last_ticked`` are used to determine the +connection's place on the outgoing queue (see `Batching +Packets <#batching-packets>`__) and on the `Advisory Tick Time +Queue <#alarm-set>`__. + +List of SCIDs +------------- + +IETF QUIC connections have one or more SCIDs (Source Connection IDs), +any one of which can be used by the peer as the DCID (Destination CID) +in the packets it sends. Each of the SCIDs is used to hash the +connection so it can be found. ``cn_cces`` points to an array of size +``cn_n_cces`` which is allocated internally inside each connection type. + +Google QUIC connections use only one CID (same for source and +destination). In order not to modify old code, the macro ``cn_cid`` is +used. + +Common Connection Interface +=========================== + +The struct ``conn_iface`` defines the common connection interface. All +connection types implement all or some of these functions. + +Some of these functions are used by the engine; others by other modules +(for example, to abort a connection); yet others are for use by the +user, e.g. ``lsquic_conn_close`` and others in lsquic.h. In that case, +these calls are wrapped in lsquic_conn.c. + +Tickability +=========== + +A connection is processed when it is tickable. More precisely, the +connection is placed onto the `Tickable Queue <#tickable-queue>`__, +which is iterated over when `connections are +processed <#processing-connections>`__. A connection reports its own +tickability via the ``ci_is_tickable`` method. + +In general, a connection is tickable if it has productive user callbacks +to dispatch (that is, user wants to read and there is data to read or +user wants to write and writing is possible), if there are packets to +send or generate, or if its advisory tick time is in the past. (The +latter is handled in ``lsquic_engine_process_conns()`` when expired +connections from the `Advisory Tick Time Queue`_ are added +to the Tickable Queue.) + +Stream +****** + +*Files: lsquic_stream.h, lsquic_stream.c* + +Overview +======== + +The lsquic stream is the conduit for data. This object is accessible by +the user via any of the ``lsquic_stream_*`` functions declared in +lsquic.h. The stream is bidirectional; in our user code, it represents +the HTTP request and response. The client writes its request to the +stream and the server reads the request in its corresponding instance of +the stream. The server sends its response using the same stream, which +the client reads from the stream. + +Besides streams exposed to the application, connections use streams +internally: + +- gQUIC has the HANDSHAKE and HEADERS streams + +- IETF QUIC has up to four HANDSHAKE streams + +- HTTP/3 has at least three unidirectional streams: + + - Settings stream + + - QPACK encoder stream + + - QPACK decoder stream + +In addition, HTTP/3 push promises use unidirectional streams. In the +code, we make a unidirectional stream simply by closing one end in the +constructor. + +All of the use cases above are handled by the single module, +lsquic_stream. The differences in behavior -- gQUIC vs IETF QUIC, HTTP +vs non-HTTP -- are handled either by explicit conditionals or via +function pointers. + +The streams hang off full connections via stream ID-to-stream hashes and +in various queues. This is similar to the way the connections hang off +the engine. + +Streams are only used in the full connections; mini connections use +their own, minimalistic, code to handle streams. + +.. _data-structures-1: + +Data Structures +=============== + +stream_hq_frame +--------------- + +This structure is used to keep information about an HTTP/3 frame that is +being, or is about to be, written. In our implementation, frame headers +can be two or three bytes long: one byte is HTTP/3 frame type and the +frame length is encoded in 1 or 2 bytes, giving us the maximum payload +size of 2\ :sup:`14` - 1 bytes. You will find literal ``2`` or ``3`` values +in code that deals with writing HQ frames. + +If the HQ frame's size is known in advance (SHF_FIXED_SIZE) -- which is +the case for HEADERS and PUSH_PROMISE frames -- then the HQ header +contents are written immediately. Otherwise, ``shf_frame_ptr`` points to +the bytes in the packet where the HQ header was written, to be filled in +later. + +See `Writing HTTP/3 Streams`_ for more information. + +hq_filter +--------- + +This structure is used to read HTTP/3 streams. A single instance of it +is stored in the stream in ``sm_hq_filter``. The framing is removed +transparently (see `Reading HTTP/3 Streams`_). + +Frame type and length are read into ``hqfi_vint2_state``. Due to greasing, +the reader must be able to support arbitrary frame types and so the code +is pretty generic: varints of any size are supported. + +``hqfi_flags`` and ``hqfi_state`` contain information needed to resume +parsing the frame header, as only partial data may have arrived. + +``hqfi_hist_buf`` and ``hqfi_hist_idx`` are used to record the last few +incoming headers. This information is used to check for validity, as +some sequences of HTTP/3 frames are invalid. + +stream_filter_if +---------------- + +This struct is used to specify functionality required to strip arbitrary +framing when reading from the stream. At the moment (and for the +foreseeable future) only one mechanism is used: that to strip the HTTP/3 +framing. At the time the code was written, however, the idea was to +future-proof it in case we needed to support more than one framing format +at a time. + +lsquic_stream +------------- + +This struct is the stream object. It contains many members that deal +with + +- Reading data + +- Writing data + +- Maintaining stream list memberships + +- Enforcing flow control + +- Dispatching read and write events + +- Calling various user callbacks + +- Interacting with HEADERS streams + +The stream has an ID (``id``). It is used to hash the stream. + +A stream can be on one or more lists: see ``next_send_stream``, +``next_read_stream``, and so on. + +Incoming data is stored in ``data_in``. Outgoing data is packetized +immediately or buffered in ``sm_buf``. + +HTTP/3 frames that are being actively written are on the ``sm_hq_frames`` +list. + +A note on naming: newer members of the stream begin with ``sm_`` for +simplicity. Originally, the structure members lacked a prefix. + +progress +-------- + +This structure is used to determine whether the user callback has made +any progress during an ``on_write`` or ``on_read`` event loop. If progress +is not made for a number of calls, the callback is interrupted, breaking +out of a suspected infinite loop. (See ``es_progress_check`` setting.) + + +frame_gen_ctx +------------- + +This structure holds function pointers to get user data and write it to +packets. ``fgc_size``, ``fgc_fin``, and ``fgc_read`` are set based on framing +requirements. This is a nice abstraction that gets passed to several +packetization functions and allows them not to care about how or whether +framing is performed. + +pwritev_ctx +----------- + +Used to aid ``lsquic_stream_pwritev``. ``hq_arr`` is used to roll back +HTTP/3 framing if necessary. (The rollback is the most complicated part +of the ``pwritev`` functionality). + +Event Dispatch +============== + +The "on stream read" and "on stream write" callbacks are part of the +lsquic API. These callbacks are called when the user has registered +interest in reading from or writing to the stream and reading or writing +is possible. + +Calling ``lsquic_stream_wantwrite`` and ``lsquic_stream_wantread`` places +the stream on the corresponding "want to write" and "want to read" list. +These lists are processed by a connection when it's ticked. For each +stream on the list, the internal function +``lsquic_stream_dispatch_read_events`` or +``lsquic_stream_dispatch_write_events``, whichever may be the case. + +Dispatching read events is simple. When ``es_rw_once`` is set, the "on +stream read" callback is called once -- if the stream is readable. +Otherwise, the callback is called in a loop as long as: + +- The stream is readable; + +- The user wants to read from it; and + +- Progress is being made + +Dispatching write events is more complicated due to the following +factors: + +- In addition to calling the "on stream write" callback, the flushing + mechanism also works by using the "want to write" list. + +- When writing occurs, the stream's position on the list may change + +STREAM frames in +================ + +The data gets in from the transport into the stream via +``lsquic_stream_frame_in`` function. The connection calls this function +after parsing a STREAM frame. + +The data from the STREAM frame is stored in one of the two "data in" +modules: ``di_nocopy`` and ``di_hash``. The two are abstracted out behind +``stream->data_in``. + +The "data in" module is used to store incoming stream data. The data is +read from this module using the ``di_get_frame`` function. See the next +section. + +Reading Data +============ + +There are three user-facing stream-reading functions; two of them are +just wrappers around ``"lsquic_stream_readf``. This function performs some +checks (we will cover HTTP mode separately) and calls +``lsquic_stream_readf``, which also performs some checks and calls +``read_data_frames``. This is the only function in the stream module where +data is actually read from the "data in" module. + +Writing Data +============ + +There are four user-facing functions to write to stream, and all of them +are wrappers around ``stream_write``. (``lsquic_stream_pwritev`` is a bit +more involved than the other three, but it's pretty well-commented -- +and the complexity is in the rollback, not writing itself.) + +Small writes get buffered. If the write size plus whatever is buffered +already exceeds the threshold -- which is the size of the largest STREAM +frame that could be fit into a single outgoing packet -- the data is +packetized instead by calling ``stream_write_to_packets``. See the next +section. + +Packetization +============= + +``stream_write_to_packets`` is the only function through which user data +makes it into outgoing packets. There are three ways to write STREAM +frames: + +1. ``stream_write_to_packet_hsk`` + +2. ``stream_write_to_packet_std`` + +3. ``stream_write_to_packet_crypto`` + +The particular function is selected based on connection and stream type +when the stream is first created. + +stream_write_to_packets +----------------------- + +Depending on the need to frame data, a reader is selected. The job of +the reader is to copy user data into the outgoing STREAM frame. In +HTTP/3 mode, HTTP/3 framing is added transparently -- see `Writing +HTTP/3 Streams`_ for more information. + +The while loop is entered if there is user data to be copied or if the +end of the stream has been reached and FIN needs to be written. Note the +threshold check: when writing data from a user call, the threshold is +set and frames smaller than the full packet are not generated. This is +to allow for usage like "write 8KB", "write 8KB", "write 8KB" not to +produce jagged STREAM frames. This way, we utilize the bandwidth most +effectively. When flushing data, the threshold is not set, so even a +1-byte data gets packetized. + +The call ``stream->sm_write_to_packet`` writes data to a single packet. +This packet is allocated by the `Send Controller <#send-controller>`__. +(Depending on when writing is performed, the returned packet may be +placed onto the scheduled queue immediately or it may be a "buffered" +packet. The stream code is oblivious to that.) If the send controller +does not give us a packet, STOP is returned and the while loop exits. An +ERROR should never happen -- this indicates a bug or maybe failure to +allocate memory -- and so the connection is aborted in that case. If +everything is OK, the while loop goes on. + +The ``seen_ok`` check is used to place the connection on the tickable list +on the first successfully packetized STREAM frame. This is so that if +the packet is buffered (meaning that the writing is occurring outside of +the callback mechanism), the connection will be processed (ticked) and +the packets will be scheduled and sent out. + +After the while loop, we conditionally close an outstanding HTTP/3 +frame, save any leftover data, schedule STREAM_BLOCKED or BLOCKED frames +to be sent out if needed, and return the number of user-provided bytes +that were copied into the outgoing packets and into the internal stream +buffer (leftovers). + +Write a single STREAM frame +--------------------------- + +We will examine ``stream_write_to_packet_std`` as it is the most +complicated of these three functions. + +First, we flush the headers stream if necessary -- this is because we +want the HTTP (gQUIC or HTTP/3) headers to be sent before the payload. + +Then, the number of bytes needed to generate a STREAM frame is +calculated. This value depends on the QUIC version, whether we need to +generate HTTP/3 framing, and whether the data to write exists (or we +just need to write an empty STREAM frame with the FIN bit set). + +(Note that the framing check is made to overshoot the estimate for +simplicity. For one, we might not need 3 bytes for the DATA frame, but +only 2. Secondly, there may already be an open HTTP/3 frame in one of +the previous packets and so we don't need to write it at all.) + +Then, a packet is allocated and ``write_stream_frame`` is called. It is in +this function that we finally make the call to generate the STREAM frame +and to copy the data from the user. The function ``pf_gen_stream_frame`` +returns the number of bytes actually written to the packet: this +includes both the STREAM frame header and the payload (which may also +include HTTP/3 frame). + +The fact that this frame type has been written is added to +``po_frame_types`` and the STREAM frame location, type, and size are +recorded. This information is necessary to be able to elide the frame +from the packet in case the stream is reset. + +``PO_STREAM_END`` is set if the STREAM frame extends to the end of the +packet. This is done to prevent this packet from being used again to +append frames to it (after, for example, some preceding frames are +elided from it). This is because both in gQUIC and IETF QUIC the STREAM +frame header is likely to omit the ``length`` field and instead use the +"extends to the end of the packet" field. If frames are shifted, the +packet cannot be appended to because it will lead to data loss and +corruption. + +Writing HTTP/3 Streams +====================== + +HTTP/3 streams use framing. In most cases, a single HEADERS frame is +followed by zero or more DATA frames. The user code does not know this: +both gQUIC and IETF QUIC streams appear to behave in exactly the same +manner. This makes lsquic simple to use. + +The drawback is internal complexity. To make the code both easy to use +and performant, HTTP/3 framing is generated on-the-fly, as data is being +written to packets (as opposed to being buffered and then written). (OK, +*mostly* on-the-fly: the HEADERS frame payload is generated and then +copied.) + +On the high level, the way it works is as follows: + +- When a write call is made, a variable-size (that is, unknown size; + it's called variable-size because the size of the DATA header may + be 2 or 3 bytes; it's not the best name in the world) frame is + opened/activated. + +- When data is written to stream, the DATA header placeholder bytes are + written to the stream transparently and a pointer is saved to this + location. + +- The active frame header is closed when + + - It reaches its maximum size; or + + - The data we are writing runs out. + +- When the header is closed, the number of bytes that follows is now + written to the location we saved when the header was activated. + +This mechanism allows us to create a DATA frame that spans several +packets before we know how many packets there will be in a single write. +(As outgoing packet allocation is governed by the `Send Controller`_.) +This is done to minimize the goodput overhead incurred by the DATA frame header. + +.. image:: stream-http3-framing.png + +There are a couple of things that do not fit into this model: + +1. The HEADERS frame is fixed size [1]_. It is generated separately + (written by QPACK encoder into a buffer on the stack) and later + copied into the stream. (See the ``send_headers_ietf`` function.) It + can happen that the whole buffer cannot be written. In that case, + a rather complicated dance of buffering the unwritten HEADERS + frame bytes is performed. Here, the "on stream write" callback is + replaced with an internal callback (see the ``select_on_write`` + function) and user interaction is prohibited until the whole of + the HEADERS frame is written to the stream. + +2. Push promise streams are even weirder. In addition to the HEADERS + handling above, the push promise stream must begin with a + variable-integer Push ID. To make this fit into the framed stream + model, the code makes up the concept of a "phantom" HTTP/3 frame. + This type of frame's header is not written. This allows us to + treat the Push ID as the payload of a regular HTTP/3 frame. + +The framing code has had its share of bugs. Because of that, there is a +dedicated unit test program just for the framing code, +*tests/test_h3_framing.c*. In addition to manually-written tests, the +program has a "fuzzer driver" mode, in which the American Fuzzy Lop +fuzzer drives the testing of the HTTP/3 framing mechanism. The advantage +of this approach is that AFL tries to explore all the code paths. + + +Debates regarding DATA framing raged in 2018 on the QUIC mailing list. +Some of the discussion is quite interesting: for example, the debate about +"optimizing" DATA frames and `calculations of the header +cost `__. + +Reading HTTP/3 Streams +====================== + +HTTP/3 frame headers are stripped out transparently -- they are never +seen by the user. From the user's perspective, the lsquic stream +represents the payload of HTTP message; a dedicated call is made first +to get at the HTTP headers. + +To accomplish this, the stream implements a generic deframing mechanism. +The `stream_filter_if`_ interface allows one to +specify functions to a) check whether the stream is readable, b) strip +header bytes from a data frame fetched from "data in" module; and c) +update byte count in the filter once bytes have been read: + +hq_filter_readable +------------------ + +This function tests for availability of non-frame-header data, stripping +frame headers from the stream transparently. Note how it calls +``read_data_frames`` with its own callback, ``hq_read``. It is inside this +callback that the HEADERS frame is fed to the QPACK decoder. + +hq_filter_df +------------ + +This function's job is to strip framing from data frames returned by the +"data in" module inside the ``read_data_frames`` function. It, too, calls +the ``hq_read`` function. This allows the two functions that read from +stream (this one) and the readability-checking function +(``hq_filter_readable``) to share the same state. This is crucial: +Otherwise this approach is not likely to work well. + +hq_decr_left +------------ + +This function is needed to update the filter state. Once all payload +bytes from the frame have been consumed, the filter is readied to strip +the next frame header again. + +.. _notable-code-1: + +Notable Code +============ + +frame_hq_gen_read +----------------- + +This is where HTTP/3 frame headers are generated. Note the use of +``shf_frame_ptr`` to record the memory location to which the correct frame +size will be written by a different function. + +Parsing +******* + +Parsing Packets +=============== + +Parsing Frames +============== + +Mini vs Full Connections +************************ + +Mini Purpose +============ + +The reason for having a mini connection is to conserve resources: a mini +connection allocates a much smaller amount of memory. This protects the +server from a potential DoS attack. The mini connection's job is to get +the handshake to succeed, after which the connection is +`promoted <#connection-promotion>`__. + +Mini/Full Differences +===================== + +Besides their size, the two connection types differ in the following +ways: + +Mini connections' lifespan is limited. If the handshake does not succeed +within 10 seconds (configurable), the mini connection is destroyed. + +A mini connection is only `tickable <#tickability>`__ if it has unsent +packets. + +Mini connections do not process packets that carry application (as +opposed to handshake) data. The 0-RTT packet processing is deferred; +these packets are stashed and handed over to the full connection during +promotion. + +Connection Promotion +==================== + +A mini connection is promoted when the handshake succeeds. The mini +connection reports this via the return status of ``ci_tick`` by setting +the ``TICK_PROMOTE`` bit. The engine creates a new connection object and +calls the corresponding server constructor. The latter copies all the +relevant state information from mini to full connection. + +For a time, two connection objects -- one mini and one full -- exist at +the same state. Most of the time, the mini connection is destroyed +within the same function call to ``process_connections()``. If, however, +the mini connection has unsent packets, it will remain live until those +packets are sent successfully. Because the mini connection is by then +removed from the CID-to-connection hash (``engine->conns_hash``), it will +not receive any more incoming packets. + +Also see `Connection Processing <#processing-connections>`__. + +Mini gQUIC Connection +********************* + +*Files: lsquic_mini_conn.h, lsquic_mini_conn.c* + +.. _overview-1: + +Overview +======== + +The original version of ``struct mini_conn`` fit into paltry 128 bytes. +The desire to fit into 128 bytes [2]_ led to, for example, +``mc_largest_recv`` -- in effect, a 3-byte integer! Since that time, +the mini conn has grown to over 512 bytes. + +Looking at the struct, we can see that a lot of other data structures +are squeezed into small fields: + +Received and sent packet history is each packed into a 64-bit integer, +``mc_received_packnos`` and ``mc_sent_packnos``, respectively. The HEADERS +stream offsets are handled by the two two-byte integers ``mc_read_off`` +and ``mc_write_off``. + +.. _notable-code-2: + +Notable Code +============ + +continue_handshake +------------------ + +This function constructs a contiguous buffer with all the HANDSHAKE +stream chunks in order and passes it to ``esf_handle_chlo()``. This is +done because the gQUIC crypto module does not buffer anything: it's all +or nothing. + +The code has been written in a generic way, so that even +many small packets can be reconstructed into a CHLO. The lsquic client +can be made to split the CHLO by setting the max packet size +sufficiently low. + +sent/unsent packets +------------------- + +To conserve space, only a single outgoing packet header exists in the +mini connection struct, ``mc_packets_out``. To differentiate between +packets that are to be sent and those that have already been sent, the +``PO_SENT`` flag is used. + +Mini IETF Connection +******************** + +*Files: lsquic_mini_conn_ietf.h, lsquic_mini_conn_ietf.c* + +.. _overview-2: + +Overview +======== + +The IETF QUIC mini connection has the same idea as the gQUIC mini +connection: use as little memory as possible. This is more difficult to +do with the IETF QUIC, however, as there are more moving parts in this +version of the protocol. + +.. _data-structures-2: + +Data Structures +=============== + +mini_crypto_stream +------------------ + +This structure is a minimal representation of a stream. The IETF QUIC +protocol uses up to four HANDSHAKE streams (one for each encryption +level) during the handshake and we need to keep track of them. Even a +basic event dispatch mechanism is supported. + +packno_set_t +------------ + +This bitmask is used to keep track of sent, received, and acknowledged +packet numbers. It can support up to 64 packet numbers: 0 through 63. We +assume that the server will not need to send more than 64 packets to +complete the handshake. + +imc_recvd_packnos +----------------- + +Because the client is allowed to start its packet number sequence with +any number in the [0, 2\ :sup:`32`-1] range, the received packet history +must be able to accommodate numbers larger than 63. To do that, the +receive history is a union. If all received packet numbers are 63 or +smaller, the packno_set_t bitmask is used. Otherwise, the receive +history is kept in `Tiny Receive History <#tiny-receive-history>`__ +(trechist). The flag ``IMC_TRECHIST`` indicates which data structure is +used. + +ietf_mini_conn +-------------- + +This structure is similar to the gQUIC mini conn. It is larger, though, +as it needs to keep track of several instances of things based on +encryption level or packet number space. + +``imc_cces`` can hold up to three SCIDs: one for the original DCID from +the client, one for SCID generated by the server, and one for when +preferred address transport parameter is used. (The preferred address +functionality is not compiled by default.) + +ietf_mini_rechist +----------------- + +The receive history is in the header file because, in addition to +generating the ACK frames in the IETF mini conn, it is used to migrate +the receive history during promotion. + +.. _notable-code-3: + +Notable Code +============ + +Switching to trechist +--------------------- + +The switch to the Tiny Receive History happens when the incoming packet +number does not fit into the bitmask anymore -- see +``imico_switch_to_trechist()``. To keep the trechist code exercised, about +one in every 16 mini connection uses trechist unconditionally -- see +``lsquic_mini_conn_ietf_new()``. + +crypto_stream_if +---------------- + +A set of functions to drive reading and writing CRYPTO frames to move +the handshake along is specified. It is passed to the crypto session. +After promotion, the full connection installs its own function pointers. + +imico_read_chlo_size +-------------------- + +This function reads the first few bytes of the first CRYPTO frame on the +first HANDSHAKE stream to figure out the size of ClientHello. The +transport parameters will not be read until the full ClientHello is +available. + + +Duplicated Code +--------------- + +Some code has been copied from gQUIC mini connection. This was done on +purpose, with the expectation that gQUIC is going away. + +ECN Blackhole Detection +----------------------- + +ECN blackhole at the beginning of connection is guessed at when none of +packets sent in the initial batch were acknowledged. This is done by +``imico_get_ecn()``. ``lsquic_mini_conn_ietf_ecn_ok()`` is also used during +promotion to check whether to use ECN. + +Connection Public Interface +*************************** + +*Files: lsquic_conn_public.h* + +TODO + +Full gQUIC Connection +********************* + +*Files: lsquic_full_conn.h, lsquic_full_conn.c* + +.. _overview-3: + +Overview +======== + +The full gQUIC connection implements the Google QUIC protocol, both +server and client side. This is where a large part of the gQUIC protocol +logic is contained and where everything -- engine, streams, sending, +event dispatch -- is tied together. + +Components +========== + +In this section, each member of the ``full_conn`` structure is documented. + +fc_conn +------- + +The first member of the struct is the common connection object, +`lsquic_conn`_. + +It must be first in the struct because the two pointer are cast to each +other, depending on circumstances. + +fc_cces +------- + +This array holds two connection CID elements. + +The reason for having two elements in this array instead of one (even +though gQUIC only uses one CID) is for the benefit of the client: In +some circumstances, the client connections are hashed by the port +number, in which case the second element is used to hash the port value. +The relevant code is in lsquic_engine.c + +fc_rechist +---------- + +This member holds the `packet receive history <#receive-history>`__. It +is used to generate ACK frames. + +fc_stream_ifs +------------- + +This three-element array holds pointers to stream callbacks and the +stream callback contexts. + +From the perspective of lsquic, Google QUIC has three stream types: + +1. HANDSHAKE stream; + +2. HEADERS stream; and + +3. Regular (message, or request/response) streams. + +The user provides stream callbacks and the context for the regular +streams (3) in ``ea_stream_if`` and ``ea_stream_if_ctx``. + +The other two stream types are internal. The full connection specifies +internal callbacks for those streams. One set handles the handshake and +the other handles reading and writing of HTTP/2 frames: SETTINGS, +HEADERS, and so on. + +fc_send_ctl +----------- + +This is the `Send Controller <#send-controller>`__. It is used to +allocate outgoing packets, control sending rate, and process +acknowledgements. + +fc_pub +------ + +This member holds the `Connection Public +Interface <#connection-public-interface>`__. + +fc_alset +-------- + +This is the `Alarm Set <#alarm-set>`__. It is used to set various timers +in the connection and the send controller. + +fc_closed_stream_ids +-------------------- + +The two sets in this array hold the IDs of closed streams. + +There are two of them because of the uneven distribution of stream IDs. +It is more efficient to hold even and odd stream IDs in separate +structures. + +fc_settings +----------- + +Pointer to the engine settings. + +This member is superfluous -- the settings can be fetched from +``fc_enpub->enp_settings``. + +fc_enpub +-------- + +This points to the `engine's public interface <#lsquic-engine-public>`__. + +fc_max_ack_packno +----------------- + +Recording the maximum packet number that contained an ACK allows us to +ignore old ACKs. + +fc_max_swf_packno +----------------- + +This is the maximum packet number that contained a STOP_WAITING frame. +It is used to ignore old STOP_WAITING frames. + +fc_mem_logged_last +------------------ + +This timestamp is used to limit logging the amount of memory used to +most once per second. + +fc_cfg +------ + +This structure holds a few important configuration parameters. (Looks +like ``max_conn_send`` is no longer used…) + +fc_flags +-------- + +The flags hold various boolean indicators associated with the full +connections. Some of them, such as ``FC_SERVER``, never change, while +others change all the time. + +fc_n_slack_akbl +--------------- + +This is the number of ackable (or, in the new parlance, *ack-eliciting*) +packets received since the last ACK was sent. + +This counter is used to decide whether an ACK should be sent (or, more +precisely, queued to be sent) immediately or whether to wait. + +fc_n_delayed_streams +-------------------- + +Count how many streams have been delayed. + +When ``lsquic_conn_make_stream()`` is called, a stream may not be created +immediately. It is delayed if creating a stream would go over the +maximum number of stream allowed by peer. + +fc_n_cons_unretx +---------------- + +Counts how many consecutive unretransmittable packets have been sent. + + +fc_last_stream_id +----------------- + +ID of the last created stream. + +Used to assign ID to streams created by this side of the connection. +Clients create odd-numbered streams, while servers initiate +even-numbered streams (push promises). + +fc_max_peer_stream_id +--------------------- + +Maximum value of stream ID created by peer. + +fc_goaway_stream_id +------------------- + +Stream ID received in the GOAWAY frame. + +This ID is used to reset locally-initiated streams with ID larger than +this. + +fc_ver_neg +---------- + +This structure holds the version negotiation state. + +This is used by the client to negotiate with the server. + + +With gQUIC going away, it is probably not very important anymore. + +fc_hsk_ctx +---------- + +Handshake context for the HANDSHAKE stream. + +Client and server have different HANDSHAKE stream handlers -- and +therefore different contexts. + +fc_stats +-------- + +Connection stats + +fc_last_stats +------------- + +Snapshot of connection stats + +This is used to log the changes in counters between calls to +``ci_log_stats()``. The calculation is straightforward in +``lsquic_conn_stats_diff()``. + +fc_stream_histories and fc_stream_hist_idx +------------------------------------------ + +Rolling log of histories of closed streams + + +fc_errmsg +--------- + +Error message associated with connection termination + +This is set when the connection is aborted for some reason. This error +message is only set once. It is used only to set the error message in +the call to ``ci_status()`` + +fc_recent_packets +----------------- + +Dual ring-buffer log of packet history + +The first element is for incoming packets, the second is for outgoing +packets. Each entry holds received or sent time and frame information. + +This can be used for debugging. It is only compiled into debug builds. + +fc_stream_ids_to_reset +---------------------- + +List of stream ID to send STREAM_RESET for + +These STREAM_RESET frames are associated with streams that are not +allowed to be created because we sent a GOAWAY frame. (There is a period +when GOAWAY is in transit, but the peer keeps on creating streams). To +queue the reset frames for such a stream, an element is added to this +list. + +fc_saved_ack_received +--------------------- + +Timestamp of the last received ACK. + +This is used for `ACK merging <#ack-merging>`__. + +fc_path +------- + +The network path -- Google QUIC only has one network path. + +fc_orig_versions +---------------- + +List (as bitmask) of original versions supplied to the client +constructor. + +Used for version negotiation. See `fc_ver_neg`_ for more +coverage of this topic. + +fc_crypto_enc_level +------------------- + +Latest crypto level + +This is for Q050 only, which does away with the HANDSHAKE stream and +uses CRYPTO frames instead. (This was part of Google's plan to move +Google QUIC protocol closer to IETF QUIC.) + +fc_ack +------ + +Saved ACK -- latest or merged + +This ACK structure is used in `ACK merging <#ack-merging>`__. + +Instantiation +============= + +The largest difference between the server and client mode of the full +connection is in the way it is created. The client creates a brand-new +connection, performs version negotiation, and runs the handshake before +dispatching user events. The server connection, on the other hand, gets +created from a mini connection during `connection +promotion <#connection-promotion>`__. By that time, both version +negotiation and handshake have already completed. + +Common Initialization +--------------------- + +The ``new_conn_common()`` function contains initialization common to both +server and client. Most full connection's internal data structures are +initialized or allocated here, among them `Send +Controller <#send-controller>`__, `Receive +History <#receive-history>`__, and `Alarm Set <#alarm-set>`__. + +The HEADERS stream is created here, if necessary. (Throughout the code, +you can see checks whether the connection is in HTTP mode or not. Even +though gQUIC means that HTTP is used, our library supports a non-HTTP +mode, in which there is no HEADERS stream. This was done for testing +purposes and made possible the echo and md5 client and server programs.) + +Server +------ + +After initializing the common structures in ``new_conn_common()``, +server-specific initialization continues in +``lsquic_gquic_full_conn_server_new()``. + +The HANDSHAKE stream is created. The handler (see +``lsquic_server_hsk_stream_if``) simply throws out data that it reads from +the client. + +Outgoing packets are inherited -- they will be sent during the next tick +-- and deferred incoming packets are processed. + +Client +------ + +The client's initialization takes place in +``lsquic_gquic_full_conn_client_new()``. Crypto session is created and the +HANDSHAKE stream is initialized. The handlers in +``lsquic_client_hsk_stream_if`` drive the handshake process. + +Incoming Packets +================ + +The entry point for incoming packets is ``ci_packet_in()``, which is +implemented by ``full_conn_ci_packet_in``. Receiving a packet restarts the +idle timer. + +The function ``process_incoming_packet`` contains some client-only logic +for processing version negotiation and stateless retry packets. In the +normal case, ``process_regular_packet()`` is called. This is where the +incoming process is decrypted, the `Receive +History <#receive-history>`__ is updated, ``parse_regular_packet()`` is +called, and some post-processing takes place (most importantly, +scheduling an ACK to be sent). + +The function ``parse_regular_packet`` is simple: It iterates over the +whole decrypted payload of the incoming packet and parses out frames one +by one. An error aborts the connection. + +ACK Merging +=========== + +Processing ACKs is `expensive <#handling-acks>`__. When sending data, a +batch of incoming packets is likely to contain an ACK frame each. The +ACK frame handler, ``process_ack_frame()``, merges consecutive ACK frames +and stores the result in `fc_ack`_. The ACK is processed +during the `next tick <#ticking>`__. If the two ACK cannot be merged +(which is unlikely), the cached ACK is processed immediately and the new +ACK is cached. + +Caching an ACK has a non-trivial memory cost: the 4KB-plus data +structure ``ack_info`` accounts for more than half of the size of the +``full_conn`` struct. Nevertheless, the tradeoff is well worth it. ACK +merging reduces the number of calls to ``lsquic_send_ctl_got_ack()`` by a +factor of 10 or 20 in some high-throughput scenarios. + +Ticking +======= + +When a `connection is processed by the +engine <#processing-connections>`__, the engine calls the connection's +``ci_tick()`` method. This is where most of the connection logic is +exercised. In the full gQUIC connection, this method is implemented by +``full_conn_ci_tick()``. + +The following steps are performed: + +- A cached ACK, if it exists, is processed + +- Expired alarms are rung + +- Stream read events are dispatched + +- An ACK frame is generated if necessary + +- Other control frames are generated if necessary + +- Lost packets are rescheduled + +- More control frames and stream resets are generated if necessary + +- HEADERS stream is flushed + +- Outgoing packets that carry stream data are scheduled in four steps: + + a. High-priority `buffered packets <#buffered-queue>`__ are scheduled + + b. Write events are dispatched for high-priority streams + + c. Non-high-priority buffered packets are scheduled + + d. Write events are dispatched for non-high-priority streams + +- Connection close or PING frames are generated if necessary + +- Streams are serviced (closed, freed, created) + +.. _notable-code-4: + +Notable Code +============ + +TODO + +Full IETF Connection +******************** + +*Files: lsquic_full_conn_ietf.h, lsquic_full_conn_ietf.c* + +Overview +======== + +This module implements IETF QUIC +`Transport `_ +and +`HTTP/3 `_ logic, +plus several QUIC extensions. To attain an overall grasp of the code, +at least some familiarity with these protocols is required. To understand +the code in detail, especially *why* some things are done, a closer reading +of the specification may be in order. + +In some places, the code contains comments with references to the +specification, e.g. + +:: + + if (conn->ifc_flags & IFC_SERVER) + { /* [draft-ietf-quic-transport-34] Section 19.7 */ + ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION, + "received unexpected NEW_TOKEN frame"); + return 0; + } + +(A search for "[draft-ietf" will reveal over one hundred places in the +code thus commented.) + +The Full IETF Connection module is similar in structure to the `Full gQUIC +Connection`_ module, from which it originated. Some code is quite similar +as well, including logic for `ACK Merging`_ and `Ticking`_. + +Components +========== + +In this section, each member of ``ietf_full_conn`` is documented. + +ifc_conn +-------- + +The first member of the struct is the common connection object, +`lsquic_conn`_. + +It must be first in the struct because the two pointer are cast to each +other, depending on circumstances. + +ifc_cces +-------- + +This array holds eight connection CID elements. +See `Managing SCIDs`_. + +ifc_rechist +----------- + +This member holds the `packet receive history <#receive-history>`__. +The receive history is used to generate ACK frames. + +ifc_max_ackable_packno_in +------------------------- + +This value is used to detect holes in incoming packet number sequence. +This information is used to queue ACK frames. + +ifc_send_ctl +------------ + +This is the `Send Controller`_. It is used to +allocate outgoing packets, control sending rate, and process +acknowledgements. + +ifc_pub +------- + +This member holds the `Connection Public Interface`_ + +ifc_alset +--------- + +This is the `Alarm Set`_. It is used to set various timers +in the connection and the send controller. + +ifc_closed_stream_ids +--------------------- + +The two sets in this array hold the IDs of closed streams. + +There are two of them because of the uneven distribution of stream IDs. +The set data structure is meant to hold sequences without gaps. +It is more efficient to hold stream IDs for each stream type in +separate structures. + +ifc_n_created_streams +--------------------- + +Counters for locally initiated streams. Used to generate next +stream ID. + +ifc_max_allowed_stream_id +------------------------- + +Maximum allowed stream ID for each of the four (``N_SITS``) stream types. +This is used all over the place. + +ifc_closed_peer_streams +----------------------- + +Counts how many remotely-initiated streams have been closed. Because the +protocol mandates that the stream IDs be assigned in order, this allows us +to make some logical inferences in the code. + +ifc_max_streams_in +------------------ + +Maximum number of open streams the peer is allowed to initiate. + +ifc_max_stream_data_uni +----------------------- + +Initial value of the maximum amount of data locally-initiated unidirectional +stream is allowed to send. + +ifc_flags +--------- + +All kinds of flags. + +ifc_mflags +---------- + +More flags! + +ifc_send_flags +-------------- + +The send flags keep track of which control frames are queued to be sent. + +ifc_delayed_send +---------------- + +Some send flags are delayed. + +We stop issuing streams credits if peer stops opening QPACK decoder window. +This addresses a potential attack whereby client can cause the server to keep +allocating memory. See `Security Considerations in the QPACK Internet-Draft +`__. + +ifc_send +-------- + +This is the `Send Controller`_. It is used to allocate outgoing packets, +control sending rate, and process acknowledgements. + +ifc_error +--------- + +This struct records which type of error has occurred (transport or application)' +and the error code. + +ifc_n_delayed_streams +--------------------- + +Count how many streams have been delayed. + +When ``lsquic_conn_make_stream()`` is called, a stream may not be created +immediately. It is delayed if creating a stream would go over the +maximum number of stream allowed by peer. + +ifc_n_cons_unretx +----------------- + +Counts how many consecutive unretransmittable packets have been sent. + +Enough unretransittable sent packets in a row causes a PING frame to +be sent. This forces the peer to send an ACK. + +ifc_pii +------- + +Points to the selected priority iterator. + +The IETF Full Connection supports two priority mechanisms: the original +Google QUIC priority mechanism and the `HTTP/3 Extensible Priorities +`__. + +ifc_errmsg +---------- + +Holds dynamically generated error message string. + +Once set, the error string does not change until the connection is +destroyed. + +ifc_enpub +--------- + +This points to the `engine's public interface <#lsquic-engine-public>`__. + +ifc_settings +------------ + +Pointer to the engine settings. + +This member is superfluous -- the settings can be fetched from +``ifc_enpub->enp_settings``. + +ifc_stream_ids_to_ss +-------------------- + +Holds a queue of STOP_SENDING frames to send as response to remotely +initiated streams that came in after we sent a GOAWAY frame. + +ifc_created +----------- + +Time when the connection was created. This is used for the Timestamp +and Delayed ACKs extensions. + +ifc_saved_ack_received +---------------------- + +Time when cached ACK frame was received. See `ACK Merging`_. + +ifc_max_ack_packno +------------------ + +Holding the maximum packet number containing an ACK frame allows us +to ignore old ACK frames. One value per Packet Number Space is kept. + +ifc_max_non_probing +------------------- + +Maximum packet number of a received non-probing packets. This is used +for path migration. + +ifc_cfg +------- + +Local copy of a couple of transport parameters. We could get at them +with a function call, but these are used often enough to optimize +fetching them. + +ifc_process_incoming_packet +--------------------------- + +The client goes through version negotiation and the switches to the +fast function. The server begins to use the fast function immediately. + +ifc_n_slack_akbl +---------------- + +Number ackable packets received since last ACK was sent. A count is +kept for each Packet Number Space. + +ifc_n_slack_all +--------------- + +Count of all packets received since last ACK was sent. This is only +used in the Application PNS (Packet Number Space). (This is regular +PNS after the handshake completes). + +ifc_max_retx_since_last_ack +--------------------------- + +This number is the maximum number of ack-eliciting packets to receive +before an ACK must be sent. + +The default value is 2. When the Delayed ACKs extension is used, this +value gets modified by peer's ACK_FREQUENCY frames. + +ifc_max_ack_delay +----------------- + +Maximum amount of allowed after before an ACK is sent if the threshold +defined by ifc_max_retx_since_last_ack_ has not yet been reached. + +The default value is 25 ms. When the Delayed ACKs extension is used, this +value gets modified by peer's ACK_FREQUENCY frames. + +ifc_ecn_counts_in +----------------- + +Incoming ECN counts in each of the Packet Number Spaces. These counts +are used to generate ACK frames. + +ifc_max_req_id +-------------- + +Keeps track of the maximum ID of bidirectional stream ID initiated by the +peers. It is used to construct the GOAWAY frame. + +ifc_hcso +-------- + +State for outgoing HTTP/3 control stream. + +ifc_hcsi +-------- + +State for incoming HTTP/3 control stream. + +ifc_qeh +------- + +QPACK encoder streams handler. + +The handler owns two unidirectional streams: a) locally-initiated QPACK +encoder stream, to which it writes; and b) peer-initiated QPACK decoder +stream, from which it reads. + +ifc_qdh +------- + +QPACK decoder streams handler. + +The handler owns two unidirectional streams: a) peer-initiated QPACK +encoder stream, from which it reads; and b) locally-initiated QPACK +decoder stream, to which it writes. + +ifc_peer_hq_settings +-------------------- + +Peer's HTTP/3 settings. + +ifc_dces +-------- + +List of destination connection ID elements (DCEs). Each holds a DCID +and the associated stateless reset token. When lsquic uses a DCID, it +inserts the stateless reset token into a hash so that stateless resets +can be found. + +Outside of the initial migration, the lsquic client code does not switch +DCIDs. One idea (suggested in the drafts somewhere) is to switch DCIDs +after a period of inactivity. + +ifc_to_retire +------------- + +List of DCIDs to retire. + +ifc_scid_seqno +-------------- + +Sequence generator for SCIDs generated by the endpoint. + +ifc_scid_timestamp +------------------ + +List of timestamps for the generated SCIDs. + +This list is used in the SCID rate-limiting mechanism. + + +ifc_incoming_ecn +---------------- + +History indicating presence of ECN markings on most recent incoming packets. + +ifc_cur_path_id +--------------- + +Current path ID -- indexes `ifc_paths`_. + +ifc_used_paths +-------------- + +Bitmask of which paths in `ifc_paths`_ are being used. + +ifc_mig_path_id +--------------- + +Path ID of the path being migrated to. + +ifc_active_cids_limit +--------------------- + +This is the maximum number of CIDs at any one time this +endpoint is allowed to issue to peer. If the TP value exceeds ``cn_n_cces``, +it is reduced to it. + +ifc_active_cids_count +--------------------- + +This value tracks how many CIDs have been issued. It is decremented +each time a CID is retired. + +ifc_first_active_cid_seqno +-------------------------- + +Another piece of the SCID rate-limiting mechanism. + +ifc_ping_unretx_thresh +---------------------- + +Once the number consecutively sent non-ack-elicing packets +(`ifc_n_cons_unretx`_) exceeds this value, this endpoint will send +a PING frame to force the peer to respond with an ACK. + +The threshold begins at 20 and then made to fluctuate randomly between +12 and 27. + +ifc_last_retire_prior_to +------------------------ + +Records the maximum value of ``Retire Prior To`` value of the +`NEW_CONNECTION_ID frame +`_. + +ifc_ack_freq_seqno +------------------ + +Sequence number generator for ACK_FREQUENCY frames generated by this +endpoint. + +ifc_last_pack_tol +----------------- + +Last value of the ``Packet Tolerance`` field sent in the last +``ACK_FREQUENCY`` frame generated by this endpoint. + +ifc_last_calc_pack_tol +---------------------- + +Last *calculated* value of the ``Packet Tolerance`` field. + +ifc_min_pack_tol_sent +--------------------- + +Minimum value of the ``Packet Tolerance`` field sent. Only used for +statistics display. + +ifc_max_pack_tol_sent +--------------------- + +Maximum value of the ``Packet Tolerance`` field sent. Only used for +statistics display. + +ifc_max_ack_freq_seqno +---------------------- + +Maximum seen sequence number of incoming ``ACK_FREQUENCY`` frame. Used +to discard old frames. + +ifc_max_udp_payload +------------------- + +Maximum UDP payload. This is the cached value of the transport parameter. + +ifc_last_live_update +-------------------- + +Last time ``ea_live_scids()`` was called. + +ifc_paths +--------- + +Array of network paths. Most of the time, only one path is used when the +peer migrates. The array has four elements as a safe upper limit. + +ifc_u.cli +--------- + +Client-specific state. This is where pointers to "crypto streams" are +stored; they are not in the ``ifc_pub.all_streams`` hash. + +ifc_u.ser +--------- + +The server-specific state is only about push promises. + +ifc_idle_to +----------- + +Idle timeout. + +ifc_ping_period +--------------- + +Ping period. + +ifc_bpus +-------- + +A hash of buffered priority updates. It is used when a priority update +(part of the Extensible HTTP Priorities extension) arrives before the +stream it is prioritizing. + +ifc_last_max_data_off_sent +-------------------------- + +Value of the last MAX_DATA frame sent. This is used to limit the number +of times we send the MAX_DATA frame in response to a DATA_BLOCKED frame. + +ifc_min_dg_sz +------------- + +Minimum size of the DATAGRAM frame. Used by the eponymous extension. + +ifc_max_dg_sz +------------- + +Maximum size of the DATAGRAM frame. Used by the eponymous extension. + +ifc_pts +------- + +PTS stands for "Packet Tolerance Stats". Information collected here +is used to calculate updates to the packet tolerance advertised to the +peer via ACK_FREQUENCY frames. Part of the Delayed ACKs extension. + +ifc_stats +--------- + +Cumulative connection stats. + +ifc_last_stats +-------------- + +Copy of `ifc_stats`_ last time ``ci_log_stats()`` was called. Used +to calculate the difference. + +ifc_ack +------- + +One or more cached incoming ACK frames. Used for `ACK merging`_. + +Managing SCIDs +============== + +Source Connection IDs -- or SCIDs for short -- are stored in the `ifc_cces`_ +array. + +Each of ``struct conn_cid_elem`` contains the CID itself, the CID's port or +sequence number, and flags: + +- ``CCE_USED`` means that this Connection ID has been used by the peer. This + information is used to check whether the peer's incoming packet is using + a new DCID or reusing an old one when the packet's DCID does not match + this path's current DCID. + +- ``CCE_REG`` signifies that the CID has been registered with the user-defined + ``ea_new_scids()`` callback. + +- ``CCE_SEQNO`` means that the connection has been issued by this endpoint + and ``cce_seqno`` contains a valid value. Most of SCIDs are issued by + either endpoint, with one exception: The DCID included in the first few + packets sent by the client becomes an interim SCID for the server and it + does not have a sequence number. This "original" SCID gets retired 2 + seconds after the handshake succeeds, see the ``AL_RET_CIDS`` alarm. + +- ``CCE_PORT`` is used to mark the special case of hashing connections by + port number. In client mode, the lsquic engine may, under some circumstances, + hash the connections by local port number instead of connection ID. + In that case, ``cce_port`` contains the port number used to hash the + connection. + +Each CIDs is hashed in the of the "CID-to-connection" mapping that the engine +maintains. If it is not in the hash, incoming packets that use this CID as +DCID will not be dispatched to the connection (because the connection will not +be found). + +Path Migration +============== + +Stream Priority Iterators +========================= + +Creating Streams on the Server +============================== + +Calculating Packet Tolerance +============================ + +When the Delayed ACKs extension is used, we advertise our ``Packet Tolerance`` +to peer. This is the number of packets the peer can receive before having to +send an acknowledgement. By default -- without the extension -- the packet +tolerance is 2. + +Because we `merge ACKs <#ack-merging>`__, receiving more than one ACK between +ticks is wasteful. Another consideration is that a packet is not declared +lost until at least one RTT passes -- the time to send a packet and receive +the acknowledgement from peer. + +To calculate the packet tolerance, we use a feedback mechanism: when number +of ACKs per RTT is too high, we increase packet tolerance; when number of +ACKs per RTT is too low, we decrease packet tolerance. The feedback is +implemented with a `PID Controller `__: +the target is the number of ACKs per RTT, normalized to 1.0. + +See the function ``packet_tolerance_alarm_expired()`` as well as comments +in ``lsquic.h`` that explain the normalization as well as the knobs available +for tuning. + +The pre-normalized target is a function of RTT. It was obtained +empirically using netem. This function together with the default +PID controller parameters give good performance in the lab and in +some limited interop testing. + +Anatomy of Outgoing Packet +************************** + +Evanescent Connection +********************* + +Send Controller +*************** + +*Files: lsquic_send_ctl.h, lsquic_send_ctl.c* + +.. _overview-4: + +Overview +======== + +The Send Controller manages outgoing packets and the sending rate: + +- It decides whether packets can be sent + +- It figures out what the congestion window is + +- It processes acknowledgements and detects packet losses + +- It allocates packets + +- It maintains sent packet history + +The controller allocates, manages, splits, coalesces, and destroys +outgoing packets. It owns these packets. + +The send controller services two modules: + +- Full connection. gQUIC and IETF full connections use the send + controller to allocate packets and delegate packet-sending + decisions to it. + +- Stream. The stream uses the stream controller as the source of + outgoing packets to write STREAM frames to. + +Packet Life Cycle +================= + +A new outgoing packet is allocated and returned to the connection or the +stream. Around this time (just before or just after, depending on the +particular function call to get the packet), the packet is placed on the +Scheduled Queue. + +When the engine is creating a batch of packets to send, it calls +``ci_next_packet_to_send()``. The connection removes the next packet from +its Scheduled Queue and returns it. The engine now owns the outgoing +packet, but only while the batch is being sent. The engine *always +returns the packet* after it tries to send it. + +If the packet was sent successfully, it is returned via the +``ci_packet_sent`` call, after which it is appended to the Unacked Queue. +If the packet could not be sent, ``ci_packet_not_sent()`` is called, at +which point it is prepended back to the Schedule Queue to be tried +later. + +There are two ways to get off the Unacked Queue: being acknowledged or +being lost. When a packet is acknowledged, it is destroyed. On the other +hand, if it is deemed lost, it is placed onto the Lost Queue, where it +will await being rescheduled. + +Packet Queues +============= + +.. image:: lsquic-packet-queues.png + +Buffered Queue +-------------- + +The Buffered Queue is a special case. When writing to the stream occurs +outside of the event dispatch loop, new outgoing packets begin their +life in the Buffered Queue. They get scheduled during a connection tick, +making their way onto the Scheduled Queue. + +There are two buffered queues: one for packets holding STREAM frames +from the highest-priority streams and one for packets for streams with +lower priority. + +Scheduled Queue +--------------- + +Packets on the Scheduled Queue have packet numbers assigned to them. In +rare cases, packets may be removed from this queue before being sent +out. (For example, a stream may be cancelled, in which case packets that +carry its STREAM frames may end up empty.) In that case, they are marked +with a special flag to generate the packet number just before they are +sent. + +Unacked Queue +------------- + +This queue holds packets that have been sent but are yet to be +acknowledged. When a packet on this queue is acknowledged, it is +destroyed. + +The loss detection code runs on this queue when ACKs are received or +when the retransmission timer expires. + +This queue is actually three queues: one for each of the IETF QUIC's +Packet Number Spaces, or PNSs. The PNS_APP queue is what is used by +gQUIC and IETF QUIC server code. PNS_INIT and PNS_HSK are only used by +the IETF QUIC client. (IETF QUIC server handles those packet number +spaces in its mini conn module.) + +In addition to regular packets, the Unacked Queue holds `loss +records <#loss-records>`__ and `poisoned packets <#poisoned-packets>`__. + +Lost Queue +---------- + +This queue holds lost packets. These packets are removed from the +Unacked Queue when it is decided that they have been lost. Packets on +this queue get rescheduled after connection schedules a packet with +control frames, as those have higher priority. + +0-RTT Stash Queue +----------------- + +This queue is used by the client to retransmit packets that carry 0-RTT +data. + +Handling ACKs +============= + +Acknowledgements are processed in the function +``lsquic_send_ctl_got_ack``. + +One of the first things that is done is ACK validation. We confirm that +the ACK does not contain any packet numbers that we did not send. +Because of the way we `generate packet numbers <#packet-numbers>`__, +this check is a simple comparison. + +The nested loops work as follows. The outer loop iterates over the +packets in the Unacked Queue in order -- meaning packet numbers +increase. In other words, older packets are examined first. The inner +loop processes ACK ranges in the ACK *backwards*, meaning that both +loops follow packets in increasing packet number order. It is done this +way as an optimization. The (previous) alternative design of looking +up a packet number in the ACK frame, even if using binary search, is +slower. + +The code is optimized: the inner loop has a minimum possible number of +branches. These optimizations predate the more-recent, higher-level +optimization. The latest ACK-handling optimization added to the code +combines incoming ACKs into a single ACK (at the connection level), thus +reducing the number of times this loop needs to be used by a lot, +sometimes by a significant factor (when lots of data is being sent). +This makes some of the code-level optimizations, such as the use of +``__builtin_prefetch``, an overkill. + +Loss Records +============ + +A loss record is a special type of outgoing packet. It marks a place in +the Unacked Queue where a lost packet had been -- the lost packet itself +having since moved on to the Lost Queue or further. The loss record and +the lost packet form a circular linked list called the "loss chain." +This list contains one real packet and zero or more loss records. The +real packet can move from the Unacked Queue to the Lost Queue to the +Scheduled Queue and back to the Unacked Queue; its loss records live +only on the Unacked Queue. + +We need loss records to be able to handle late acknowledgements -- those +that acknowledge a packet *after* it has been deemed lost. When an +acknowledgment for any of the packet numbers associated with this packet +comes in, the packet is acknowledged and the whole loss chain is +destroyed. + +Poisoned Packets +================ + +A poisoned packet is used to thwart opportunistic ACK attacks. The +opportunistic ACK attack works as follows: + +- The client requests a large resource + +- The server begins sending the response + +- The client sends ACKs for packet number before it sees these packets, + tricking the server into sending packets faster than it would + otherwise + +The poisoned packet is placed onto the Unacked Queue. If the peer lies +about packet numbers it received, it will acknowledge the poisoned +packet, in which case it will be discovered during ACK processing. + +Poisoned packets cycle in and out of the Unacked Queue. A maximum of one +poisoned packet is outstanding at any one time for simplicity. (And we +don't need more). + +Packet Numbers +============== + +The Send Controller aims to send out packets without any gaps in the +packet number sequence. (The only exception to this rule is the handling +of poisoned packets, where the gap is what we want.) Not having gaps in +the packet number sequence is beneficial: + +- ACK verification is cheap + +- Send history updates are fast + +- Send history uses very little memory + +The downside is code complexity and having to renumber packets when they +are removed from the Scheduled Queue (due to, for example, STREAM frame +elision or loss chain destruction) or resized (due to a path or MTU +change, for instance). + +Some scenarios when gaps can be produced inadvertently are difficult to +test or foresee. To cope with that, a special warning in the send +history code is added when the next packet produces a gap. This warning +is limited to once per connection. Having a gap does not break +functionality other than ACK verification, but that's minor. On the +other hand, we want to fix such bugs when they crop up -- that's why the +warning is there. + +Loss Detection and Retransmission +================================= + +The loss detection and retransmission logic in the Send Controller was +taken from the Chromium code in the fall of 2016, in the beginning of +the lsquic project. This logic has not changed much since then -- only +some bugs have been fixed here and there. The code bears no resemblance +to what is described in the QUIC Recovery Internet Draft. Instead, `the +much earlier +document `__, +describing gQUIC, could be looked to for reference. + +Congestions Controllers +======================= + +The Send Controller has a choice of two congestion controllers: Cubic +and BBRv1. The latter was translated from Chromium into C. BBRv1 does +not work well for very small RTTs. + +To cope with that, lsquic puts the Send Controller into the "adaptive CC" +mode by default. The CC is selected after RTT is determined: below a +certain threshold (configurable; 1.5 ms by default), Cubic is used. +Until Cubic or BBRv1 is selected, *both* CC controllers are used -- +because we won't have the necessary state to instantiate a controller +when the decision is made. + +Buffered Packet Handling +======================== + +Buffered packets require quite a bit of special handling. Because they +are created outside of the regular event dispatch, a lot of things are +unknown: + +- Congestion window + +- Whether more incoming packets will arrive before the next tick + +- The optimal packet number size + +The Send Controller tries its best to accommodate the buffered packets +usage scenario. + +ACKs +---- + +When buffered packets are created, we want to generate an ACK, if +possible. This can be seen in ``send_ctl_get_buffered_packet``, which +calls ``ci_write_ack()`` + +This ACK should be in the first buffered packet to be scheduled. Because +the Send Controller does not dictate the order of buffered packet +creation -- high-priority versus low-priority -- it may need to move (or +steal) the ACK frame from a packet on the low-priority queue to a packet +on the high-priority queue. + +When buffered packets are finally scheduled, we have to remove ACKs from +them if another ACK has already been sent. This is because Chrome errors +out if out-of-order ACKs come in. + +Flushing QPACK Decoder +---------------------- + +The priority-based write events dispatch is emulated when the first +buffered packet is allocated: the QPACK decoder is flushed. Without it, +QPACK updates are delayed, which may negatively affect compression +ratio. + +Snapshot and Rollback +===================== + +The Send Controller snapshot and rollback functionality was implemented +exclusively for the benefit of the optimized ``lsquic_stream_pwritev`` +call. + +Complexity Woes +=============== + +The Send Controller is complicated. Because we write stream data to +packets directly and packets need to be resized, a lot of complexity +resides in the code to resize packets, be it due to repathing, STREAM +frame elision, or MTU changes. This is the price to be paid for +efficiency in the normal case. + + +Alarm Set +********* + +*Files: lsquic_alarmset.h, lsquic_alarmset.c, test_alarmset.c* + +TODO + +Tickable Queue +************** + +*Files: lsquic_engine.c, lsquic_min_heap.h, lsquic_min_heap.c* + +The Tickable Queue is a min-heap used as a priority queue. Connections +on this queue are in line to be processed. Connections that were last +ticked a longer time ago have higher priority than those ticked +recently. (``cn_last_ticked`` is used for ordering.) This is intended to +prevent starvation as multiple connections vye for the ability to send +packets. + +The way the min-heap grows is described in `Growing +Min-Heaps <#growing-min-heaps>`__. + +Advisory Tick Time Queue +************************ + +*Files: lsquic_attq.h, lsquic_attq.c* + +This data structure is a mini-heap. Connections are ordered by the value +of the next time they should be processed (ticked). (Because this is not +a hard limit, this value is advisory -- hence its name.) + +This particular min-heap implementation has two properties worth +highlighting: + +Removal of Arbitrary Elements +============================= + +When a connection's next tick time is updated (or when the connection is +destroyed), the connection is removed from the ATTQ. At that time, it +may be at any position in the min-heap. The position is recorded in the +heap element, ``attq_elem->ae_heap_idx`` and is updated when elements are +swapped. This makes it unnecessary to search for the entry in the +min-heap. + +Swapping Speed +============== + +To make swapping faster, the array that underlies the min-heap is an +array of *pointers* to ``attq_elem``. This makes it unnecessary to update +each connection's ``cn_attq_elem`` as array elements are swapped: the +memory that stores ``attq_elem`` stays put. This is why there are both +``aq_elem_malo`` and ``aq_heap``. + +CID Purgatory +************* + +Memory Manager +************** + +Malo Allocator +************** + +Receive History +*************** + +*Files: lsquic_rechist.h, lsquic_rechist.c, test_rechist.c* + +.. _overview-5: + +Overview +======== + +The reason for keeping the history of received packets is to generate +ACK frames. The Receive History module provides functionality to add +packet numbers, truncate history, and iterate over the received packet +number ranges. + +.. _data-structures-3: + +Data Structures +=============== + +.. _overview-6: + +Overview +-------- + +The receive history is a singly-linked list of packet number ranges, +ordered from high to low: + +.. image:: rechist-linked-list.png + +The ordering is maintained as an invariant with each addition to the +list and each truncation. This makes it trivial to iterate over the +ranges. + +To limit the amount of memory this data structure can allocate, the +maximum number of elements is specified when Receive History is +initialized. In the unlikely case that that number is reached, new +elements will push out the elements at the tail of the linked list. + +Memory Layout +------------- + +In memory, the linked list elements are stored in an array. Placing them +into contiguous memory achieves three goals: + +- Receive history manipulation is fast because the elements are all + close together. + +- Memory usage is reduced because each element does not use pointers to + other memory locations. + +- Memory fragmentation is reduced. + +The array grows as necessary as the number of elements increases. + +The elements are allocated from and returned to the array with the aid +of an auxiliary data structure. An array of bitmasks is kept where each +bit corresponds to an array element. A set bit means that the element is +allocated; a cleared bit indicates that the corresponding element is +free. + +To take memory savings and speed further, the element array and the +array of bitmasks are allocated in a single span of memory. + +.. image:: rechist-memory-layout.png + +rechist_elem +------------ + +``re_low`` and ``re_count`` define the packet range. To save memory, we +assume that the range will not contain more than 4 billion entries and +use a four-byte integer instead of a second ``lsquic_packno_t``. + +``re_next`` is the index of the next element. Again, we assume that there +will be no more than 4 billion elements. The NULL pointer is represented +by ``UINT_MAX``. + +This struct is just 16 bytes in size, which is a nice number. + +lsquic_rechist +-------------- + +``rh_elems`` and ``rh_masks`` are the element array and the bitmask array, +respectively, as described above. The two use the same memory chunk. + +``rh_head`` is the index of the first element of the linked list. + +The iterator state, ``rh_iter``, is embedded into the main object itself, +as there is no expectation that more than one iterations will need to be +active at once. + +.. _notable-code-5: + +Notable Code +============ + +Inserting Elements +------------------ + +Elements may be inserted into the list when a new packet number is added +to history via ``lsquic_rechist_received()``. If the new packet number +requires a new range (e.g. it does not expand one of the existing +ranges), a new element is allocated and linked. + +There are four cases to consider: + +1. Inserting the new element at the head of the list, with it becoming + the new head. (This is the most common scenario.) The code that + does it is labeled ``first_elem``. + +2. Appending the new element to the list, with it becoming the new tail. + This code is located right after the ``while`` loop. + +3. Inserting the new element between two existing elements. This code is + labeled ``insert_before``. + +4. Like (3), but when the insertion is between the last and the + next-to-last elements and the maximum number of elements has been + reached. In this case, the last element's packet number + information can simply be replaced. This code is labeled + ``replace_last_el``. + +Growing the Array +----------------- + +When all allocated elements in ``rh_elems`` are in use +(``rh_n_used >= rh_n_alloced``), the element array needs to be expanded. +This is handled by the function ``rechist_grow``. + +Note how, after realloc, the bitmask array is moved to its new location +on the right side of the array. + +Handling Element Overflow +------------------------- + +When the array has grown to its maximum allowed size, allocating a new +element occurs via reusing the last element on the list, effectively +pushing it out. This happens in ``rechist_reuse_last_elem``. + +The first loop finds the last linked list element: that's the element +whose ``re_next`` is equal to ``UINT_MAX``. + +Then, the second loop finds the element that points to the last element. +This is the next-to-last (penultimate) element. This element's next +pointer will now be set to NULL, effectively dropping the last element, +which can now be reused. + +Iterating Over Ranges +--------------------- + +Iteration is performed by the ``lsquic_rechist_first`` and +``lsquic_rechist_next`` pair of functions. The former resets the internal +iterator. Only one iteration at a time is possible. + +These functions have a specific signature: they and the pointer to the +receive history are passed to the ``pf_gen_ack_frame`` function, which +generates an ACK frame. + +Clone Functionality +------------------- + +The Receive History can be initialized from another instance of a +receive history. This is done by ``lsquic_rechist_copy_ranges``. This +functionality is used during connection promotion, when `Tiny Receive +History <#tiny-receive-history>`__ that is used by the `IETF mini +connection <#mini-ietf-connection>`__ is converted to Receive History. + +Tiny Receive History +******************** + +*Files: lsquic_trechist.h, lsquic_trechist.c, test_trechist.c* + +.. _overview-7: + +Overview +======== + +The Tiny Receive History is similar to `Receive +History <#receive-history>`__, but it is even more frugal with memory. +It is used in the `IETF mini connection <#mini-ietf-connection>`__ as a +more costly `alternative to using bare bitmasks <#imc-recvd-packnos>`__. + +Because it is so similar to Receive History, only differences are +covered in this section. + +Less Memory +=========== + +No Trechist Type +---------------- + +There is no ``lsquic_trechist``. The history is just a single 32-bit +bitmask and a pointer to the array of elements. The bitmask and the +pointer are passed to all ``lsquic_trechist_*`` functions. + +This gives the user of Tiny Receive History some flexibility and saves +memory. + +Element +------- + +The linked list element, ``trechist_elem``, is just 6 bytes in size. The +assumptions are: + +- No packet number is larger than 2\ :sup:`32` - 1 + +- No packet range contains more than 255 packets + +- Linked list is limited to 256 elements + +Head Does Not Move +================== + +Because of memory optimizations described above, the head element is +always at index 0. The NULL pointer ``te_next`` is indicated by the value +0 (because nothing points to the first element). + +Array Does Not Grow +=================== + +The size of the element array is limited by the 32-bit bitmask. As a +further optimization, the number of ranges is limited to 16 via the +``TRECHIST_MAX_RANGES`` macro. + +Insertion Range Check +===================== + +A packet range spanning more than 255 (UCHAR_MAX) packets cannot be +represented. This will cause a failure, as it is checked for in the +code. + +This many packets are unlikely to even be required to complete the +handshake. If this limit is hit, it is perhaps good to abort the mini +connection. + +Set64 +***** + +Appendix A: List of Data Structures +*********************************** + +The majority of data structures employed by lsquic are linked lists and, +to a lesser extent, arrays. This makes the code simple and fast +(assuming a smart memory layout). + +Nevertheless, a few places in the code called for more involved and, at +times, customized data structures. This appendix catalogues them. + +This is the list of non-trivial data structures implemented in lsquic: + +Ring Buffer Linked Lists +======================== + +- `Receive History <#receive-history>`__ + +- `Tiny Receive History <#tiny-receive-history>`__ + +Hashes +====== + +- lsquic_hash + +- hash_data_in + +Min-heaps +========= + +- `Advisory Tick Time Queue <#advisory-tick-time-queue>`__ + +- lsquic_min_heap + +Bloom Filters +============= + +- CID Purgatory + + +Appendix B: Obsolete and Defunct Code +************************************* + +Mem Used +======== + +Engine History +============== + +QLOG +==== + + +.. [1] + This is due to the limitation of the QPACK library: the decoder can + read input one byte at a time, but the encoder cannot write output + one byte at a time. It could be made to do that, but the effort is + not worth it. + +.. [2] + Mini conn structs are allocated out of the `Malo + Allocator <#malo-allocator>`__, which used to be limited to objects + whose size is a power of two, so it was either fitting it into 128 + bytes or effectively doubling the mini conn size. diff --git a/docs/lsquic-packet-queues.png b/docs/lsquic-packet-queues.png new file mode 100644 index 0000000000000000000000000000000000000000..8cda9240528d8ea99b07f4ce25e70db686838d03 GIT binary patch literal 25056 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 literal 0 HcmV?d00001 diff --git a/docs/rechist-linked-list.png b/docs/rechist-linked-list.png new file mode 100644 index 0000000000000000000000000000000000000000..a49a751b8f858f4e484a494f5a7f53f44053ae1d GIT binary patch literal 8854 zcmdsdXH=8h)~*yOQiUzOSg4yWy#)jmQ7MWFNbe#LN+5s&p$7yh7K)UBh^R=1gx;%x zfb>v9@6AvWLiqxmZ``x*81DQ3{>jM5yJlN!zH`m_Jc%;U)26$`ap}T^3v@bnwH{r# zKnfs!x2K{Ye(KIG7hkw=-CRfO)?;6b^>oTI?kDI7{ZYDW)H+n$VZG!JLqqyr(cHe3 zBm6{N>$1~zs>r_{USz}P_sqNT$O1lXZ?2i znkmDP=&K!h2rEtUtCM%=a?nN>D&r`{r8M6MN5Jt59D>mEMm4G@M?)oQ-_O+D(1*TZ zSKh}i zx7TQSh@NO^?N!|F0+l);MF~B-!&C02gzWo7T}j?CKuZf>KWcpNOfL2~w^ zeJ2@}jd#Y-yq2eHK2zM@A4(kCJp55>68-WTE^A(U!s1L=nY%5S$J zk&^d^RPAHSDO^2bVOU3$LUgNC?$nv^r#gz)&F{pqV;|++dBZupk2w87_n=K;J8sB^ z&xe_%JCtE4nFl%e@H0)|vrv(rAFlOL=;wD{ng7YS90D7{yq~8xIJ5Rr)JQu)9Opz= zAY>GAms6!&!05e*=9!dXqO$s#zstpD+_$SRxv~|X%-FBHZVh}idsskwCZ%Vxap5fD zFYTV*f1Rm*?P(ub)@bPLdAsta)~S?&eI?mn|Gzra0K~5Z#5q=P)dsZhn(*V-LpevJuIWh74-?4U?PJd z>IudOIzT|bD|#R&*KxLO*B${m5k}NHp-y%dOY|)Ww@;P%l(p>`|GToA#u&2pw8QE6 z#V&ob4R~XXAcVDO*}kjxL?_9i5}RP@Fm*B*EnX{Lx7*uO0)^r8?iXIFPuR{G{@27< zO9Ya_n0r*@=M?d4G^2ez#FE;(+ahNikngP#9dmTtJ#}aSAKX)R%x;gN#tZ0ue9w(T zWtTz&@fwf}O33!20aVy9H(VuKI}Rm>_N-ou_5lpQ3x{2+?BU&d)!`?+0l!VM?r#iL z*KPhBCp7rjNe}t^flqpl83B;R_WJI<@y&Rb&M4;QW2eSZ;G(<(h!+<{*f22JQjX!P zRC?{zsPF84n#-e@Z?cuAZ@}r6rE@}TN=C{>32TwchicJsd6 zm_z&8C+9lGDj4qM>%;7lI5RJM2%t{0sCezShy$<6;ybiGjBpP=d<>1)WQvSmOt3r= z8?&i4xhAQE;noh{klnjwAnW%{7;RK{WPEb`+SVUfc@$g()11QA!IgSm{oY42F(-C8 ztk)Ug4lz9^@AC;icbk)QZp11pR?jTBg;j}`j$cAw5~&xhUMzMT_h7iV(zkGLbACmr z@1k0M<7p{y3e6pwJAGfzV*KezIU**tW#XK^JL=$dS*B~;J=^7wiCIGuv?lDh4}q!& z@4S(l`jU@~JgMK?O?9}hZz)%XiHDDwPI6FaLzXvq%RW@^##Tq{yy89a1~*=j4_B($ z|13TFdwe?-=Ey9li-J#i#~sTkSNPSYaN3C!)V!d|uo*RBHRIXyS>6RTE$+j%x3?4f0pHFj)w6k(V9QS&!p{{PbKu^=U@%YZ$f33>ZIig!pEf?CsPTcr9h6t}V zWN-dsifEpjLlOS3)<8jHJ_&SklPWQt#RQ?>6EGFt-=htb;7Qv}mv6#`n)@8my-OcO zVrf2!#1yN5VYgk;$H$L$g{^0>%Q>^PJqKDBhrdX*dOVLwDYd%v;`?ZwmL zYW}*$y65Ns``f2!w`U=EhRmOP%hWWKctth<*@d>QjhH~1-!$*-2Q1khEj#wqUH=?_ zYtpaYl)V2s)izDHl|wy-P>cs)@r6(it(c=}KFrKRxE>=bQ)IA_38p#7X{Gt^A5ShncHB&`ulk#k@6x8kRF{br~fDb9v-lhjm=9X}S0}K^_ zReaHY)US1>mg(H6gZatOPElj)Vs6gJcDFcr+RT#%sM_T{Bwg%N^a12V?1|k8`83ax zQ*!PdAxU{jWmbyzL@cgUjj@v{2BgeuWSXUsKMHfEYwrR;SH~Tu)>(`aMY6?ls%$p5 z*!T9A{=&5oE|)@PRU@|3$-g4=i{2vD(t>-SfIuAuN*4Yt6YP8;)$Y{u(tH9oFmS_1 zbCkomaP_yy7Q6Arg=+iE21?)G3lt5^$HVpev+>~lY<+01%<)pRzPZ_wCALbpDQLPd zU|;*%n{8!(xW0y2*NDXI%Nfi<07IaoDbF!mMSPbrX#KdXqyd^4$r6s!ZltTMP^AZc zk^P#im;QC^i>d2WBH*!5cK3%#n~?Fb=x{Tws1#=!X#cR) zIJvZH(|El4UL^bPCxHwbm(5DIg}(f%>H(~2cDmovZ!?_=)PhVPNntmiqo+4Rtm-Q= zxL0S3VKY-r?!}p?=r`vVAwx?+{tWYwj$;OEU&Ru(MRVtV*gcNT{@3`Xe$X+W7Lq|9 zOwK>P)}d|0;TJh#%RzG4@459%n9YLGHlGBIKPJ6B@Txy7RY=gmgHue^ADVkd{GN&| z&rz_nW*#Zt)j;mC5G0HLhDJfGX1IqXvarH)jJCQX2X^#GVXp$k%qk0j9AGV-kv_xO z^_O_oe3lZXr2&`k_U72_cm7Zd`JwQ&mY%9(MDW%X{Uxvd4;KOC(&W2x>4JW-A}D|z zI()`MTLGpxwF*qg{2}L)hy3vHio>Jd^2kWZVme@z zZ}Z7AeMqSQ4NXa`o)iPCy10zYegc$*|7WSb*MqoRwc*4@Lj^o_(3+f8rYs5i?>rP0 zVUe`e_-$1Pk5&EojoVCO>nF5YgDhl#*+%Q2UrdtgOO0fPNBXw{2kx%@JsULzT0>${VJ4%k%C+UluP2WwG=nq~6FLTYh~c^sS%BSSjaAy-kt_~nQHc## zO1uatW5V{fgqlCd7D+xN@a|>6YX&}Ss~^Ig8U3m%e=%I#Gnc8LAZ~I~v*V#Em5q>X z(NriXqe>*BWYQbuAD3)(sYhn~+K&oT8=wSH20!vcFsH2< z`=?t+%L_TjGIvM(y&K$I>Hab4{NM~BIT&)5ol0gt%U9rn-fKG*f zKre4(UJ2v8Ix$Gz7UL(hKsRwcU{=#eghc8VWut+M6puK?CI2PV?BTSTbtglWm2dK= zE0LK!3vohQPPtZ;gJ@&>f-0wEG3i`i}036RC1)?pPZNo|D|v zkWCiY!429pM^m(j!OAB?40GyKG>yeg@!$>lK3mrI)G4q#?r>c&W4(ewndkeZp)sW* z`+-jNU($?eH~Xte4?oEfgOdKTyge#a$_f=#mj_47@rVBoR6QwStI9=X@r@Y14EOX;2Vn$P#5hW;ea43wYfVn$jjXcAUxih7+N zHurxf;PxtvgFd4sdrjTviZ@n~G#`%W-Lo>4Lr>bnCD*UoD9j!rm4MMjcLfT0g-e7a zVC#S3+^4g4gQKtLo746>Mlkp}<-ZmK(1O3j+S9nTZPb;xYK9+O>CV4Z5TitF52iy< zY`h$w$}5(;!Q8|Hq4f4fi&1OYy0SXMbi&Mae&6_Dmr8S>8G7Q z&;MZ_MEF4XxGctRQw|*8e~!i7{fa*i4JMhFx$MxXcwloo4;Pz z_2;RZ+y*(vpSF|4BDEHe`l!37ENb5%M{yUMav`;oQI5BF=5AiJZroOwYY)Q^TPa@Y zm+)4Uv-*d1=c2@W7XjJC6ssgr3e&rj~mGlc1Mwtr$( zd@G}bUCVgLg)>S_5gwJY`|7Ua0xfKL#>%p_wdQ&kqa8(tZs9w!F*hGS8@lH@HD2>ysjBdp zigf9>HEpvX0A!oY0gTcYliBo?x>^*O51>`D>y-{Qi4HaLlMxeO<0-aZtZb$dcA(Vq=5WN9>_ zp^Xjm$!(PyM$bKNAr&rUT)kPh5tb=WL~x)PEoKq~R4 z`pTgf0UiPH68;<4Rrr2gX~U%@w@vXt3LB%}lKk?PGR8FT2IrV44F-cUu<6j68RHCv zJlVR?67dno>RQdAA8C}HJScRhi}Qp30^7L$jw!c>kUexk)viQOUFv@|8v-O2pw21< z!%NSw(Q~#)l+=hJlDt-?!Xh3|ABr6hVICia`DM!DT%7{$44Q{)f&r0jNAb|2{ zVV$X4tuP!9%qzk86d0aYE$aZDr*@9Y8U3!e4Z83)j`-Fmo+dK!peS)I0QP&T9?2bU z;yWDFsz~jrkZU59QJ{;ud?-Po$#R~^lJqg_9#*f|%K%vUj%Es5C*vP15>x^>LK%M5 zuBZVDDz9MjWB&oU=#zRyH@4DWPEs1ATo#)R z{dA$4itQ2D2o~$_{<#kB6#S*XB@KtZvFiS^moUC~u9{f66ms5NAZMMQjMJ)iSaCOnLySn?GBc<-|(+&@kxV&dnrp;YL zC%*b#lOS-3P8s|M`dqOiwjCZ}E{J88p=#@Gv#J^e81B`2S&x$SkhO^wR~CP&irhq# zVjE+SFI`r254e1ogb39-_a>)lf&yV6Ac|={6{=61X2SQ4wvR?0Pf@pt#Y@xyGfgYv zf2kV4?@r86)W`iBzE*KZYWL_%TCOKtaxCIDQBw}FM*yVL4Gce{_$5v-!t^u$5xr2M z9aUXKr=b;RyWr2}>5EKVFq%QOjs~<8<`>crtg5N~jU`cEK1lUdBNg;!?-%|NR@#z` zVz&Z$)}@xdML$g?&I6}TA6 zts}uH-E<&9OYc0Ri(pwX+Op&8_#*;tJ@7T{9Vi2d{^^o;x;Ok3fCrR(TbatyXvGJ= z_~R>Ae_4k6gJT#BlT12dy=9(vS01(%M#zd%g(N3v@(dX(Y;EmFY!=(R;QHJKpf*cx zymj?b*kWa&0Nmm}r*pvU+ zIf^n{=i`RO8|x9@&p|osry5e}taS+}&N93U8SH)`j2SRyV)0S)E(jbS<0f39f@O#uLH`0lpRyI5F> zB?2ol>Z^DxIb?;{IUL6pZ|2SQqB|U)5Va*4C8y- zC8%dZ*;uuBp8kpvQ%@&_b;HrW1}~mMt{pwT>1;T!XYf=+kn|U5c%SiM42zJ3u_IRY zqPO|;&dB(@?7OEx{27}En4kGJHrQlm0& z1VTMjDeJIH@tb8Jae62F;2t8XF}B@3SYsNVVs!{9Y7nez0PYPU!i*S-{42Wq`3CII zN8&%w0Q+~z)gE{|<}6-Aa$6y2Ww)Xv-l{CVbsnwrLFEibcuiV$E$UBf&2Oa`F}0{b zxxF>+Mw8{<*zWG~$lN8~(7{7H_DKw-9m%?Pknc{-$#UG(nENAw%uw!yit~&`a8`Xd z#l>D=nyUav@p_x37dB=LhJ${FHzk` zOQQtE7IP|fa${ytB%2C-Y2^=Ie*3S+><>)C z7S%wpq7S;|=Ku177T*iq4yR19t9&eEEI58j z5D1t}!f89tIeVcr6`><^Pyd%DJ>^jO#NH;49{W2VN9!v8{09?zd&o~Q3ElGP9~|p5 zyX|{46iS6ad98m?u`+z=8K5`1G5?@zo$lK3J6bpv$4&(OLkUNT-p|TOf$RU_T-nZO z;Rs?;kysG5m9O{*#j9hqm}bR1@eiK&v#w`gqV)IVr8BBH>-p<5`nPMac4?pU)Z2d=3=J*vs5nn+?1jI{0}7LTxS{K zoYQ_&CF|&RJbUi^1n*h&F9z5Z9k+SU{dCtnw@qoB?*Dt^3DQTcQ-%{VHNT}_lv22 literal 0 HcmV?d00001 diff --git a/docs/rechist-memory-layout.png b/docs/rechist-memory-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..cdde72a1000b0b347da3cc3ab4c4cbbe308fd410 GIT binary patch literal 12524 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 H_x}F}j5BpC literal 0 HcmV?d00001 diff --git a/docs/stream-http3-framing.png b/docs/stream-http3-framing.png new file mode 100644 index 0000000000000000000000000000000000000000..e315dc269bb9b0bdde62bf4bb58b2d60db0834ac GIT binary patch literal 22699 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 ITm=#M9|uRWA^-pY literal 0 HcmV?d00001 diff --git a/include/lsquic.h b/include/lsquic.h index 29b4a5e..862981c 100644 --- a/include/lsquic.h +++ b/include/lsquic.h @@ -25,7 +25,7 @@ extern "C" { #define LSQUIC_MAJOR_VERSION 2 #define LSQUIC_MINOR_VERSION 29 -#define LSQUIC_PATCH_VERSION 5 +#define LSQUIC_PATCH_VERSION 6 /** * Engine flags: diff --git a/src/liblsquic/lsquic_conn.c b/src/liblsquic/lsquic_conn.c index cd48d4b..028b1d9 100644 --- a/src/liblsquic/lsquic_conn.c +++ b/src/liblsquic/lsquic_conn.c @@ -242,7 +242,10 @@ void lsquic_generate_scid (void *ctx, struct lsquic_conn *lconn, lsquic_cid_t *scid, unsigned len) { - lsquic_generate_cid(scid, len); + if (len) + lsquic_generate_cid(scid, len); + else + scid->len = len; } diff --git a/src/liblsquic/lsquic_engine.c b/src/liblsquic/lsquic_engine.c index 717c3a4..0a11837 100644 --- a/src/liblsquic/lsquic_engine.c +++ b/src/liblsquic/lsquic_engine.c @@ -64,7 +64,6 @@ #include "lsquic_sfcw.h" #include "lsquic_hash.h" #include "lsquic_conn.h" -#include "lsquic_send_ctl.h" #include "lsquic_full_conn.h" #include "lsquic_util.h" #include "lsquic_qtags.h" @@ -2908,6 +2907,8 @@ process_connections (lsquic_engine_t *engine, conn_iter_f next_conn, } } + cub_flush(&engine->new_scids); + if ((engine->pub.enp_flags & ENPUB_CAN_SEND) && lsquic_engine_has_unsent_packets(engine)) send_packets_out(engine, &ticked_conns, &closed_conns); @@ -2948,7 +2949,6 @@ process_connections (lsquic_engine_t *engine, conn_iter_f next_conn, } } - cub_flush(&engine->new_scids); cub_flush(&cub_live); cub_flush(&cub_old); } diff --git a/src/liblsquic/lsquic_full_conn_ietf.c b/src/liblsquic/lsquic_full_conn_ietf.c index 9ee2bb3..b563804 100644 --- a/src/liblsquic/lsquic_full_conn_ietf.c +++ b/src/liblsquic/lsquic_full_conn_ietf.c @@ -158,6 +158,7 @@ enum more_flags MF_SEND_WRONG_COUNTS= 1 << 5, /* Send wrong ECN counts to peer */ MF_WANT_DATAGRAM_WRITE = 1 << 6, MF_DOING_0RTT = 1 << 7, + MF_HAVE_HCSI = 1 << 8, /* Have HTTP Control Stream Incoming */ }; @@ -402,8 +403,6 @@ struct ietf_full_conn /* App PNS only, used to calculate was_missing: */ lsquic_packno_t ifc_max_ackable_packno_in; struct lsquic_send_ctl ifc_send_ctl; - struct lsquic_stream *ifc_stream_hcsi; /* HTTP Control Stream Incoming */ - struct lsquic_stream *ifc_stream_hcso; /* HTTP Control Stream Outgoing */ struct lsquic_conn_public ifc_pub; lsquic_alarmset_t ifc_alset; struct lsquic_set64 ifc_closed_stream_ids[N_SITS]; @@ -424,9 +423,6 @@ struct ietf_full_conn struct conn_err ifc_error; unsigned ifc_n_delayed_streams; unsigned ifc_n_cons_unretx; - const struct lsquic_stream_if - *ifc_stream_if; - void *ifc_stream_ctx; const struct prio_iter_if *ifc_pii; char *ifc_errmsg; struct lsquic_engine_public @@ -452,7 +448,6 @@ struct ietf_full_conn unsigned ifc_max_retx_since_last_ack; lsquic_time_t ifc_max_ack_delay; uint64_t ifc_ecn_counts_in[N_PNS][4]; - uint64_t ifc_ecn_counts_out[N_PNS][4]; lsquic_stream_id_t ifc_max_req_id; struct hcso_writer ifc_hcso; struct http_ctl_stream_in ifc_hcsi; @@ -1672,7 +1667,6 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub, for (i = 0; i < 4; ++i) { conn->ifc_ecn_counts_in[pns][i] = imc->imc_ecn_counts_in[pns][i]; - conn->ifc_ecn_counts_out[pns][i] = imc->imc_ecn_counts_out[pns][i]; } conn->ifc_incoming_ecn = imc->imc_incoming_ecn; conn->ifc_pub.rtt_stats = imc->imc_rtt_stats; @@ -2040,6 +2034,7 @@ can_issue_cids (const struct ietf_full_conn *conn) can = ((1 << conn->ifc_conn.cn_n_cces) - 1 != conn->ifc_conn.cn_cces_mask) + && conn->ifc_enpub->enp_settings.es_scid_len && conn->ifc_active_cids_count < conn->ifc_active_cids_limit; LSQ_DEBUG("can issue CIDs: %d (n_cces %hhu; mask: 0x%hhX; " "active: %hhu; limit: %hhu)", @@ -3775,7 +3770,7 @@ handshake_ok (struct lsquic_conn *lconn) if (conn->ifc_settings->es_dplpmtud) conn->ifc_mflags |= MF_CHECK_MTU_PROBE; - if (can_issue_cids(conn) && CN_SCID(&conn->ifc_conn)->len != 0) + if (can_issue_cids(conn)) conn->ifc_send_flags |= SF_SEND_NEW_CID; maybe_create_delayed_streams(conn); @@ -7379,8 +7374,7 @@ process_regular_packet (struct ietf_full_conn *conn, packet_in->pi_received); switch (st) { case REC_ST_OK: - if (!(conn->ifc_flags & (IFC_SERVER|IFC_DCID_SET)) - && (packet_in->pi_scid_len)) + if (!(conn->ifc_flags & (IFC_SERVER|IFC_DCID_SET))) record_dcid(conn, packet_in); saved_path_id = conn->ifc_cur_path_id; parse_regular_packet(conn, packet_in); @@ -7742,8 +7736,6 @@ ietf_full_conn_ci_packet_sent (struct lsquic_conn *lconn, s = lsquic_send_ctl_sent_packet(&conn->ifc_send_ctl, packet_out); if (s != 0) ABORT_ERROR("sent packet failed: %s", strerror(errno)); - ++conn->ifc_ecn_counts_out[ lsquic_packet_out_pns(packet_out) ] - [ lsquic_packet_out_ecn(packet_out) ]; /* Set blocked keep-alive for a [1,8] seconds */ if (packet_out->po_frame_types & (QUIC_FTBIT_BLOCKED|QUIC_FTBIT_STREAM_BLOCKED)) @@ -9398,7 +9390,7 @@ hcsi_on_new (void *stream_if_ctx, struct lsquic_stream *stream) struct ietf_full_conn *const conn = (void *) stream_if_ctx; const struct hcsi_callbacks *callbacks; - conn->ifc_stream_hcsi = stream; + conn->ifc_mflags |= MF_HAVE_HCSI; switch ((!!(conn->ifc_flags & IFC_SERVER) << 8) | conn->ifc_conn.cn_version) { @@ -9488,8 +9480,6 @@ hcsi_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) static void hcsi_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) { - struct ietf_full_conn *const conn = (void *) ctx; - conn->ifc_stream_hcsi = NULL; } @@ -9509,7 +9499,7 @@ apply_uni_stream_class (struct ietf_full_conn *conn, switch (stream_type) { case HQUST_CONTROL: - if (!conn->ifc_stream_hcsi) + if (!(conn->ifc_mflags & MF_HAVE_HCSI)) { LSQ_DEBUG("Incoming HTTP control stream ID: %"PRIu64, stream->id); @@ -9518,9 +9508,7 @@ apply_uni_stream_class (struct ietf_full_conn *conn, else { ABORT_QUIETLY(1, HEC_STREAM_CREATION_ERROR, - "Control stream %"PRIu64" already exists: cannot create " - "second control stream %"PRIu64, conn->ifc_stream_hcsi->id, - stream->id); + "Attempt to create second control stream"); lsquic_stream_close(stream); } break; diff --git a/src/liblsquic/lsquic_mini_conn.c b/src/liblsquic/lsquic_mini_conn.c index 10b53ac..b765f29 100644 --- a/src/liblsquic/lsquic_mini_conn.c +++ b/src/liblsquic/lsquic_mini_conn.c @@ -11,10 +11,6 @@ * spooled, if necessary. When mini connection is promoted to full * connection, the state, including spooled incoming packets, is transferred * to the full connection. - * - * Note that mini connections do not retransmit lost packets. This is to - * minimize the effect of magnification attacks. Clients like Chrome and - * Opera fall back to using TCP if QUIC handshake times out. */ diff --git a/src/liblsquic/lsquic_mini_conn_ietf.c b/src/liblsquic/lsquic_mini_conn_ietf.c index 17641b3..5e6c26c 100644 --- a/src/liblsquic/lsquic_mini_conn_ietf.c +++ b/src/liblsquic/lsquic_mini_conn_ietf.c @@ -52,6 +52,9 @@ static unsigned highest_bit_set (unsigned long long); static int imico_can_send (const struct ietf_mini_conn *, size_t); +static void +ietf_mini_conn_ci_abort_error (struct lsquic_conn *lconn, int is_app, + unsigned error_code, const char *fmt, ...); static const enum header_type el2hety[] = { @@ -1432,11 +1435,19 @@ ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn, dec_packin = lconn->cn_esf_c->esf_decrypt_packet(lconn->cn_enc_session, conn->imc_enpub, &conn->imc_conn, packet_in); - if (dec_packin != DECPI_OK) + switch (dec_packin) { + case DECPI_OK: + break; + case DECPI_VIOLATION: + ietf_mini_conn_ci_abort_error(lconn, 0, TEC_PROTOCOL_VIOLATION, + "protocol violation detected while decrypting packet"); + return; + case DECPI_NOT_YET: + imico_maybe_delay_processing(conn, packet_in); + return; + default: LSQ_DEBUG("could not decrypt packet"); - if (DECPI_NOT_YET == dec_packin) - imico_maybe_delay_processing(conn, packet_in); return; } @@ -1511,8 +1522,6 @@ ietf_mini_conn_ci_packet_sent (struct lsquic_conn *lconn, mc->mc_flags &= ~MC_UNSENT_ACK; } #endif - ++conn->imc_ecn_counts_out[ lsquic_packet_out_pns(packet_out) ] - [ lsquic_packet_out_ecn(packet_out) ]; if (packet_out->po_header_type == HETY_HANDSHAKE) conn->imc_flags |= IMC_HSK_PACKET_SENT; LSQ_DEBUG("%s: packet %"PRIu64" sent", __func__, packet_out->po_packno); diff --git a/src/liblsquic/lsquic_mini_conn_ietf.h b/src/liblsquic/lsquic_mini_conn_ietf.h index 1af3529..2347bf3 100644 --- a/src/liblsquic/lsquic_mini_conn_ietf.h +++ b/src/liblsquic/lsquic_mini_conn_ietf.h @@ -105,7 +105,6 @@ struct ietf_mini_conn uint8_t imc_ecn_packnos; uint8_t imc_ack_exp; uint8_t imc_ecn_counts_in[IMICO_N_PNS][4]; - uint8_t imc_ecn_counts_out[IMICO_N_PNS][4]; uint8_t imc_incoming_ecn; uint8_t imc_tls_alert; #define IMICO_MAX_DELAYED_PACKETS_UNVALIDATED 1u diff --git a/src/liblsquic/lsquic_packet_in.h b/src/liblsquic/lsquic_packet_in.h index 3651bbd..1efc918 100644 --- a/src/liblsquic/lsquic_packet_in.h +++ b/src/liblsquic/lsquic_packet_in.h @@ -23,13 +23,14 @@ struct data_frame typedef struct stream_frame { - /* Stream frames are stored in a list inside stream. */ + /* Stream frames are stored in a list inside "di nocopy" (if "di nocopy" + * is used). + */ TAILQ_ENTRY(stream_frame) next_frame; - /* `data' points somewhere into the packet payload. The packet object - * is reference-counted. When the frame is freed, the packet is released - * via lsquic_packet_put(). If data_length is zero, the frame does not - * keep a reference to the incoming packet and this pointer is not set. + /* `data_frame.df_data' points somewhere into the packet payload. The + * packet object is reference-counted. When the frame is freed, the + * packet is released via lsquic_packet_in_put(). */ struct lsquic_packet_in *packet_in; diff --git a/src/liblsquic/lsquic_qdec_hdl.c b/src/liblsquic/lsquic_qdec_hdl.c index 87fb1b7..d8dfdf6 100644 --- a/src/liblsquic/lsquic_qdec_hdl.c +++ b/src/liblsquic/lsquic_qdec_hdl.c @@ -553,6 +553,25 @@ qdh_maybe_set_user_agent (struct qpack_dec_hdl *qdh, } +/* Intercept header errors so that upper-layer errors do not get + * misinterpreted as QPACK errors. + */ +static int +qdh_hsi_process_wrapper (struct qpack_dec_hdl *qdh, void *hset, + struct lsxpack_header *xhdr) +{ + int retval; + + retval = qdh->qdh_enpub->enp_hsi_if->hsi_process_header(hset, xhdr); + if (0 != retval) + qdh->qdh_conn->cn_if->ci_abort_error(qdh->qdh_conn, 1, + 1 == retval ? HEC_MESSAGE_ERROR : HEC_INTERNAL_ERROR, + "error processing headers"); + + return retval; +} + + static int qdh_process_header (void *stream_p, struct lsxpack_header *xhdr) { @@ -584,7 +603,7 @@ qdh_process_header (void *stream_p, struct lsxpack_header *xhdr) else if ((qdh->qdh_flags & QDH_SAVE_UA) && !qdh->qdh_ua) qdh_maybe_set_user_agent(qdh, xhdr, &qdh->qdh_ua); - return qdh->qdh_enpub->enp_hsi_if->hsi_process_header(u->ctx.hset, xhdr); + return qdh_hsi_process_wrapper(qdh, u->ctx.hset, xhdr); } @@ -643,8 +662,7 @@ qdh_header_read_results (struct qpack_dec_hdl *qdh, uh->uh_exclusive = -1; if (qdh->qdh_enpub->enp_hsi_if == lsquic_http1x_if) uh->uh_flags |= UH_H1H; - if (0 != qdh->qdh_enpub->enp_hsi_if - ->hsi_process_header(hset, NULL)) + if (0 != qdh_hsi_process_wrapper(qdh, hset, NULL)) { LSQ_DEBUG("finishing hset failed"); free(uh); diff --git a/tools/gen-tags.pl b/tools/gen-tags.pl index 6dd7a7b..1012044 100755 --- a/tools/gen-tags.pl +++ b/tools/gen-tags.pl @@ -54,6 +54,7 @@ while () or s/^iquic_esf_/esf_/ or s/^gquic[0-9]?_esf_/esf_/ or s/^iquic_esfi_/esfi_/ + or s/^lsquic_[sh]pi_/pii_/ or s/^(lsquic_cubic|lsquic_bbr)_/cci_/ ) {