mirror of
https://github.com/openssl/openssl.git
synced 2025-01-06 13:26:43 +08:00
QUIC RCIDM: Add RCIDM
Reviewed-by: Matt Caswell <matt@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.org> (Merged from https://github.com/openssl/openssl/pull/23022)
This commit is contained in:
parent
7c1d533a51
commit
63f77f0454
171
include/internal/quic_rcidm.h
Normal file
171
include/internal/quic_rcidm.h
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#ifndef OSSL_INTERNAL_QUIC_RCIDM_H
|
||||
# define OSSL_INTERNAL_QUIC_RCIDM_H
|
||||
# pragma once
|
||||
|
||||
# include "internal/e_os.h"
|
||||
# include "internal/time.h"
|
||||
# include "internal/quic_types.h"
|
||||
# include "internal/quic_wire.h"
|
||||
|
||||
# ifndef OPENSSL_NO_QUIC
|
||||
|
||||
/*
|
||||
* QUIC Remote Connection ID Manager
|
||||
* =================================
|
||||
*
|
||||
* This manages connection IDs for the TX side, which is to say that it tracks
|
||||
* remote CIDs (RCIDs) which a peer has issued to us and which we can use as the
|
||||
* DCID of packets we transmit. It is entirely separate from the LCIDM, which
|
||||
* handles routing received packets by their DCIDs.
|
||||
*
|
||||
* RCIDs fall into four categories:
|
||||
*
|
||||
* 1. A client's Initial ODCID (0..1)
|
||||
* 2. A peer's Initial SCID (1)
|
||||
* 3. A server's Retry SCID (0..1)
|
||||
* 4. A CID issued via a NEW_CONNECTION_ID frame (n)
|
||||
*
|
||||
* Unlike a LCIDM, which is per port, a RCIDM is per connection, as there is no
|
||||
* need for routing of outgoing packets.
|
||||
*/
|
||||
typedef struct quic_rcidm_st QUIC_RCIDM;
|
||||
|
||||
/*
|
||||
* Creates a new RCIDM. Returns NULL on failure.
|
||||
*
|
||||
* For a client, initial_rcid is the client's Initial ODCID.
|
||||
* For a server, initial_rcid is NULL.
|
||||
*/
|
||||
QUIC_RCIDM *ossl_quic_rcidm_new(const QUIC_CONN_ID *initial_odcid);
|
||||
|
||||
/* Frees a RCIDM. */
|
||||
void ossl_quic_rcidm_free(QUIC_RCIDM *rcidm);
|
||||
|
||||
/*
|
||||
* CID Events
|
||||
* ==========
|
||||
*/
|
||||
|
||||
/*
|
||||
* To be called by a client when a server responds to the first Initial packet
|
||||
* sent with its own Initial packet with its own SCID; or to be called by a
|
||||
* server when we first get an Initial packet from a client with the client's
|
||||
* supplied SCID. The added RCID implicitly has a sequence number of 0.
|
||||
*
|
||||
* We immediately switch to using this SCID as our preferred RCID. This SCID
|
||||
* must be enrolled using this function. May only be called once.
|
||||
*/
|
||||
int ossl_quic_rcidm_add_from_initial(QUIC_RCIDM *rcidm,
|
||||
const QUIC_CONN_ID *rcid);
|
||||
|
||||
/*
|
||||
* To be called by a client when a server responds to the first Initial packet
|
||||
* sent with a Retry packet with its own SCID (the "Retry ODCID"). We
|
||||
* immediately switch to using this SCID as our preferred RCID when conducting
|
||||
* the retry. This SCID must be enrolled using this function. May only be called
|
||||
* once. The added RCID has no sequence number associated with it as it is
|
||||
* essentially a new ODCID (hereafter a Retry ODCID).
|
||||
*
|
||||
* Not for server use.
|
||||
*/
|
||||
int ossl_quic_rcidm_add_from_server_retry(QUIC_RCIDM *rcidm,
|
||||
const QUIC_CONN_ID *retry_odcid);
|
||||
|
||||
/*
|
||||
* Processes an incoming NEW_CONN_ID frame, recording the new CID as a potential
|
||||
* RCID. The RCIDM retirement mechanism is ratcheted according to the
|
||||
* ncid->retire_prior_to field. The stateless_reset field is ignored; the caller
|
||||
* is responsible for handling it separately.
|
||||
*/
|
||||
int ossl_quic_rcidm_add_from_ncid(QUIC_RCIDM *rcidm,
|
||||
const OSSL_QUIC_FRAME_NEW_CONN_ID *ncid);
|
||||
|
||||
/*
|
||||
* Other Events
|
||||
* ============
|
||||
*/
|
||||
|
||||
/*
|
||||
* Notifies the RCIDM that the handshake for a connection is complete.
|
||||
* Should only be called once; further calls are ignored.
|
||||
*
|
||||
* This may influence the RCIDM's RCID change policy.
|
||||
*/
|
||||
void ossl_quic_rcidm_on_handshake_complete(QUIC_RCIDM *rcidm);
|
||||
|
||||
/*
|
||||
* Notifies the RCIDM that one or more packets have been sent.
|
||||
*
|
||||
* This may influence the RCIDM's RCID change policy.
|
||||
*/
|
||||
void ossl_quic_rcidm_on_packet_sent(QUIC_RCIDM *rcidm, uint64_t num_packets);
|
||||
|
||||
/*
|
||||
* Manually request switching to a new RCID as soon as possible.
|
||||
*/
|
||||
void ossl_quic_rcidm_request_roll(QUIC_RCIDM *rcidm);
|
||||
|
||||
/*
|
||||
* Queries
|
||||
* =======
|
||||
*/
|
||||
|
||||
/*
|
||||
* The RCIDM decides when it will never use a given RCID again. When it does
|
||||
* this, it outputs the sequence number of that RCID using this function, which
|
||||
* pops from a logical queue of retired RCIDs. The caller is responsible
|
||||
* for polling this function and generating Retire CID frames from the result.
|
||||
*
|
||||
* If nothing needs doing and the queue is empty, this function returns 0. If
|
||||
* there is an RCID which needs retiring, the sequence number of that RCID is
|
||||
* written to *seq_num (if seq_num is non-NULL) and this function returns 1. The
|
||||
* queue entry is popped (and the caller is thus assumed to have taken
|
||||
* responsibility for transmitting the necessary Retire CID frame).
|
||||
*
|
||||
* Note that the caller should not transmit a Retire CID frame immediately as
|
||||
* packets using the RCID may still be in flight. The caller must determine an
|
||||
* appropriate delay using knowledge of network conditions (RTT, etc.) which is
|
||||
* outside the scope of the RCIDM. The caller is responsible for implementing
|
||||
* this delay.
|
||||
*/
|
||||
int ossl_quic_rcidm_pop_retire_seq_num(QUIC_RCIDM *rcid, uint64_t *seq_num);
|
||||
|
||||
/*
|
||||
* Like ossl_quic_rcidm_pop_retire_seek_num, but does not pop the item from the
|
||||
* queue. If this call succeeds, the next call to
|
||||
* ossl_quic_rcidm_pop_retire_seq_num is guaranteed to output the same sequence
|
||||
* number.
|
||||
*/
|
||||
int ossl_quic_rcidm_peek_retire_seq_num(QUIC_RCIDM *rcid, uint64_t *seq_num);
|
||||
|
||||
/*
|
||||
* Writes the DCID preferred for a newly transmitted packet at this time to
|
||||
* *tx_dcid. This function should be called to determine what DCID to use when
|
||||
* transmitting a packet to the peer. The RCIDM may implement arbitrary policy
|
||||
* to decide when to change the preferred RCID.
|
||||
*
|
||||
* Returns 1 on success and 0 on failure.
|
||||
*/
|
||||
int ossl_quic_rcidm_get_preferred_tx_dcid(QUIC_RCIDM *rcidm,
|
||||
QUIC_CONN_ID *tx_dcid);
|
||||
|
||||
/*
|
||||
* Returns 1 if the value output by ossl_quic_rcidm_get_preferred_tx_dcid() has
|
||||
* changed since the last call to this function with clear set. If clear is set,
|
||||
* clears the changed flag. Returns the old value of the changed flag.
|
||||
*/
|
||||
int ossl_quic_rcidm_get_preferred_tx_dcid_changed(QUIC_RCIDM *rcidm,
|
||||
int clear);
|
||||
|
||||
# endif
|
||||
|
||||
#endif
|
@ -15,5 +15,6 @@ SOURCE[$LIBSSL]=quic_tls.c
|
||||
SOURCE[$LIBSSL]=quic_thread_assist.c
|
||||
SOURCE[$LIBSSL]=quic_trace.c
|
||||
SOURCE[$LIBSSL]=quic_srtm.c quic_srt_gen.c
|
||||
SOURCE[$LIBSSL]=quic_lcidm.c
|
||||
SOURCE[$LIBSSL]=quic_lcidm.c quic_rcidm.c
|
||||
SOURCE[$LIBSSL]=quic_types.c
|
||||
SOURCE[$LIBSSL]=quic_lcidm.c
|
||||
|
619
ssl/quic/quic_rcidm.c
Normal file
619
ssl/quic/quic_rcidm.c
Normal file
@ -0,0 +1,619 @@
|
||||
/*
|
||||
* Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include "internal/quic_rcidm.h"
|
||||
#include "internal/priority_queue.h"
|
||||
#include "internal/list.h"
|
||||
#include "internal/common.h"
|
||||
|
||||
/*
|
||||
* QUIC Remote Connection ID Manager
|
||||
* =================================
|
||||
*
|
||||
* We can receive an arbitrary number of RCIDs via NCID frames. Periodically, we
|
||||
* may desire (for example for anti-connection fingerprinting reasons, etc.)
|
||||
* to switch to a new RCID according to some arbitrary policy such as the number
|
||||
* of packets we have sent.
|
||||
*
|
||||
* When we do this we should move to the next RCID in the sequence of received
|
||||
* RCIDs ordered by sequence number. For example, if a peer sends us three NCID
|
||||
* frames with sequence numbers 10, 11, 12, we should seek to consume these
|
||||
* RCIDs in order.
|
||||
*
|
||||
* However, due to the possibility of packet reordering in the network, NCID
|
||||
* frames might be received out of order. Thus if a peer sends us NCID frames
|
||||
* with sequence numbers 12, 10, 11, we should still consume the RCID with
|
||||
* sequence number 10 before consuming the RCIDs with sequence numbers 11 or 12.
|
||||
*
|
||||
* We use a priority queue for this purpose.
|
||||
*/
|
||||
static void rcidm_update(QUIC_RCIDM *rcidm);
|
||||
static void rcidm_set_preferred_rcid(QUIC_RCIDM *rcidm,
|
||||
const QUIC_CONN_ID *rcid);
|
||||
|
||||
#define PACKETS_PER_RCID 1000
|
||||
|
||||
#define INITIAL_SEQ_NUM 0
|
||||
#define PREF_ADDR_SEQ_NUM 1
|
||||
|
||||
/*
|
||||
* RCID
|
||||
* ====
|
||||
*
|
||||
* The RCID structure is used to track RCIDs which have sequence numbers (i.e.,
|
||||
* INITIAL, PREF_ADDR and NCID type RCIDs). The RCIDs without sequence numbers
|
||||
* (Initial ODCIDs and Retry ODCIDs), hereafter referred to as unnumbered RCIDs,
|
||||
* can logically be viewed as their own type of RCID but are tracked separately
|
||||
* as singletons without needing a discrete structure.
|
||||
*
|
||||
* At any given time an RCID object is in one of these states:
|
||||
*
|
||||
*
|
||||
* (start)
|
||||
* |
|
||||
* [add]
|
||||
* |
|
||||
* _____v_____ ___________ ____________
|
||||
* | | | | | |
|
||||
* | PENDING | --[select]--> | CURRENT | --[retire]--> | RETIRING |
|
||||
* |___________| |___________| |____________|
|
||||
* |
|
||||
* [pop]
|
||||
* |
|
||||
* v
|
||||
* (fin)
|
||||
*
|
||||
* The transition through the states is monotonic and irreversible.
|
||||
* The RCID object is freed when it is popped.
|
||||
*
|
||||
* PENDING
|
||||
* Invariants:
|
||||
* rcid->state == RCID_STATE_PENDING;
|
||||
* rcid->pq_idx != SIZE_MAX (debug assert only);
|
||||
* the RCID is not the current RCID, rcidm->cur_rcid != rcid;
|
||||
* the RCID is in the priority queue;
|
||||
* the RCID is not in the retiring_list.
|
||||
*
|
||||
* CURRENT
|
||||
* Invariants:
|
||||
* rcid->state == RCID_STATE_CUR;
|
||||
* rcid->pq_idx == SIZE_MAX (debug assert only);
|
||||
* the RCID is the current RCID, rcidm->cur_rcid == rcid;
|
||||
* the RCID is not in the priority queue;
|
||||
* the RCID is not in the retiring_list.
|
||||
*
|
||||
* RETIRE
|
||||
* Invariants:
|
||||
* rcid->state == RCID_STATE_RETIRE;
|
||||
* rcid->pq_idx == SIZE_MAX (debug assert only);
|
||||
* the RCID is not the current RCID, rcidm->cur_rcid != rcid;
|
||||
* the RCID is not in the priority queue;
|
||||
* the RCID is not in the retiring_list.
|
||||
*
|
||||
* Invariant: At most one RCID object is in the CURRENT state at any one time.
|
||||
*
|
||||
* (If no RCID object is in the CURRENT state, this means either
|
||||
* an unnumbered RCID is being used as the preferred RCID
|
||||
* or we currently have no preferred RCID.)
|
||||
*/
|
||||
enum {
|
||||
RCID_STATE_PENDING,
|
||||
RCID_STATE_CUR,
|
||||
RCID_STATE_RETIRING,
|
||||
};
|
||||
|
||||
enum {
|
||||
RCID_TYPE_INITIAL, /* CID is from an peer INITIAL packet (seq 0) */
|
||||
RCID_TYPE_PREF_ADDR, /* CID is from a preferred_address TPARAM (seq 1) */
|
||||
RCID_TYPE_NCID, /* CID is from a NCID frame */
|
||||
/*
|
||||
* INITIAL_ODCID and RETRY_ODCID also conceptually exist but are tracked
|
||||
* separately.
|
||||
*/
|
||||
};
|
||||
|
||||
typedef struct rcid_st {
|
||||
OSSL_LIST_MEMBER(retiring, struct rcid_st); /* valid iff retire == 1 */
|
||||
|
||||
QUIC_CONN_ID cid; /* The actual CID string for this RCID */
|
||||
uint64_t seq_num;
|
||||
size_t pq_idx; /* Index of entry into priority queue */
|
||||
unsigned int state : 2; /* RCID_STATE_* */
|
||||
unsigned int type : 2; /* RCID_TYPE_* */
|
||||
} RCID;
|
||||
|
||||
DEFINE_PRIORITY_QUEUE_OF(RCID);
|
||||
DEFINE_LIST_OF(retiring, RCID);
|
||||
|
||||
/*
|
||||
* RCID Manager
|
||||
* ============
|
||||
*
|
||||
* The following "business logic" invariants also apply to the RCIDM
|
||||
* as a whole:
|
||||
*
|
||||
* Invariant: An RCID of INITIAL type has a sequence number of 0.
|
||||
* Invariant: An RCID of PREF_ADDR type has a sequence number of 1.
|
||||
*
|
||||
* Invariant: There is never more than one Initial ODCID
|
||||
* added throughout the lifetime of an RCIDM.
|
||||
* Invariant: There is never more than one Retry ODCID
|
||||
* added throughout the lifetime of an RCIDM.
|
||||
* Invariant: There is never more than one INITIAL RCID created
|
||||
* throughout the lifetime of an RCIDM.
|
||||
* Invariant: There is never more than one PREF_ADDR RCID created
|
||||
* throughout the lifetime of an RCIDM.
|
||||
* Invariant: No INITIAL or PREF_ADDR RCID may be added after
|
||||
* the handshake is completed.
|
||||
*
|
||||
*/
|
||||
struct quic_rcidm_st {
|
||||
/*
|
||||
* The current RCID we prefer to use (value undefined if
|
||||
* !have_preferred_rcid).
|
||||
*/
|
||||
QUIC_CONN_ID preferred_rcid;
|
||||
|
||||
/*
|
||||
* These are initialized if the corresponding added_ flags are set.
|
||||
*/
|
||||
QUIC_CONN_ID initial_odcid, retry_odcid;
|
||||
|
||||
/*
|
||||
* Total number of packets sent since we last made a packet count-based RCID
|
||||
* update decision.
|
||||
*/
|
||||
uint64_t packets_sent;
|
||||
|
||||
/* Number of post-handshake RCID changes we have performed. */
|
||||
uint64_t num_changes;
|
||||
|
||||
/*
|
||||
* The Retire Prior To watermark value; max(retire_prior_to) of all received
|
||||
* NCID frames.
|
||||
*/
|
||||
uint64_t retire_prior_to;
|
||||
|
||||
/* (SORT BY seq_num ASC) -> (RCID *) */
|
||||
PRIORITY_QUEUE_OF(RCID) *rcids;
|
||||
|
||||
/*
|
||||
* Current RCID we are using. This may differ from the first item in the
|
||||
* priority queue if we received NCID frames out of order. For example if we
|
||||
* get seq 5, switch to it immediately, then get seq 4, we want to keep
|
||||
* using seq 5 until we decide to roll again rather than immediately switch
|
||||
* to seq 4. Never points to an object on the retiring_list.
|
||||
*/
|
||||
RCID *cur_rcid;
|
||||
|
||||
/*
|
||||
* When a RCID becomes pending-retirement, it is moved to the retiring_list,
|
||||
* then freed when it is popped from the retired queue. We use a list for
|
||||
* this rather than a priority queue as the order in which items are freed
|
||||
* does not matter. We always append to the tail of the list in order to
|
||||
* maintain the guarantee that the head (if present) only changes when a
|
||||
* caller calls pop().
|
||||
*/
|
||||
OSSL_LIST(retiring) retiring_list;
|
||||
|
||||
/* preferred_rcid has been changed? */
|
||||
unsigned int preferred_rcid_changed : 1;
|
||||
|
||||
/* Do we have any RCID we can use currently? */
|
||||
unsigned int have_preferred_rcid : 1;
|
||||
|
||||
/* QUIC handshake has been completed? */
|
||||
unsigned int handshake_complete : 1;
|
||||
|
||||
/* odcid was set (not necessarily still valid as a RCID)? */
|
||||
unsigned int added_initial_odcid : 1;
|
||||
/* retry_odcid was set (not necessarily still valid as a RCID?) */
|
||||
unsigned int added_retry_odcid : 1;
|
||||
/* An initial RCID was added as an RCID structure? */
|
||||
unsigned int added_initial_rcid : 1;
|
||||
/* Has a RCID roll been manually requested? */
|
||||
unsigned int roll_requested : 1;
|
||||
};
|
||||
|
||||
/* Check invariants of an RCID */
|
||||
static void rcidm_check_rcid(QUIC_RCIDM *rcidm, RCID *rcid)
|
||||
{
|
||||
assert(rcid->state == RCID_STATE_PENDING
|
||||
|| rcid->state == RCID_STATE_CUR
|
||||
|| rcid->state == RCID_STATE_RETIRING);
|
||||
assert((rcid->state == RCID_STATE_PENDING)
|
||||
== (rcid->pq_idx != SIZE_MAX));
|
||||
assert((rcid->state == RCID_STATE_CUR)
|
||||
== (rcidm->cur_rcid == rcid));
|
||||
assert((ossl_list_retiring_next(rcid) != NULL
|
||||
|| ossl_list_retiring_prev(rcid) != NULL
|
||||
|| ossl_list_retiring_head(&rcidm->retiring_list) == rcid)
|
||||
== (rcid->state == RCID_STATE_RETIRING));
|
||||
assert(rcid->type != RCID_TYPE_INITIAL || rcid->seq_num == 0);
|
||||
assert(rcid->type != RCID_TYPE_PREF_ADDR || rcid->seq_num == 1);
|
||||
assert(rcid->seq_num <= OSSL_QUIC_VLINT_MAX);
|
||||
assert(rcid->cid.id_len > 0 && rcid->cid.id_len <= QUIC_MAX_CONN_ID_LEN);
|
||||
assert(rcid->seq_num >= rcidm->retire_prior_to
|
||||
|| rcid->state == RCID_STATE_RETIRING);
|
||||
assert(rcidm->num_changes == 0 || rcidm->handshake_complete);
|
||||
}
|
||||
|
||||
static int rcid_cmp(const RCID *a, const RCID *b)
|
||||
{
|
||||
if (a->seq_num < b->seq_num)
|
||||
return -1;
|
||||
if (a->seq_num > b->seq_num)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
QUIC_RCIDM *ossl_quic_rcidm_new(const QUIC_CONN_ID *initial_odcid)
|
||||
{
|
||||
QUIC_RCIDM *rcidm;
|
||||
|
||||
if ((rcidm = OPENSSL_zalloc(sizeof(*rcidm))) == NULL)
|
||||
return NULL;
|
||||
|
||||
if ((rcidm->rcids = ossl_pqueue_RCID_new(rcid_cmp)) == NULL) {
|
||||
OPENSSL_free(rcidm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (initial_odcid != NULL) {
|
||||
rcidm->initial_odcid = *initial_odcid;
|
||||
rcidm->added_initial_odcid = 1;
|
||||
}
|
||||
|
||||
rcidm_update(rcidm);
|
||||
return rcidm;
|
||||
}
|
||||
|
||||
void ossl_quic_rcidm_free(QUIC_RCIDM *rcidm)
|
||||
{
|
||||
RCID *rcid, *rnext;
|
||||
|
||||
if (rcidm == NULL)
|
||||
return;
|
||||
|
||||
OPENSSL_free(rcidm->cur_rcid);
|
||||
while ((rcid = ossl_pqueue_RCID_pop(rcidm->rcids)) != NULL)
|
||||
OPENSSL_free(rcid);
|
||||
|
||||
LIST_FOREACH_DELSAFE(rcid, rnext, retiring, &rcidm->retiring_list)
|
||||
OPENSSL_free(rcid);
|
||||
|
||||
ossl_pqueue_RCID_free(rcidm->rcids);
|
||||
OPENSSL_free(rcidm);
|
||||
}
|
||||
|
||||
static void rcidm_set_preferred_rcid(QUIC_RCIDM *rcidm,
|
||||
const QUIC_CONN_ID *rcid)
|
||||
{
|
||||
if (rcid == NULL) {
|
||||
rcidm->preferred_rcid_changed = 1;
|
||||
rcidm->have_preferred_rcid = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ossl_quic_conn_id_eq(&rcidm->preferred_rcid, rcid))
|
||||
return;
|
||||
|
||||
rcidm->preferred_rcid = *rcid;
|
||||
rcidm->preferred_rcid_changed = 1;
|
||||
rcidm->have_preferred_rcid = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* RCID Lifecycle Management
|
||||
* =========================
|
||||
*/
|
||||
static RCID *rcidm_create_rcid(QUIC_RCIDM *rcidm, uint64_t seq_num,
|
||||
const QUIC_CONN_ID *cid,
|
||||
unsigned int type)
|
||||
{
|
||||
RCID *rcid;
|
||||
|
||||
if ((rcid = OPENSSL_zalloc(sizeof(*rcid))) == NULL)
|
||||
return NULL;
|
||||
|
||||
rcid->seq_num = seq_num;
|
||||
rcid->cid = *cid;
|
||||
rcid->type = type;
|
||||
rcid->state = RCID_STATE_PENDING;
|
||||
|
||||
if (!ossl_pqueue_RCID_push(rcidm->rcids, rcid, &rcid->pq_idx)) {
|
||||
OPENSSL_free(rcid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rcidm_check_rcid(rcidm, rcid);
|
||||
return rcid;
|
||||
}
|
||||
|
||||
static void rcidm_transition_rcid(QUIC_RCIDM *rcidm, RCID *rcid,
|
||||
unsigned int state)
|
||||
{
|
||||
unsigned int old_state = rcid->state;
|
||||
|
||||
assert(state >= old_state && state <= RCID_STATE_RETIRING);
|
||||
rcidm_check_rcid(rcidm, rcid);
|
||||
if (state == old_state)
|
||||
return;
|
||||
|
||||
if (rcidm->cur_rcid != NULL && state == RCID_STATE_CUR) {
|
||||
rcidm_transition_rcid(rcidm, rcidm->cur_rcid, RCID_STATE_RETIRING);
|
||||
assert(rcidm->cur_rcid == NULL);
|
||||
}
|
||||
|
||||
if (old_state == RCID_STATE_PENDING) {
|
||||
ossl_pqueue_RCID_remove(rcidm->rcids, rcid->pq_idx);
|
||||
rcid->pq_idx = SIZE_MAX;
|
||||
}
|
||||
|
||||
rcid->state = state;
|
||||
|
||||
if (state == RCID_STATE_CUR) {
|
||||
rcidm->cur_rcid = rcid;
|
||||
} else if (state == RCID_STATE_RETIRING) {
|
||||
if (old_state == RCID_STATE_CUR)
|
||||
rcidm->cur_rcid = NULL;
|
||||
|
||||
ossl_list_retiring_insert_tail(&rcidm->retiring_list, rcid);
|
||||
}
|
||||
|
||||
rcidm_check_rcid(rcidm, rcid);
|
||||
}
|
||||
|
||||
static void rcidm_free_rcid(QUIC_RCIDM *rcidm, RCID *rcid)
|
||||
{
|
||||
if (rcid == NULL)
|
||||
return;
|
||||
|
||||
rcidm_check_rcid(rcidm, rcid);
|
||||
|
||||
switch (rcid->state) {
|
||||
case RCID_STATE_PENDING:
|
||||
ossl_pqueue_RCID_remove(rcidm->rcids, rcid->pq_idx);
|
||||
break;
|
||||
case RCID_STATE_CUR:
|
||||
rcidm->cur_rcid = NULL;
|
||||
break;
|
||||
case RCID_STATE_RETIRING:
|
||||
ossl_list_retiring_remove(&rcidm->retiring_list, rcid);
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
OPENSSL_free(rcid);
|
||||
}
|
||||
|
||||
static void rcidm_handle_retire_prior_to(QUIC_RCIDM *rcidm,
|
||||
uint64_t retire_prior_to)
|
||||
{
|
||||
RCID *rcid;
|
||||
|
||||
if (retire_prior_to <= rcidm->retire_prior_to)
|
||||
return;
|
||||
|
||||
rcidm->retire_prior_to = retire_prior_to;
|
||||
|
||||
/*
|
||||
* Any RCIDs needing retirement will be at the start of the priority queue,
|
||||
* so just stop once we see a higher sequence number exceeding the
|
||||
* threshold.
|
||||
*/
|
||||
while ((rcid = ossl_pqueue_RCID_peek(rcidm->rcids)) != NULL
|
||||
&& rcid->seq_num < retire_prior_to)
|
||||
rcidm_transition_rcid(rcidm, rcid, RCID_STATE_RETIRING);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decision Logic
|
||||
* ==============
|
||||
*/
|
||||
|
||||
static void rcidm_roll(QUIC_RCIDM *rcidm)
|
||||
{
|
||||
RCID *rcid;
|
||||
|
||||
if (ossl_pqueue_RCID_num(rcidm->rcids) < 1)
|
||||
return;
|
||||
|
||||
rcid = ossl_pqueue_RCID_peek(rcidm->rcids);
|
||||
rcidm_transition_rcid(rcidm, rcid, RCID_STATE_CUR);
|
||||
|
||||
++rcidm->num_changes;
|
||||
rcidm->roll_requested = 0;
|
||||
|
||||
if (rcidm->packets_sent >= PACKETS_PER_RCID)
|
||||
rcidm->packets_sent %= PACKETS_PER_RCID;
|
||||
else
|
||||
rcidm->packets_sent = 0;
|
||||
}
|
||||
|
||||
static void rcidm_update(QUIC_RCIDM *rcidm)
|
||||
{
|
||||
/* Prefer use of any current numbered RCID we have, if possible. */
|
||||
if (rcidm->cur_rcid != NULL) {
|
||||
rcidm_check_rcid(rcidm, rcidm->cur_rcid);
|
||||
rcidm_set_preferred_rcid(rcidm, &rcidm->cur_rcid->cid);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are no RCIDs from NCID frames we can use, go through the various
|
||||
* kinds of bootstrapping RCIDs we can use in order of priority.
|
||||
*/
|
||||
if (rcidm->added_retry_odcid) {
|
||||
rcidm_set_preferred_rcid(rcidm, &rcidm->retry_odcid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rcidm->added_initial_odcid && !rcidm->handshake_complete) {
|
||||
rcidm_set_preferred_rcid(rcidm, &rcidm->initial_odcid);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We don't know of any usable RCIDs */
|
||||
rcidm_set_preferred_rcid(rcidm, NULL);
|
||||
}
|
||||
|
||||
static int rcidm_should_roll(QUIC_RCIDM *rcidm)
|
||||
{
|
||||
/*
|
||||
* Always switch as soon as possible if handshake completes;
|
||||
* and every n packets after handshake completes or the last roll; and
|
||||
* whenever manually requested.
|
||||
*/
|
||||
return rcidm->handshake_complete
|
||||
&& (rcidm->num_changes == 0
|
||||
|| rcidm->packets_sent >= PACKETS_PER_RCID
|
||||
|| rcidm->roll_requested);
|
||||
}
|
||||
|
||||
static void rcidm_tick(QUIC_RCIDM *rcidm)
|
||||
{
|
||||
if (rcidm_should_roll(rcidm))
|
||||
rcidm_roll(rcidm);
|
||||
|
||||
rcidm_update(rcidm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Events
|
||||
* ======
|
||||
*/
|
||||
void ossl_quic_rcidm_on_handshake_complete(QUIC_RCIDM *rcidm)
|
||||
{
|
||||
if (rcidm->handshake_complete)
|
||||
return;
|
||||
|
||||
rcidm->handshake_complete = 1;
|
||||
rcidm_tick(rcidm);
|
||||
}
|
||||
|
||||
void ossl_quic_rcidm_on_packet_sent(QUIC_RCIDM *rcidm, uint64_t num_packets)
|
||||
{
|
||||
if (num_packets == 0)
|
||||
return;
|
||||
|
||||
rcidm->packets_sent += num_packets;
|
||||
rcidm_tick(rcidm);
|
||||
}
|
||||
|
||||
void ossl_quic_rcidm_request_roll(QUIC_RCIDM *rcidm)
|
||||
{
|
||||
rcidm->roll_requested = 1;
|
||||
rcidm_tick(rcidm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mutation Operations
|
||||
* ===================
|
||||
*/
|
||||
int ossl_quic_rcidm_add_from_initial(QUIC_RCIDM *rcidm,
|
||||
const QUIC_CONN_ID *rcid)
|
||||
{
|
||||
RCID *rcid_obj;
|
||||
|
||||
if (rcidm->added_initial_rcid || rcidm->handshake_complete)
|
||||
return 0;
|
||||
|
||||
rcid_obj = rcidm_create_rcid(rcidm, INITIAL_SEQ_NUM,
|
||||
rcid, RCID_TYPE_INITIAL);
|
||||
if (rcid_obj == NULL)
|
||||
return 0;
|
||||
|
||||
rcidm->added_initial_rcid = 1;
|
||||
rcidm_tick(rcidm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ossl_quic_rcidm_add_from_server_retry(QUIC_RCIDM *rcidm,
|
||||
const QUIC_CONN_ID *retry_odcid)
|
||||
{
|
||||
if (rcidm->added_retry_odcid || rcidm->handshake_complete)
|
||||
return 0;
|
||||
|
||||
rcidm->retry_odcid = *retry_odcid;
|
||||
rcidm->added_retry_odcid = 1;
|
||||
rcidm_tick(rcidm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ossl_quic_rcidm_add_from_ncid(QUIC_RCIDM *rcidm,
|
||||
const OSSL_QUIC_FRAME_NEW_CONN_ID *ncid)
|
||||
{
|
||||
RCID *rcid;
|
||||
|
||||
rcidm_handle_retire_prior_to(rcidm, ncid->retire_prior_to);
|
||||
|
||||
rcid = rcidm_create_rcid(rcidm, ncid->seq_num, &ncid->conn_id, RCID_TYPE_NCID);
|
||||
if (rcid == NULL)
|
||||
return 0;
|
||||
|
||||
rcidm_tick(rcidm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Queries
|
||||
* =======
|
||||
*/
|
||||
|
||||
static int rcidm_get_retire(QUIC_RCIDM *rcidm, uint64_t *seq_num, int peek)
|
||||
{
|
||||
RCID *rcid = ossl_list_retiring_head(&rcidm->retiring_list);
|
||||
|
||||
if (rcid == NULL)
|
||||
return 0;
|
||||
|
||||
if (seq_num != NULL)
|
||||
*seq_num = rcid->seq_num;
|
||||
|
||||
if (!peek)
|
||||
rcidm_free_rcid(rcidm, rcid);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ossl_quic_rcidm_pop_retire_seq_num(QUIC_RCIDM *rcidm,
|
||||
uint64_t *seq_num)
|
||||
{
|
||||
return rcidm_get_retire(rcidm, seq_num, /*peek=*/0);
|
||||
}
|
||||
|
||||
int ossl_quic_rcidm_peek_retire_seq_num(QUIC_RCIDM *rcidm,
|
||||
uint64_t *seq_num)
|
||||
{
|
||||
return rcidm_get_retire(rcidm, seq_num, /*peek=*/1);
|
||||
}
|
||||
|
||||
int ossl_quic_rcidm_get_preferred_tx_dcid(QUIC_RCIDM *rcidm,
|
||||
QUIC_CONN_ID *tx_dcid)
|
||||
{
|
||||
if (!rcidm->have_preferred_rcid)
|
||||
return 0;
|
||||
|
||||
*tx_dcid = rcidm->preferred_rcid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ossl_quic_rcidm_get_preferred_tx_dcid_changed(QUIC_RCIDM *rcidm,
|
||||
int clear)
|
||||
{
|
||||
int r = rcidm->preferred_rcid_changed;
|
||||
|
||||
if (clear)
|
||||
rcidm->preferred_rcid_changed = 0;
|
||||
|
||||
return r;
|
||||
}
|
Loading…
Reference in New Issue
Block a user