Skip to content

Internal structures and states

Christian Huitema edited this page Apr 12, 2020 · 3 revisions

Contexts and connections

Picoquic code uses 2 main objects types to represent the Quic context (type picoquic_quic_t) and the Quic connection (type picoquic_cnx_t). There is normally just one Quic context per process, and one Quic connection per connection.

The Quic context gathers the information common to all connections, such as for example the length of connection identifiers or the value of the reset secret. The Quic context keeps track of all connections created through the call to picoquic_create_cnx. These lists of connections are used in context level calls, such as:

  • Arrival of a packet on a socket shared by all connections: picoquic_incoming_packet
  • Preparing the next packet from one of the connections: picoquic_prepare_next_packet

The Quic connection context tracks all the state of a connection, either directly or through a set of other objects such as paths or packet contexts.

Connection context and dependencies

The connection context includes variables describing the state of the connection, as well as pointers or tables of the following types:

  • TLS context for the connection, represented by a void *, because the structure depends on the specific TLS stack used in the implementation. Of course, we only use picotls today. See documentation of TLS usage (TBD).

  • List of connections: the management of connections requires listing the connection pointer in several lists managed by the Quic context, including a double linked list of connections ordered by wake time.

  • Queue of miscellaneous frames: frames that are produced outside the regular polling process, either because they are one-off or because they are inserted by test programs. Each frame is represented by a picoquic_misc_frame_header_t object.

  • tls_stream and crypto context: two tables indexed by epoch picoquic_epoch_enum, listing the data received from the peer in the TLS Stream for the epoch (during handshake) and the current crypto context used for encrypting or decrypting packets for that epoch. (Note that encryption keys are opaquely stored in the crypto contexts.) In addition, the "new" and "old" crypto contexts are used to manage rotation of the 1-RTT key.

  • Table of packet contexts, indexed by context number picoquic_packet_context_enum. This is almost the same concept as epoch, except that 0-RTT and 1-RTT share a single context. (TODO: unify in a future code reorg.) The packet context maintains the transmission data for a class of packets, such as sequence number, list of unacknowledged packets, or sack dashboard. Note that the packet structures are allocated once and reused, and that the list of empty packets in maintained in the Quic context.

  • Management of streams: lists of the picoquic_stream_head_t objects describing the Quic streams in progress on the connection. There are two lists: a splay of all open frames, and a chained list of the frames currently ready to send data.

  • Queue of datagram frames: datagram frames that are ready to be sent. Each frame is represented by a picoquic_misc_frame_header_t object. These structures will probably be revisited as we understand datagram usage better.

  • Table of paths: a variable size array of path pointers allocated to match the number of active paths. Each path is defined by the four-tuple of local and remote IP addresses and UDP ports. There is a picoquic_path_t object for each path.

  • List of connection ID sent to the peer and not yet retired. Each ID is documented in an object of type picoquic_local_cnxid_t. Each of the local connection ID is registered with the Quic Context. They can be used by the peer at any time as destination connection ID. The Quic context will use its table to assign the incoming packet to the proper connection.

Path context

Each path context maintains data used to manage transmission or tests on that path, including a pointer to the local connection ID last used by the peer for sending data on the path, and a copy of the connection ID used for sending to that peer.

The path context includes a pointer to the congestion context for the path, allocated when the path is created by the selected congestion control algorithm.

Picoquic is "almost ready" to support multi-path operation, but the current code only send data to the path number 0. In case of migration, the newly selected path is moved to index 0, and the old path is marked "deprecated".

TLS

Picoquic relies on the "picotls" package for TLS. There was an initial attempt to provide a layer of indirection, so picotls could be swapped out for another package. The code interfacing picotls is located in its own file, "tls_api.c". In theory, only that file contains direct references to the picotls API defined in "picotls.h". In practice, there are also direct references in test functions that exercise the TLS API, in logging functions that need to log TLS results, and in the demonstration client that may need to access internal TLS data for enabling specific interoperability tests. Otherwise, the transport code only accesses the TLS API through the functions defined in "tls_api.h", which does not expose the internal structures of "picotls.h".

TLS references are added to both the Quic context and the connection context:

  • The structure picoquic_quic_t includes a pointer to tls_master_ctx, storing the TLS parameters necessary for all connections,
  • The structure picoquic_cnx_t includes a pointer to tls_ctx, storing the TLS parameters for a specific connection.

Per context TLS parameters

The pointer tls_master_ctx points to a structure of type ptls_context_t, cast as a void * to isolate the picoquic transport code from the details of the picotls TLS code. That structure is created with a call to picoquic_master_tlscontext, and populated with picoquic specific parameters, as well as a backpointer to the picoquic context. The structure includes pointers to the callback functions required by picotls:

  • traffic keys update,
  • time management,
  • certificate management,
  • client hello callback,
  • random number generation.

The structure also manages the encryption keys used for tickets (ticket key).

Per connection TLS parameters

The pointer tls_ctx points to a structure of type picoquic_tls_ctx_t, cast as a void * to isolate the picoquic transport code from the details of the picotls TLS code. That structure is created with a call to picoquic_tlscontext_create, and populated with picoquic specific parameters, in particular:

  • data structures required to call the picotls API, such as handshake properties,
  • pointer tls to the picotls context of the connection, type ptls_t allocated by a call to ptls_new.

Management of encryption keys

The QUIC connections uses a number of encryption keys:

  • Data encryption keys associated with different epochs of the connection: initial, 0-rtt, handshake, 1-rtt, or with key rotations
  • Encryption keys used for encrypting tickets or tokens.

The data encryption keys are derived by hashing connection specific secrets by a call to picoquic_set_aead_from_secret, which generates an AEAD context through a call to ptls_aead_new. The AEAD context encapsulates the encryption key and the initial vector. The initial vector is stored in clear text (aead->iv, aead->iv_size), but the encryption key is not. It is stored in structures that depend on the cryptographic provider used by picotls, in our case OpenSSL. For security reasons, cryptographic providers try to protect access to these keys. For the same security reasons, Picoquic discards the secrets as soon as the corresponding key has been generated.

Despite not directly storing encryption keys, Picoquic has the option to log the keys to local file. This is useful if admins are using Wireshark to understand and debug local Quic connections. The Picoquic demo servers and clients, for example, will call the function picoquic_set_key_log_file_from_env that check whether the environment variable SSLKEYLOGFILE has been set to the name of the desired log file. If that is true, Picoquic will call picoquic_set_key_log_file to open that file and set a log_event callback in the picotls context. Each time a new key is created, the callback will be instantiated and the key values will be written to the file. That file can later be passed to Wireshark to decode the Quic traffic.