Path: blob/main/crypto/openssl/ssl/quic/quic_rcidm.c
109086 views
/*1* Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/89#include "internal/quic_rcidm.h"10#include "internal/priority_queue.h"11#include "internal/list.h"12#include "internal/common.h"1314/*15* QUIC Remote Connection ID Manager16* =================================17*18* We can receive an arbitrary number of RCIDs via NCID frames. Periodically, we19* may desire (for example for anti-connection fingerprinting reasons, etc.)20* to switch to a new RCID according to some arbitrary policy such as the number21* of packets we have sent.22*23* When we do this we should move to the next RCID in the sequence of received24* RCIDs ordered by sequence number. For example, if a peer sends us three NCID25* frames with sequence numbers 10, 11, 12, we should seek to consume these26* RCIDs in order.27*28* However, due to the possibility of packet reordering in the network, NCID29* frames might be received out of order. Thus if a peer sends us NCID frames30* with sequence numbers 12, 10, 11, we should still consume the RCID with31* sequence number 10 before consuming the RCIDs with sequence numbers 11 or 12.32*33* We use a priority queue for this purpose.34*/35static void rcidm_update(QUIC_RCIDM *rcidm);36static void rcidm_set_preferred_rcid(QUIC_RCIDM *rcidm,37const QUIC_CONN_ID *rcid);3839#define PACKETS_PER_RCID 100004041#define INITIAL_SEQ_NUM 042#define PREF_ADDR_SEQ_NUM 14344/*45* RCID46* ====47*48* The RCID structure is used to track RCIDs which have sequence numbers (i.e.,49* INITIAL, PREF_ADDR and NCID type RCIDs). The RCIDs without sequence numbers50* (Initial ODCIDs and Retry ODCIDs), hereafter referred to as unnumbered RCIDs,51* can logically be viewed as their own type of RCID but are tracked separately52* as singletons without needing a discrete structure.53*54* At any given time an RCID object is in one of these states:55*56*57* (start)58* |59* [add]60* |61* _____v_____ ___________ ____________62* | | | | | |63* | PENDING | --[select]--> | CURRENT | --[retire]--> | RETIRING |64* |___________| |___________| |____________|65* |66* [pop]67* |68* v69* (fin)70*71* The transition through the states is monotonic and irreversible.72* The RCID object is freed when it is popped.73*74* PENDING75* Invariants:76* rcid->state == RCID_STATE_PENDING;77* rcid->pq_idx != SIZE_MAX (debug assert only);78* the RCID is not the current RCID, rcidm->cur_rcid != rcid;79* the RCID is in the priority queue;80* the RCID is not in the retiring_list.81*82* CURRENT83* Invariants:84* rcid->state == RCID_STATE_CUR;85* rcid->pq_idx == SIZE_MAX (debug assert only);86* the RCID is the current RCID, rcidm->cur_rcid == rcid;87* the RCID is not in the priority queue;88* the RCID is not in the retiring_list.89*90* RETIRING91* Invariants:92* rcid->state == RCID_STATE_RETIRING;93* rcid->pq_idx == SIZE_MAX (debug assert only);94* the RCID is not the current RCID, rcidm->cur_rcid != rcid;95* the RCID is not in the priority queue;96* the RCID is in the retiring_list.97*98* Invariant: At most one RCID object is in the CURRENT state at any one time.99*100* (If no RCID object is in the CURRENT state, this means either101* an unnumbered RCID is being used as the preferred RCID102* or we currently have no preferred RCID.)103*104* All of the above states can be considered substates of the 'ACTIVE' state105* for an RCID as specified in RFC 9000. A CID only ceases to be active106* when we send a RETIRE_CONN_ID frame, which is the responsibility of the107* user of the RCIDM and happens after the above state machine is terminated.108*/109enum {110RCID_STATE_PENDING,111RCID_STATE_CUR,112RCID_STATE_RETIRING113};114115enum {116RCID_TYPE_INITIAL, /* CID is from an peer INITIAL packet (seq 0) */117RCID_TYPE_PREF_ADDR, /* CID is from a preferred_address TPARAM (seq 1) */118RCID_TYPE_NCID /* CID is from a NCID frame */119/*120* INITIAL_ODCID and RETRY_ODCID also conceptually exist but are tracked121* separately.122*/123};124125typedef struct rcid_st {126OSSL_LIST_MEMBER(retiring, struct rcid_st); /* valid iff RETIRING */127128QUIC_CONN_ID cid; /* The actual CID string for this RCID */129uint64_t seq_num;130size_t pq_idx; /* Index of entry into priority queue */131unsigned int state : 2; /* RCID_STATE_* */132unsigned int type : 2; /* RCID_TYPE_* */133} RCID;134135DEFINE_PRIORITY_QUEUE_OF(RCID);136DEFINE_LIST_OF(retiring, RCID);137138/*139* RCID Manager140* ============141*142* The following "business logic" invariants also apply to the RCIDM143* as a whole:144*145* Invariant: An RCID of INITIAL type has a sequence number of 0.146* Invariant: An RCID of PREF_ADDR type has a sequence number of 1.147*148* Invariant: There is never more than one Initial ODCID149* added throughout the lifetime of an RCIDM.150* Invariant: There is never more than one Retry ODCID151* added throughout the lifetime of an RCIDM.152* Invariant: There is never more than one INITIAL RCID created153* throughout the lifetime of an RCIDM.154* Invariant: There is never more than one PREF_ADDR RCID created155* throughout the lifetime of an RCIDM.156* Invariant: No INITIAL or PREF_ADDR RCID may be added after157* the handshake is completed.158*159*/160struct quic_rcidm_st {161/*162* The current RCID we prefer to use (value undefined if163* !have_preferred_rcid).164*165* This is preferentially set to a numbered RCID (represented by an RCID166* object) if we have one (in which case preferred_rcid == cur_rcid->cid);167* otherwise it is set to one of the unnumbered RCIDs (the Initial ODCID or168* Retry ODCID) if available (and cur_rcid == NULL).169*/170QUIC_CONN_ID preferred_rcid;171172/*173* These are initialized if the corresponding added_ flags are set.174*/175QUIC_CONN_ID initial_odcid, retry_odcid;176177/*178* Total number of packets sent since we last made a packet count-based RCID179* update decision.180*/181uint64_t packets_sent;182183/* Number of post-handshake RCID changes we have performed. */184uint64_t num_changes;185186/*187* The Retire Prior To watermark value; max(retire_prior_to) of all received188* NCID frames.189*/190uint64_t retire_prior_to;191192/* (SORT BY seq_num ASC) -> (RCID *) */193PRIORITY_QUEUE_OF(RCID) * rcids;194195/*196* Current RCID object we are using. This may differ from the first item in197* the priority queue if we received NCID frames out of order. For example198* if we get seq 5, switch to it immediately, then get seq 4, we want to199* keep using seq 5 until we decide to roll again rather than immediately200* switch to seq 4. Never points to an object on the retiring_list.201*/202RCID *cur_rcid;203204/*205* When a RCID becomes pending-retirement, it is moved to the retiring_list,206* then freed when it is popped from the retired queue. We use a list for207* this rather than a priority queue as the order in which items are freed208* does not matter. We always append to the tail of the list in order to209* maintain the guarantee that the head (if present) only changes when a210* caller calls pop().211*/212OSSL_LIST(retiring)213retiring_list;214215/* Number of entries on the retiring_list. */216size_t num_retiring;217218/* preferred_rcid has been changed? */219unsigned int preferred_rcid_changed : 1;220221/* Do we have any RCID we can use currently? */222unsigned int have_preferred_rcid : 1;223224/* QUIC handshake has been completed? */225unsigned int handshake_complete : 1;226227/* odcid was set (not necessarily still valid as a RCID)? */228unsigned int added_initial_odcid : 1;229/* retry_odcid was set (not necessarily still valid as a RCID?) */230unsigned int added_retry_odcid : 1;231/* An initial RCID was added as an RCID structure? */232unsigned int added_initial_rcid : 1;233/* Has a RCID roll been manually requested? */234unsigned int roll_requested : 1;235};236237/*238* Caller must periodically pop retired RCIDs and handle them. If the caller239* fails to do so, fail safely rather than start exhibiting integer rollover.240* Limit the total number of numbered RCIDs to an implausibly large but safe241* value.242*/243#define MAX_NUMBERED_RCIDS (SIZE_MAX / 2)244245static void rcidm_transition_rcid(QUIC_RCIDM *rcidm, RCID *rcid,246unsigned int state);247248/* Check invariants of an RCID */249static void rcidm_check_rcid(QUIC_RCIDM *rcidm, RCID *rcid)250{251assert(rcid->state == RCID_STATE_PENDING252|| rcid->state == RCID_STATE_CUR253|| rcid->state == RCID_STATE_RETIRING);254assert((rcid->state == RCID_STATE_PENDING)255== (rcid->pq_idx != SIZE_MAX));256assert((rcid->state == RCID_STATE_CUR)257== (rcidm->cur_rcid == rcid));258assert((ossl_list_retiring_next(rcid) != NULL259|| ossl_list_retiring_prev(rcid) != NULL260|| ossl_list_retiring_head(&rcidm->retiring_list) == rcid)261== (rcid->state == RCID_STATE_RETIRING));262assert(rcid->type != RCID_TYPE_INITIAL || rcid->seq_num == 0);263assert(rcid->type != RCID_TYPE_PREF_ADDR || rcid->seq_num == 1);264assert(rcid->seq_num <= OSSL_QUIC_VLINT_MAX);265assert(rcid->cid.id_len > 0 && rcid->cid.id_len <= QUIC_MAX_CONN_ID_LEN);266assert(rcid->seq_num >= rcidm->retire_prior_to267|| rcid->state == RCID_STATE_RETIRING);268assert(rcidm->num_changes == 0 || rcidm->handshake_complete);269assert(rcid->state != RCID_STATE_RETIRING || rcidm->num_retiring > 0);270}271272static int rcid_cmp(const RCID *a, const RCID *b)273{274if (a->seq_num < b->seq_num)275return -1;276if (a->seq_num > b->seq_num)277return 1;278return 0;279}280281QUIC_RCIDM *ossl_quic_rcidm_new(const QUIC_CONN_ID *initial_odcid)282{283QUIC_RCIDM *rcidm;284285if ((rcidm = OPENSSL_zalloc(sizeof(*rcidm))) == NULL)286return NULL;287288if ((rcidm->rcids = ossl_pqueue_RCID_new(rcid_cmp)) == NULL) {289OPENSSL_free(rcidm);290return NULL;291}292293if (initial_odcid != NULL) {294rcidm->initial_odcid = *initial_odcid;295rcidm->added_initial_odcid = 1;296}297298rcidm_update(rcidm);299return rcidm;300}301302void ossl_quic_rcidm_free(QUIC_RCIDM *rcidm)303{304RCID *rcid, *rnext;305306if (rcidm == NULL)307return;308309OPENSSL_free(rcidm->cur_rcid);310while ((rcid = ossl_pqueue_RCID_pop(rcidm->rcids)) != NULL)311OPENSSL_free(rcid);312313OSSL_LIST_FOREACH_DELSAFE(rcid, rnext, retiring, &rcidm->retiring_list)314OPENSSL_free(rcid);315316ossl_pqueue_RCID_free(rcidm->rcids);317OPENSSL_free(rcidm);318}319320static void rcidm_set_preferred_rcid(QUIC_RCIDM *rcidm,321const QUIC_CONN_ID *rcid)322{323if (rcid == NULL) {324rcidm->preferred_rcid_changed = 1;325rcidm->have_preferred_rcid = 0;326return;327}328329if (ossl_quic_conn_id_eq(&rcidm->preferred_rcid, rcid))330return;331332rcidm->preferred_rcid = *rcid;333rcidm->preferred_rcid_changed = 1;334rcidm->have_preferred_rcid = 1;335}336337/*338* RCID Lifecycle Management339* =========================340*/341static RCID *rcidm_create_rcid(QUIC_RCIDM *rcidm, uint64_t seq_num,342const QUIC_CONN_ID *cid,343unsigned int type)344{345RCID *rcid;346347if (cid->id_len < 1 || cid->id_len > QUIC_MAX_CONN_ID_LEN348|| seq_num > OSSL_QUIC_VLINT_MAX349|| ossl_pqueue_RCID_num(rcidm->rcids) + rcidm->num_retiring350> MAX_NUMBERED_RCIDS)351return NULL;352353if ((rcid = OPENSSL_zalloc(sizeof(*rcid))) == NULL)354return NULL;355356rcid->seq_num = seq_num;357rcid->cid = *cid;358rcid->type = type;359360if (rcid->seq_num >= rcidm->retire_prior_to) {361rcid->state = RCID_STATE_PENDING;362363if (!ossl_pqueue_RCID_push(rcidm->rcids, rcid, &rcid->pq_idx)) {364OPENSSL_free(rcid);365return NULL;366}367} else {368/* RCID is immediately retired upon creation. */369rcid->state = RCID_STATE_RETIRING;370rcid->pq_idx = SIZE_MAX;371ossl_list_retiring_insert_tail(&rcidm->retiring_list, rcid);372++rcidm->num_retiring;373}374375rcidm_check_rcid(rcidm, rcid);376return rcid;377}378379static void rcidm_transition_rcid(QUIC_RCIDM *rcidm, RCID *rcid,380unsigned int state)381{382unsigned int old_state = rcid->state;383384assert(state >= old_state && state <= RCID_STATE_RETIRING);385rcidm_check_rcid(rcidm, rcid);386if (state == old_state)387return;388389if (rcidm->cur_rcid != NULL && state == RCID_STATE_CUR) {390rcidm_transition_rcid(rcidm, rcidm->cur_rcid, RCID_STATE_RETIRING);391assert(rcidm->cur_rcid == NULL);392}393394if (old_state == RCID_STATE_PENDING) {395ossl_pqueue_RCID_remove(rcidm->rcids, rcid->pq_idx);396rcid->pq_idx = SIZE_MAX;397}398399rcid->state = state;400401if (state == RCID_STATE_CUR) {402rcidm->cur_rcid = rcid;403} else if (state == RCID_STATE_RETIRING) {404if (old_state == RCID_STATE_CUR)405rcidm->cur_rcid = NULL;406407ossl_list_retiring_insert_tail(&rcidm->retiring_list, rcid);408++rcidm->num_retiring;409}410411rcidm_check_rcid(rcidm, rcid);412}413414static void rcidm_free_rcid(QUIC_RCIDM *rcidm, RCID *rcid)415{416if (rcid == NULL)417return;418419rcidm_check_rcid(rcidm, rcid);420421switch (rcid->state) {422case RCID_STATE_PENDING:423ossl_pqueue_RCID_remove(rcidm->rcids, rcid->pq_idx);424break;425case RCID_STATE_CUR:426rcidm->cur_rcid = NULL;427break;428case RCID_STATE_RETIRING:429ossl_list_retiring_remove(&rcidm->retiring_list, rcid);430--rcidm->num_retiring;431break;432default:433assert(0);434break;435}436437OPENSSL_free(rcid);438}439440static void rcidm_handle_retire_prior_to(QUIC_RCIDM *rcidm,441uint64_t retire_prior_to)442{443RCID *rcid;444445if (retire_prior_to <= rcidm->retire_prior_to)446return;447448/*449* Retire the current RCID (if any) if it is affected.450*/451if (rcidm->cur_rcid != NULL && rcidm->cur_rcid->seq_num < retire_prior_to)452rcidm_transition_rcid(rcidm, rcidm->cur_rcid, RCID_STATE_RETIRING);453454/*455* Any other RCIDs needing retirement will be at the start of the priority456* queue, so just stop once we see a higher sequence number exceeding the457* threshold.458*/459while ((rcid = ossl_pqueue_RCID_peek(rcidm->rcids)) != NULL460&& rcid->seq_num < retire_prior_to)461rcidm_transition_rcid(rcidm, rcid, RCID_STATE_RETIRING);462463rcidm->retire_prior_to = retire_prior_to;464}465466/*467* Decision Logic468* ==============469*/470471static void rcidm_roll(QUIC_RCIDM *rcidm)472{473RCID *rcid;474475if ((rcid = ossl_pqueue_RCID_peek(rcidm->rcids)) == NULL)476return;477478rcidm_transition_rcid(rcidm, rcid, RCID_STATE_CUR);479480++rcidm->num_changes;481rcidm->roll_requested = 0;482483if (rcidm->packets_sent >= PACKETS_PER_RCID)484rcidm->packets_sent %= PACKETS_PER_RCID;485else486rcidm->packets_sent = 0;487}488489static void rcidm_update(QUIC_RCIDM *rcidm)490{491RCID *rcid;492493/*494* If we have no current numbered RCID but have one or more pending, use it.495*/496if (rcidm->cur_rcid == NULL497&& (rcid = ossl_pqueue_RCID_peek(rcidm->rcids)) != NULL) {498rcidm_transition_rcid(rcidm, rcid, RCID_STATE_CUR);499assert(rcidm->cur_rcid != NULL);500}501502/* Prefer use of any current numbered RCID we have, if possible. */503if (rcidm->cur_rcid != NULL) {504rcidm_check_rcid(rcidm, rcidm->cur_rcid);505rcidm_set_preferred_rcid(rcidm, &rcidm->cur_rcid->cid);506return;507}508509/*510* If there are no RCIDs from NCID frames we can use, go through the various511* kinds of bootstrapping RCIDs we can use in order of priority.512*/513if (rcidm->added_retry_odcid && !rcidm->handshake_complete) {514rcidm_set_preferred_rcid(rcidm, &rcidm->retry_odcid);515return;516}517518if (rcidm->added_initial_odcid && !rcidm->handshake_complete) {519rcidm_set_preferred_rcid(rcidm, &rcidm->initial_odcid);520return;521}522523/* We don't know of any usable RCIDs */524rcidm_set_preferred_rcid(rcidm, NULL);525}526527static int rcidm_should_roll(QUIC_RCIDM *rcidm)528{529/*530* Always switch as soon as possible if handshake completes;531* and every n packets after handshake completes or the last roll; and532* whenever manually requested.533*/534return rcidm->handshake_complete535&& (rcidm->num_changes == 0536|| rcidm->packets_sent >= PACKETS_PER_RCID537|| rcidm->roll_requested);538}539540static void rcidm_tick(QUIC_RCIDM *rcidm)541{542if (rcidm_should_roll(rcidm))543rcidm_roll(rcidm);544545rcidm_update(rcidm);546}547548/*549* Events550* ======551*/552void ossl_quic_rcidm_on_handshake_complete(QUIC_RCIDM *rcidm)553{554if (rcidm->handshake_complete)555return;556557rcidm->handshake_complete = 1;558rcidm_tick(rcidm);559}560561void ossl_quic_rcidm_on_packet_sent(QUIC_RCIDM *rcidm, uint64_t num_packets)562{563if (num_packets == 0)564return;565566rcidm->packets_sent += num_packets;567rcidm_tick(rcidm);568}569570void ossl_quic_rcidm_request_roll(QUIC_RCIDM *rcidm)571{572rcidm->roll_requested = 1;573rcidm_tick(rcidm);574}575576/*577* Mutation Operations578* ===================579*/580int ossl_quic_rcidm_add_from_initial(QUIC_RCIDM *rcidm,581const QUIC_CONN_ID *rcid)582{583RCID *rcid_obj;584585if (rcidm->added_initial_rcid || rcidm->handshake_complete)586return 0;587588rcid_obj = rcidm_create_rcid(rcidm, INITIAL_SEQ_NUM,589rcid, RCID_TYPE_INITIAL);590if (rcid_obj == NULL)591return 0;592593rcidm->added_initial_rcid = 1;594rcidm_tick(rcidm);595return 1;596}597598int ossl_quic_rcidm_add_from_server_retry(QUIC_RCIDM *rcidm,599const QUIC_CONN_ID *retry_odcid)600{601if (rcidm->added_retry_odcid || rcidm->handshake_complete)602return 0;603604rcidm->retry_odcid = *retry_odcid;605rcidm->added_retry_odcid = 1;606rcidm_tick(rcidm);607return 1;608}609610int ossl_quic_rcidm_add_from_ncid(QUIC_RCIDM *rcidm,611const OSSL_QUIC_FRAME_NEW_CONN_ID *ncid)612{613RCID *rcid;614615rcid = rcidm_create_rcid(rcidm, ncid->seq_num, &ncid->conn_id, RCID_TYPE_NCID);616if (rcid == NULL)617return 0;618619rcidm_handle_retire_prior_to(rcidm, ncid->retire_prior_to);620rcidm_tick(rcidm);621return 1;622}623624/*625* Queries626* =======627*/628629static int rcidm_get_retire(QUIC_RCIDM *rcidm, uint64_t *seq_num, int peek)630{631RCID *rcid = ossl_list_retiring_head(&rcidm->retiring_list);632633if (rcid == NULL)634return 0;635636if (seq_num != NULL)637*seq_num = rcid->seq_num;638639if (!peek)640rcidm_free_rcid(rcidm, rcid);641642return 1;643}644645int ossl_quic_rcidm_pop_retire_seq_num(QUIC_RCIDM *rcidm,646uint64_t *seq_num)647{648return rcidm_get_retire(rcidm, seq_num, /*peek=*/0);649}650651int ossl_quic_rcidm_peek_retire_seq_num(QUIC_RCIDM *rcidm,652uint64_t *seq_num)653{654return rcidm_get_retire(rcidm, seq_num, /*peek=*/1);655}656657int ossl_quic_rcidm_get_preferred_tx_dcid(QUIC_RCIDM *rcidm,658QUIC_CONN_ID *tx_dcid)659{660if (!rcidm->have_preferred_rcid)661return 0;662663*tx_dcid = rcidm->preferred_rcid;664return 1;665}666667int ossl_quic_rcidm_get_preferred_tx_dcid_changed(QUIC_RCIDM *rcidm,668int clear)669{670int r = rcidm->preferred_rcid_changed;671672if (clear)673rcidm->preferred_rcid_changed = 0;674675return r;676}677678size_t ossl_quic_rcidm_get_num_active(const QUIC_RCIDM *rcidm)679{680return ossl_pqueue_RCID_num(rcidm->rcids)681+ (rcidm->cur_rcid != NULL ? 1 : 0)682+ ossl_quic_rcidm_get_num_retiring(rcidm);683}684685size_t ossl_quic_rcidm_get_num_retiring(const QUIC_RCIDM *rcidm)686{687return rcidm->num_retiring;688}689690691