2 * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
4 * Licensed under the Apache License 2.0 (the "License"). You may not use
5 * this file except in compliance with the License. You can obtain a copy
6 * in the file LICENSE in the source distribution or at
7 * https://www.openssl.org/source/license.html
10 #include "internal/quic_rcidm.h"
11 #include "internal/priority_queue.h"
12 #include "internal/list.h"
13 #include "internal/common.h"
16 * QUIC Remote Connection ID Manager
17 * =================================
19 * We can receive an arbitrary number of RCIDs via NCID frames. Periodically, we
20 * may desire (for example for anti-connection fingerprinting reasons, etc.)
21 * to switch to a new RCID according to some arbitrary policy such as the number
22 * of packets we have sent.
24 * When we do this we should move to the next RCID in the sequence of received
25 * RCIDs ordered by sequence number. For example, if a peer sends us three NCID
26 * frames with sequence numbers 10, 11, 12, we should seek to consume these
29 * However, due to the possibility of packet reordering in the network, NCID
30 * frames might be received out of order. Thus if a peer sends us NCID frames
31 * with sequence numbers 12, 10, 11, we should still consume the RCID with
32 * sequence number 10 before consuming the RCIDs with sequence numbers 11 or 12.
34 * We use a priority queue for this purpose.
36 static void rcidm_update(QUIC_RCIDM
*rcidm
);
37 static void rcidm_set_preferred_rcid(QUIC_RCIDM
*rcidm
,
38 const QUIC_CONN_ID
*rcid
);
40 #define PACKETS_PER_RCID 1000
42 #define INITIAL_SEQ_NUM 0
43 #define PREF_ADDR_SEQ_NUM 1
49 * The RCID structure is used to track RCIDs which have sequence numbers (i.e.,
50 * INITIAL, PREF_ADDR and NCID type RCIDs). The RCIDs without sequence numbers
51 * (Initial ODCIDs and Retry ODCIDs), hereafter referred to as unnumbered RCIDs,
52 * can logically be viewed as their own type of RCID but are tracked separately
53 * as singletons without needing a discrete structure.
55 * At any given time an RCID object is in one of these states:
62 * _____v_____ ___________ ____________
64 * | PENDING | --[select]--> | CURRENT | --[retire]--> | RETIRING |
65 * |___________| |___________| |____________|
72 * The transition through the states is monotonic and irreversible.
73 * The RCID object is freed when it is popped.
77 * rcid->state == RCID_STATE_PENDING;
78 * rcid->pq_idx != SIZE_MAX (debug assert only);
79 * the RCID is not the current RCID, rcidm->cur_rcid != rcid;
80 * the RCID is in the priority queue;
81 * the RCID is not in the retiring_list.
85 * rcid->state == RCID_STATE_CUR;
86 * rcid->pq_idx == SIZE_MAX (debug assert only);
87 * the RCID is the current RCID, rcidm->cur_rcid == rcid;
88 * the RCID is not in the priority queue;
89 * the RCID is not in the retiring_list.
93 * rcid->state == RCID_STATE_RETIRING;
94 * rcid->pq_idx == SIZE_MAX (debug assert only);
95 * the RCID is not the current RCID, rcidm->cur_rcid != rcid;
96 * the RCID is not in the priority queue;
97 * the RCID is in the retiring_list.
99 * Invariant: At most one RCID object is in the CURRENT state at any one time.
101 * (If no RCID object is in the CURRENT state, this means either
102 * an unnumbered RCID is being used as the preferred RCID
103 * or we currently have no preferred RCID.)
105 * All of the above states can be considered substates of the 'ACTIVE' state
106 * for an RCID as specified in RFC 9000. A CID only ceases to be active
107 * when we send a RETIRE_CONN_ID frame, which is the responsibility of the
108 * user of the RCIDM and happens after the above state machine is terminated.
117 RCID_TYPE_INITIAL
, /* CID is from an peer INITIAL packet (seq 0) */
118 RCID_TYPE_PREF_ADDR
, /* CID is from a preferred_address TPARAM (seq 1) */
119 RCID_TYPE_NCID
/* CID is from a NCID frame */
121 * INITIAL_ODCID and RETRY_ODCID also conceptually exist but are tracked
126 typedef struct rcid_st
{
127 OSSL_LIST_MEMBER(retiring
, struct rcid_st
); /* valid iff retire == 1 */
129 QUIC_CONN_ID cid
; /* The actual CID string for this RCID */
131 size_t pq_idx
; /* Index of entry into priority queue */
132 unsigned int state
: 2; /* RCID_STATE_* */
133 unsigned int type
: 2; /* RCID_TYPE_* */
136 DEFINE_PRIORITY_QUEUE_OF(RCID
);
137 DEFINE_LIST_OF(retiring
, RCID
);
143 * The following "business logic" invariants also apply to the RCIDM
146 * Invariant: An RCID of INITIAL type has a sequence number of 0.
147 * Invariant: An RCID of PREF_ADDR type has a sequence number of 1.
149 * Invariant: There is never more than one Initial ODCID
150 * added throughout the lifetime of an RCIDM.
151 * Invariant: There is never more than one Retry ODCID
152 * added throughout the lifetime of an RCIDM.
153 * Invariant: There is never more than one INITIAL RCID created
154 * throughout the lifetime of an RCIDM.
155 * Invariant: There is never more than one PREF_ADDR RCID created
156 * throughout the lifetime of an RCIDM.
157 * Invariant: No INITIAL or PREF_ADDR RCID may be added after
158 * the handshake is completed.
161 struct quic_rcidm_st
{
163 * The current RCID we prefer to use (value undefined if
164 * !have_preferred_rcid).
166 QUIC_CONN_ID preferred_rcid
;
169 * These are initialized if the corresponding added_ flags are set.
171 QUIC_CONN_ID initial_odcid
, retry_odcid
;
174 * Total number of packets sent since we last made a packet count-based RCID
177 uint64_t packets_sent
;
179 /* Number of post-handshake RCID changes we have performed. */
180 uint64_t num_changes
;
183 * The Retire Prior To watermark value; max(retire_prior_to) of all received
186 uint64_t retire_prior_to
;
188 /* (SORT BY seq_num ASC) -> (RCID *) */
189 PRIORITY_QUEUE_OF(RCID
) *rcids
;
192 * Current RCID we are using. This may differ from the first item in the
193 * priority queue if we received NCID frames out of order. For example if we
194 * get seq 5, switch to it immediately, then get seq 4, we want to keep
195 * using seq 5 until we decide to roll again rather than immediately switch
196 * to seq 4. Never points to an object on the retiring_list.
201 * When a RCID becomes pending-retirement, it is moved to the retiring_list,
202 * then freed when it is popped from the retired queue. We use a list for
203 * this rather than a priority queue as the order in which items are freed
204 * does not matter. We always append to the tail of the list in order to
205 * maintain the guarantee that the head (if present) only changes when a
206 * caller calls pop().
208 OSSL_LIST(retiring
) retiring_list
;
210 /* Number of entries on the retiring_list. */
213 /* preferred_rcid has been changed? */
214 unsigned int preferred_rcid_changed
: 1;
216 /* Do we have any RCID we can use currently? */
217 unsigned int have_preferred_rcid
: 1;
219 /* QUIC handshake has been completed? */
220 unsigned int handshake_complete
: 1;
222 /* odcid was set (not necessarily still valid as a RCID)? */
223 unsigned int added_initial_odcid
: 1;
224 /* retry_odcid was set (not necessarily still valid as a RCID?) */
225 unsigned int added_retry_odcid
: 1;
226 /* An initial RCID was added as an RCID structure? */
227 unsigned int added_initial_rcid
: 1;
228 /* Has a RCID roll been manually requested? */
229 unsigned int roll_requested
: 1;
232 static void rcidm_transition_rcid(QUIC_RCIDM
*rcidm
, RCID
*rcid
,
235 /* Check invariants of an RCID */
236 static void rcidm_check_rcid(QUIC_RCIDM
*rcidm
, RCID
*rcid
)
238 assert(rcid
->state
== RCID_STATE_PENDING
239 || rcid
->state
== RCID_STATE_CUR
240 || rcid
->state
== RCID_STATE_RETIRING
);
241 assert((rcid
->state
== RCID_STATE_PENDING
)
242 == (rcid
->pq_idx
!= SIZE_MAX
));
243 assert((rcid
->state
== RCID_STATE_CUR
)
244 == (rcidm
->cur_rcid
== rcid
));
245 assert((ossl_list_retiring_next(rcid
) != NULL
246 || ossl_list_retiring_prev(rcid
) != NULL
247 || ossl_list_retiring_head(&rcidm
->retiring_list
) == rcid
)
248 == (rcid
->state
== RCID_STATE_RETIRING
));
249 assert(rcid
->type
!= RCID_TYPE_INITIAL
|| rcid
->seq_num
== 0);
250 assert(rcid
->type
!= RCID_TYPE_PREF_ADDR
|| rcid
->seq_num
== 1);
251 assert(rcid
->seq_num
<= OSSL_QUIC_VLINT_MAX
);
252 assert(rcid
->cid
.id_len
> 0 && rcid
->cid
.id_len
<= QUIC_MAX_CONN_ID_LEN
);
253 assert(rcid
->seq_num
>= rcidm
->retire_prior_to
254 || rcid
->state
== RCID_STATE_RETIRING
);
255 assert(rcidm
->num_changes
== 0 || rcidm
->handshake_complete
);
256 assert(rcid
->state
!= RCID_STATE_RETIRING
|| rcidm
->num_retiring
> 0);
257 assert(rcidm
->num_retiring
< SIZE_MAX
/ 2);
260 static int rcid_cmp(const RCID
*a
, const RCID
*b
)
262 if (a
->seq_num
< b
->seq_num
)
264 if (a
->seq_num
> b
->seq_num
)
266 if ((uintptr_t)a
< (uintptr_t)b
)
268 if ((uintptr_t)a
> (uintptr_t)b
)
273 QUIC_RCIDM
*ossl_quic_rcidm_new(const QUIC_CONN_ID
*initial_odcid
)
277 if ((rcidm
= OPENSSL_zalloc(sizeof(*rcidm
))) == NULL
)
280 if ((rcidm
->rcids
= ossl_pqueue_RCID_new(rcid_cmp
)) == NULL
) {
285 if (initial_odcid
!= NULL
) {
286 rcidm
->initial_odcid
= *initial_odcid
;
287 rcidm
->added_initial_odcid
= 1;
294 void ossl_quic_rcidm_free(QUIC_RCIDM
*rcidm
)
301 OPENSSL_free(rcidm
->cur_rcid
);
302 while ((rcid
= ossl_pqueue_RCID_pop(rcidm
->rcids
)) != NULL
)
305 LIST_FOREACH_DELSAFE(rcid
, rnext
, retiring
, &rcidm
->retiring_list
)
308 ossl_pqueue_RCID_free(rcidm
->rcids
);
312 static void rcidm_set_preferred_rcid(QUIC_RCIDM
*rcidm
,
313 const QUIC_CONN_ID
*rcid
)
316 rcidm
->preferred_rcid_changed
= 1;
317 rcidm
->have_preferred_rcid
= 0;
321 if (ossl_quic_conn_id_eq(&rcidm
->preferred_rcid
, rcid
))
324 rcidm
->preferred_rcid
= *rcid
;
325 rcidm
->preferred_rcid_changed
= 1;
326 rcidm
->have_preferred_rcid
= 1;
330 * RCID Lifecycle Management
331 * =========================
333 static RCID
*rcidm_create_rcid(QUIC_RCIDM
*rcidm
, uint64_t seq_num
,
334 const QUIC_CONN_ID
*cid
,
339 if (cid
->id_len
< 1 || cid
->id_len
> QUIC_MAX_CONN_ID_LEN
340 || seq_num
> OSSL_QUIC_VLINT_MAX
)
343 if ((rcid
= OPENSSL_zalloc(sizeof(*rcid
))) == NULL
)
346 rcid
->seq_num
= seq_num
;
350 if (rcid
->seq_num
>= rcidm
->retire_prior_to
) {
351 rcid
->state
= RCID_STATE_PENDING
;
353 if (!ossl_pqueue_RCID_push(rcidm
->rcids
, rcid
, &rcid
->pq_idx
)) {
359 /* RCID is immediately retired upon creation. */
360 rcid
->state
= RCID_STATE_RETIRING
;
361 rcid
->pq_idx
= SIZE_MAX
;
362 ossl_list_retiring_insert_tail(&rcidm
->retiring_list
, rcid
);
363 ++rcidm
->num_retiring
;
366 rcidm_check_rcid(rcidm
, rcid
);
370 static void rcidm_transition_rcid(QUIC_RCIDM
*rcidm
, RCID
*rcid
,
373 unsigned int old_state
= rcid
->state
;
375 assert(state
>= old_state
&& state
<= RCID_STATE_RETIRING
);
376 rcidm_check_rcid(rcidm
, rcid
);
377 if (state
== old_state
)
380 if (rcidm
->cur_rcid
!= NULL
&& state
== RCID_STATE_CUR
) {
381 rcidm_transition_rcid(rcidm
, rcidm
->cur_rcid
, RCID_STATE_RETIRING
);
382 assert(rcidm
->cur_rcid
== NULL
);
385 if (old_state
== RCID_STATE_PENDING
) {
386 ossl_pqueue_RCID_remove(rcidm
->rcids
, rcid
->pq_idx
);
387 rcid
->pq_idx
= SIZE_MAX
;
392 if (state
== RCID_STATE_CUR
) {
393 rcidm
->cur_rcid
= rcid
;
394 } else if (state
== RCID_STATE_RETIRING
) {
395 if (old_state
== RCID_STATE_CUR
)
396 rcidm
->cur_rcid
= NULL
;
398 ossl_list_retiring_insert_tail(&rcidm
->retiring_list
, rcid
);
399 ++rcidm
->num_retiring
;
402 rcidm_check_rcid(rcidm
, rcid
);
405 static void rcidm_free_rcid(QUIC_RCIDM
*rcidm
, RCID
*rcid
)
410 rcidm_check_rcid(rcidm
, rcid
);
412 switch (rcid
->state
) {
413 case RCID_STATE_PENDING
:
414 ossl_pqueue_RCID_remove(rcidm
->rcids
, rcid
->pq_idx
);
417 rcidm
->cur_rcid
= NULL
;
419 case RCID_STATE_RETIRING
:
420 ossl_list_retiring_remove(&rcidm
->retiring_list
, rcid
);
421 --rcidm
->num_retiring
;
431 static void rcidm_handle_retire_prior_to(QUIC_RCIDM
*rcidm
,
432 uint64_t retire_prior_to
)
436 if (retire_prior_to
<= rcidm
->retire_prior_to
)
440 * Retire the current RCID (if any) if it is affected.
442 if (rcidm
->cur_rcid
!= NULL
&& rcidm
->cur_rcid
->seq_num
< retire_prior_to
)
443 rcidm_transition_rcid(rcidm
, rcidm
->cur_rcid
, RCID_STATE_RETIRING
);
446 * Any other RCIDs needing retirement will be at the start of the priority
447 * queue, so just stop once we see a higher sequence number exceeding the
450 while ((rcid
= ossl_pqueue_RCID_peek(rcidm
->rcids
)) != NULL
451 && rcid
->seq_num
< retire_prior_to
)
452 rcidm_transition_rcid(rcidm
, rcid
, RCID_STATE_RETIRING
);
454 rcidm
->retire_prior_to
= retire_prior_to
;
462 static void rcidm_roll(QUIC_RCIDM
*rcidm
)
466 if ((rcid
= ossl_pqueue_RCID_peek(rcidm
->rcids
)) == NULL
)
469 rcidm_transition_rcid(rcidm
, rcid
, RCID_STATE_CUR
);
471 ++rcidm
->num_changes
;
472 rcidm
->roll_requested
= 0;
474 if (rcidm
->packets_sent
>= PACKETS_PER_RCID
)
475 rcidm
->packets_sent
%= PACKETS_PER_RCID
;
477 rcidm
->packets_sent
= 0;
480 static void rcidm_update(QUIC_RCIDM
*rcidm
)
485 * If we have no current numbered RCID but have one or more pending, use it.
487 if (rcidm
->cur_rcid
== NULL
488 && (rcid
= ossl_pqueue_RCID_peek(rcidm
->rcids
)) != NULL
) {
489 rcidm_transition_rcid(rcidm
, rcid
, RCID_STATE_CUR
);
490 assert(rcidm
->cur_rcid
!= NULL
);
493 /* Prefer use of any current numbered RCID we have, if possible. */
494 if (rcidm
->cur_rcid
!= NULL
) {
495 rcidm_check_rcid(rcidm
, rcidm
->cur_rcid
);
496 rcidm_set_preferred_rcid(rcidm
, &rcidm
->cur_rcid
->cid
);
501 * If there are no RCIDs from NCID frames we can use, go through the various
502 * kinds of bootstrapping RCIDs we can use in order of priority.
504 if (rcidm
->added_retry_odcid
) {
505 rcidm_set_preferred_rcid(rcidm
, &rcidm
->retry_odcid
);
509 if (rcidm
->added_initial_odcid
&& !rcidm
->handshake_complete
) {
510 rcidm_set_preferred_rcid(rcidm
, &rcidm
->initial_odcid
);
514 /* We don't know of any usable RCIDs */
515 rcidm_set_preferred_rcid(rcidm
, NULL
);
518 static int rcidm_should_roll(QUIC_RCIDM
*rcidm
)
521 * Always switch as soon as possible if handshake completes;
522 * and every n packets after handshake completes or the last roll; and
523 * whenever manually requested.
525 return rcidm
->handshake_complete
526 && (rcidm
->num_changes
== 0
527 || rcidm
->packets_sent
>= PACKETS_PER_RCID
528 || rcidm
->roll_requested
);
531 static void rcidm_tick(QUIC_RCIDM
*rcidm
)
533 if (rcidm_should_roll(rcidm
))
543 void ossl_quic_rcidm_on_handshake_complete(QUIC_RCIDM
*rcidm
)
545 if (rcidm
->handshake_complete
)
548 rcidm
->handshake_complete
= 1;
552 void ossl_quic_rcidm_on_packet_sent(QUIC_RCIDM
*rcidm
, uint64_t num_packets
)
554 if (num_packets
== 0)
557 rcidm
->packets_sent
+= num_packets
;
561 void ossl_quic_rcidm_request_roll(QUIC_RCIDM
*rcidm
)
563 rcidm
->roll_requested
= 1;
568 * Mutation Operations
569 * ===================
571 int ossl_quic_rcidm_add_from_initial(QUIC_RCIDM
*rcidm
,
572 const QUIC_CONN_ID
*rcid
)
576 if (rcidm
->added_initial_rcid
|| rcidm
->handshake_complete
)
579 rcid_obj
= rcidm_create_rcid(rcidm
, INITIAL_SEQ_NUM
,
580 rcid
, RCID_TYPE_INITIAL
);
581 if (rcid_obj
== NULL
)
584 rcidm
->added_initial_rcid
= 1;
589 int ossl_quic_rcidm_add_from_server_retry(QUIC_RCIDM
*rcidm
,
590 const QUIC_CONN_ID
*retry_odcid
)
592 if (rcidm
->added_retry_odcid
|| rcidm
->handshake_complete
)
595 rcidm
->retry_odcid
= *retry_odcid
;
596 rcidm
->added_retry_odcid
= 1;
601 int ossl_quic_rcidm_add_from_ncid(QUIC_RCIDM
*rcidm
,
602 const OSSL_QUIC_FRAME_NEW_CONN_ID
*ncid
)
606 rcid
= rcidm_create_rcid(rcidm
, ncid
->seq_num
, &ncid
->conn_id
, RCID_TYPE_NCID
);
610 rcidm_handle_retire_prior_to(rcidm
, ncid
->retire_prior_to
);
620 static int rcidm_get_retire(QUIC_RCIDM
*rcidm
, uint64_t *seq_num
, int peek
)
622 RCID
*rcid
= ossl_list_retiring_head(&rcidm
->retiring_list
);
628 *seq_num
= rcid
->seq_num
;
631 rcidm_free_rcid(rcidm
, rcid
);
636 int ossl_quic_rcidm_pop_retire_seq_num(QUIC_RCIDM
*rcidm
,
639 return rcidm_get_retire(rcidm
, seq_num
, /*peek=*/0);
642 int ossl_quic_rcidm_peek_retire_seq_num(QUIC_RCIDM
*rcidm
,
645 return rcidm_get_retire(rcidm
, seq_num
, /*peek=*/1);
648 int ossl_quic_rcidm_get_preferred_tx_dcid(QUIC_RCIDM
*rcidm
,
649 QUIC_CONN_ID
*tx_dcid
)
651 if (!rcidm
->have_preferred_rcid
)
654 *tx_dcid
= rcidm
->preferred_rcid
;
658 int ossl_quic_rcidm_get_preferred_tx_dcid_changed(QUIC_RCIDM
*rcidm
,
661 int r
= rcidm
->preferred_rcid_changed
;
664 rcidm
->preferred_rcid_changed
= 0;
669 size_t ossl_quic_rcidm_get_num_active(const QUIC_RCIDM
*rcidm
)
671 return ossl_pqueue_RCID_num(rcidm
->rcids
)
672 + (rcidm
->cur_rcid
!= NULL
? 1 : 0)
673 + ossl_quic_rcidm_get_num_retiring(rcidm
);
676 size_t ossl_quic_rcidm_get_num_retiring(const QUIC_RCIDM
*rcidm
)
678 return rcidm
->num_retiring
;