diff options
author | Hugo Landau <hlandau@openssl.org> | 2023-03-01 17:52:40 +0100 |
---|---|---|
committer | Matt Caswell <matt@openssl.org> | 2023-05-01 12:03:54 +0200 |
commit | 90699176b07469e0b6b688ed88bc3f1deb5ccc26 (patch) | |
tree | 44087a2c077d8b1193686b2eb43847fc7460bb63 | |
parent | Update the corpora (diff) | |
download | openssl-90699176b07469e0b6b688ed88bc3f1deb5ccc26.tar.xz openssl-90699176b07469e0b6b688ed88bc3f1deb5ccc26.zip |
QUIC CC: Major revisions to CC abstract interface
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20423)
-rw-r--r-- | doc/designs/quic-design/congestion-control.md | 293 | ||||
-rw-r--r-- | include/internal/quic_cc.h | 230 | ||||
-rw-r--r-- | ssl/quic/cc_dummy.c | 100 | ||||
-rw-r--r-- | ssl/quic/quic_ackm.c | 70 | ||||
-rw-r--r-- | ssl/quic/quic_channel.c | 4 | ||||
-rw-r--r-- | ssl/quic/quic_txp.c | 17 | ||||
-rw-r--r-- | test/quic_ackm_test.c | 2 | ||||
-rw-r--r-- | test/quic_fifd_test.c | 2 | ||||
-rw-r--r-- | test/quic_txp_test.c | 2 |
9 files changed, 283 insertions, 437 deletions
diff --git a/doc/designs/quic-design/congestion-control.md b/doc/designs/quic-design/congestion-control.md index d5bb4a1d79..d2c9323695 100644 --- a/doc/designs/quic-design/congestion-control.md +++ b/doc/designs/quic-design/congestion-control.md @@ -1,249 +1,50 @@ Congestion control API design ============================= -This is mostly inspired by the MSQUIC congestion control API -as it was the most isolated one among the libraries. - -The API is designed in a way to be later easily transformed into a -a fetchable implementation API. - -The API is centered around two structures - the `OSSL_CC_METHOD` -structure holding the API calls into the congestion control module -and `OSSL_CC_DATA` opaque type that holds the data of needed -for the operation of the module. - -Most of the information when the functions of the API are -supposed to be called and how the API is designed to operate -should be clear from the comments for the individual -functions in [Appendix A](#appendix-a). - -Changeables ------------ - -As some parameters that the congestion control algorithm needs might -be updated during the lifetime of the connection these parameters -are called changeables. - -The CC implementation will save the pointers to these parameters' -data and will use the current value of the data at the pointer when -it needs to recalculate the allowance and whether the sending is -blocked or not. - -Some examples of changeables - - - `payload_length` - the maximum length of payload that can be sent - in a single UDP packet (not counting UDP and IP headers). - - `smoothed_rtt` - the current round trip time of the connection - as computed from the acked packets. - - `rtt_variance` - the variance of the `smoothed_rtt` value. - -Thread handling ---------------- - -The `OSSL_CC_DATA` is created with the `new` function per each -connection. The expectation is that there is only a single thread -accessing the `ccdata` which should not be a limitation as the calls -into the module will be done from the packetizer layer of the QUIC -connection handling and there the eventual writes from multiple threads -handling individual streams of the connection have to be synchronized -already to create the packets. - -Congestion event handling -------------------------- - -The congestion state of a connection can change only after some event -happens - i.e., a packet is considered lost meaning the `on_data_lost()` -is called, or an ack arrives for a packet causing calls to -`on_data_acked()` or `on_spurious_congestion_event()` functions. -The congestion control does not produce any timer events by itself. - -Exemptions ----------- - -To facilitate probing and to avoid having to always special-case -probing packets when considering congestion on sending, the -`set_exemption()` function allows setting a number of packets that are -allowed to be sent even when forbidden by the eventual congestion state. - -The exemptions must be used if and only if a packet (or multiple packets) -has to be sent as required by the protocol regardless of the congestion state. - -Paths ------ - -Initially the design expects that only a single path per-connection is -actively sending data. In future when multiple active paths sending data -shall be supported the instances of `OSSL_CC_DATA` would be per-path. - -There might need to be further adjustments needed in that case. However -at least initially this API is intended to be internal to the -OpenSSL library allowing any necessary changes of the API. - -Appendix A ----------- - -Proposed header file with comments explaining the individual -functions. The API is meant to be internal initially so the method -accessors to set the individual functions will be added later once -the API is public. Alternatively this might be also implemented -as fetchable dispatch API. - -```C -/* - * Copyright 2022 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 <openssl/ssl.h> - -typedef struct ossl_cc_method_st OSSL_CC_METHOD; -/* - * An OSSL_CC_DATA is an externally defined opaque pointer holding - * the internal data of the congestion control method - */ -typedef struct ossl_cc_data_st OSSL_CC_DATA; - -/* - * Once this becomes public API then we will need functions to create and - * free an OSSL_CC_METHOD, as well as functions to get/set the various - * function pointers....unless we make it fetchable. - */ -struct ossl_cc_method_st { - /* - * Create a new OSSL_CC_DATA object to handle the congestion control - * calculations. - * - * |settings| are mandatory settings that will cause the - * new() call to fail if they are not understood). - * |options| are optional settings that will not - * cause the new() call to fail if they are not understood. - * |changeables| contain additional parameters that the congestion - * control algorithms need that can be updated during the - * connection lifetime - for example size of the datagram payload. - * To avoid calling a function with OSSL_PARAM array every time - * these parameters are changed the addresses of these param - * values are considered permanent and the values can be updated - * any time. - */ - OSSL_CC_DATA *(*new)(OSSL_PARAM *settings, OSSL_PARAM *options, - OSSL_PARAM *changeables); - - /* - * Release the OSSL_CC_DATA. - */ - void (*free)(OSSL_CC_DATA *ccdata); - - /* - * Reset the congestion control state. - * |flags| to support different level of reset (partial/full). - */ - void (*reset)(OSSL_CC_DATA *ccdata, int flags); - - /* - * Set number of packets exempted from CC - used for probing - * |numpackets| is a small value (2). - * Returns 0 on error, 1 otherwise. - */ - int (*set_exemption)(OSSL_CC_DATA *ccdata, int numpackets); - - /* - * Get current number of packets exempted from CC. - * Returns negative value on error, the number otherwise. - */ - int (*get_exemption)(OSSL_CC_DATA *ccdata); - - /* - * Returns 1 if sending is allowed, 0 otherwise. - */ - int (*can_send)(OSSL_CC_DATA *ccdata); - - /* - * Returns number of bytes allowed to be sent. - * |time_since_last_send| is time since last send operation - * in microseconds. - * |time_valid| is 1 if the |time_since_last_send| holds - * a meaningful value, 0 otherwise. - */ - size_t (*get_send_allowance)(OSSL_CC_DATA *ccdata, - uint64_t time_since_last_send, - int time_valid); - - /* - * Returns the maximum number of bytes allowed to be in flight. - */ - size_t (*get_bytes_in_flight_max)(OSSL_CC_DATA *ccdata); - - /* - * Returns the next time at which the CC will release more budget for - * sending, or ossl_time_infinite(). - */ - OSSL_TIME (*get_next_credit_time)(OSSL_CC_DATA *ccdata); - - /* - * To be called when a packet with retransmittable data was sent. - * |num_retransmittable_bytes| is the number of bytes sent - * in the packet that are retransmittable. - * Returns 1 on success, 0 otherwise. - */ - int (*on_data_sent)(OSSL_CC_DATA *ccdata, - size_t num_retransmittable_bytes); - - /* - * To be called when retransmittable data was invalidated. - * I.E. they are not considered in-flight anymore but - * are neither acknowledged nor lost. In particular used when - * 0RTT data was rejected. - * |num_retransmittable_bytes| is the number of bytes - * of the invalidated data. - * Returns 1 if sending is unblocked (can_send returns 1), 0 - * otherwise. - */ - int (*on_data_invalidated)(OSSL_CC_DATA *ccdata, - size_t num_retransmittable_bytes); - - /* - * To be called when sent data was acked. - * |time_now| is current time in microseconds. - * |largest_pn_acked| is the largest packet number of the acked - * packets. - * |num_retransmittable_bytes| is the number of retransmittable - * packet bytes that were newly acked. - * Returns 1 if sending is unblocked (can_send returns 1), 0 - * otherwise. - */ - int (*on_data_acked)(OSSL_CC_DATA *ccdata, - uint64_t time_now, - uint64_t last_pn_acked, - size_t num_retransmittable_bytes); - - /* - * To be called when sent data is considered lost. - * |largest_pn_lost| is the largest packet number of the lost - * packets. - * |largest_pn_sent| is the largest packet number sent on this - * connection. - * |num_retransmittable_bytes| is the number of retransmittable - * packet bytes that are newly considered lost. - * |persistent_congestion| is 1 if the congestion is considered - * persistent (see RFC 9002 Section 7.6), 0 otherwise. - */ - void (*on_data_lost)(OSSL_CC_DATA *ccdata, - uint64_t largest_pn_lost, - uint64_t largest_pn_sent, - size_t num_retransmittable_bytes, - int persistent_congestion); - - /* - * To be called when all lost data from the previous call to - * on_data_lost() was actually acknowledged. - * This reverts the size of the congestion window to the state - * before the on_data_lost() call. - * Returns 1 if sending is unblocked, 0 otherwise. - */ - int (*on_spurious_congestion_event)(OSSL_CC_DATA *ccdata); -}; -``` +We use an abstract interface for the QUIC congestion controller to facilitate +use of pluggable QUIC congestion controllers in the future. The interface is +based on interfaces suggested by RFC 9002 and MSQUIC's congestion control APIs. + +`OSSL_CC_METHOD` provides a vtable of function pointers to congestion controller +methods. `OSSL_CC_DATA` is an opaque type representing a congestion controller +instance. + +For details on the API, see the comments in `include/internal/quic_cc.h`. + +Congestion controllers are not thread safe; the caller is responsible for +synchronisation. + +Congestion controllers may vary their state with respect to time. This is +faciliated via the `get_wakeup_deadline` method and the `now` argument to the +`new` method, which provides access to a clock. While no current congestion +controller makes use of this facility, it can be used by future congestion +controllers to implement packet pacing. + +Congestion controllers may expose integer configuration options via the +`set_option_uint` and `get_option_uint` methods. These options may be specific +to the congestion controller method, although there are some well known ones +intended to be common to all congestion controllers. The use of strings for +option names is avoided for performance reasons. + +Currently, the only dependency injected to a congestion controller is access to +a clock. In the future it is likely that access at least to the statistics +manager will be provided. Excessive futureproofing of the congestion controller +interface has been avoided as this is currently an internal API for which no API +stability guarantees are required; for example, no currently implemented +congestion control algorithm requires access to the statistics manager, but such +access can readily be added later as needed. + +QUIC congestion control state is per-path, per-connection. Currently we support +only a single path per connection, so there is one congestion control instance +per connection. This may change in future. + +While the congestion control API is roughly based around the arrangement of +functions as described by the congestion control psuedocode in RFC 9002, there +are some deliberate changes in order to obtain cleaner separation between the +loss detection and congestion control functions. Where a literal option of RFC +9002 psuedocode would require a congestion controller to access the ACK +manager's internal state directly, the interface between the two has been +changed to avoid this. This involves some small amounts of functionality which +RFC 9002 considers part of the congestion controller being part of the ACK +manager in our implementation. See the comments in `include/internal/quic_cc.h` +and `ssl/quic/quic_ackm.c` for more information. diff --git a/include/internal/quic_cc.h b/include/internal/quic_cc.h index 8e318e33ff..2ddc86c746 100644 --- a/include/internal/quic_cc.h +++ b/include/internal/quic_cc.h @@ -14,143 +14,181 @@ # ifndef OPENSSL_NO_QUIC -typedef struct ossl_cc_data_st *OSSL_CC_DATA; +typedef struct ossl_cc_data_st OSSL_CC_DATA; -typedef struct ossl_cc_method_st { - void *dummy; +typedef struct ossl_cc_ack_info_st { + /* The time the packet being acknowledged was originally sent. */ + OSSL_TIME tx_time; - /* - * Create a new OSSL_CC_DATA object to handle the congestion control - * calculations. - * - * |settings| are mandatory settings that will cause the - * new() call to fail if they are not understood). - * |options| are optional settings that will not - * cause the new() call to fail if they are not understood. - * |changeables| contain additional parameters that the congestion - * control algorithms need that can be updated during the - * connection lifetime - for example size of the datagram payload. - * To avoid calling a function with OSSL_PARAM array every time - * these parameters are changed the addresses of these param - * values are considered permanent and the values can be updated - * any time. - */ - OSSL_CC_DATA *(*new)(OSSL_PARAM *settings, OSSL_PARAM *options, - OSSL_PARAM *changeables); + /* The size in bytes of the packet being acknowledged. */ + size_t tx_size; +} OSSL_CC_ACK_INFO; + +typedef struct ossl_cc_loss_info_st { + /* The time the packet being lost was originally sent. */ + OSSL_TIME tx_time; + /* The size in bytes of the packet which has been determined lost. */ + size_t tx_size; +} OSSL_CC_LOSS_INFO; + +typedef struct ossl_cc_ecn_info_st { /* - * Release the OSSL_CC_DATA. + * The time at which the largest acked PN (in the incoming ACK frame) was + * sent. */ - void (*free)(OSSL_CC_DATA *ccdata); + OSSL_TIME largest_acked_time; +} OSSL_CC_ECN_INFO; + +/* Parameter (read-write): Maximum datagram payload length in bytes. */ +#define OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN 1 + +/* Diagnostic (read-only): current congestion window size in bytes. */ +#define OSSL_CC_OPTION_CUR_CWND_SIZE 2 +/* Diagnostic (read-only): minimum congestion window size in bytes. */ +#define OSSL_CC_OPTION_MIN_CWND_SIZE 3 + +/* Diagnostic (read-only): current net bytes in flight. */ +#define OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT 4 + +/* + * Congestion control abstract interface. + * + * This interface is broadly based on the design described in RFC 9002. However, + * the demarcation between the ACKM and the congestion controller does not + * exactly match that delineated in the RFC 9002 psuedocode. Where aspects of + * the demarcation involve the congestion controller accessing internal state of + * the ACKM, the interface has been revised where possible to provide the + * information needed by the congestion controller and avoid needing to give the + * congestion controller access to the ACKM's internal data structures. + * + * Particular changes include: + * + * - In our implementation, it is the responsibility of the ACKM to determine + * if a loss event constitutes persistent congestion. + * + * - In our implementation, it is the responsibility of the ACKM to determine + * if the ECN-CE counter has increased. The congestion controller is simply + * informed when an ECN-CE event occurs. + * + * All of these changes are intended to avoid having a congestion controller + * have to access ACKM internal state. + * + */ +#define OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION (1U << 0) + +typedef struct ossl_cc_method_st { /* - * Reset the congestion control state. - * |flags| to support different level of reset (partial/full). + * Instantiation. */ - void (*reset)(OSSL_CC_DATA *ccdata, int flags); + OSSL_CC_DATA *(*new)(OSSL_TIME (*now_cb)(void *arg), + void *now_cb_arg); + + void (*free)(OSSL_CC_DATA *ccdata); /* - * Set number of packets exempted from CC - used for probing - * |numpackets| is a small value (2). - * Returns 0 on error, 1 otherwise. + * Reset of state. */ - int (*set_exemption)(OSSL_CC_DATA *ccdata, int numpackets); + void (*reset)(OSSL_CC_DATA *ccdata); /* - * Get current number of packets exempted from CC. - * Returns negative value on error, the number otherwise. + * Escape hatch for option configuration. + * + * option_id: One of OSSL_CC_OPTION_*. + * + * value: The option value to set. + * + * Returns 1 on success and 0 on failure. */ - int (*get_exemption)(OSSL_CC_DATA *ccdata); + int (*set_option_uint)(OSSL_CC_DATA *ccdata, + uint32_t option_id, + uint64_t value); /* - * Returns 1 if sending is allowed, 0 otherwise. + * On success, returns 1 and writes the current value of the given option to + * *value. Otherwise, returns 0. */ - int (*can_send)(OSSL_CC_DATA *ccdata); + int (*get_option_uint)(OSSL_CC_DATA *ccdata, + uint32_t option_id, + uint64_t *value); /* - * Returns number of bytes allowed to be sent. - * |time_since_last_send| is time since last send operation - * in microseconds. - * |time_valid| is 1 if the |time_since_last_send| holds - * a meaningful value, 0 otherwise. + * Returns the amount of additional data (above and beyond the data + * currently in flight) which can be sent in bytes. Returns 0 if no more + * data can be sent at this time. The return value of this method + * can vary as time passes. */ - uint64_t (*get_send_allowance)(OSSL_CC_DATA *ccdata, - OSSL_TIME time_since_last_send, - int time_valid); + uint64_t (*get_tx_allowance)(OSSL_CC_DATA *ccdata); /* - * Returns the maximum number of bytes allowed to be in flight. + * Returns the time at which the return value of get_tx_allowance might be + * higher than its current value. This is not a guarantee and spurious + * wakeups are allowed. Returns ossl_time_infinite() if there is no current + * wakeup deadline. */ - uint64_t (*get_bytes_in_flight_max)(OSSL_CC_DATA *ccdata); + OSSL_TIME (*get_wakeup_deadline)(OSSL_CC_DATA *ccdata); /* - * Returns the next time at which the CC will release more budget for - * sending, or ossl_time_infinite(). + * The On Data Sent event. num_bytes should be the size of the packet in + * bytes (or the aggregate size of multiple packets which have just been + * sent). */ - OSSL_TIME (*get_next_credit_time)(OSSL_CC_DATA *ccdata); + int (*on_data_sent)(OSSL_CC_DATA *ccdata, + uint64_t num_bytes); /* - * To be called when a packet with retransmittable data was sent. - * |num_retransmittable_bytes| is the number of bytes sent - * in the packet that are retransmittable. - * Returns 1 on success, 0 otherwise. + * The On Data Acked event. See OSSL_CC_ACK_INFO structure for details + * of the information to be passed. */ - int (*on_data_sent)(OSSL_CC_DATA *ccdata, - uint64_t num_retransmittable_bytes); + int (*on_data_acked)(OSSL_CC_DATA *ccdata, + const OSSL_CC_ACK_INFO *info); /* - * To be called when retransmittable data was invalidated. - * I.E. they are not considered in-flight anymore but - * are neither acknowledged nor lost. In particular used when - * 0RTT data was rejected. - * |num_retransmittable_bytes| is the number of bytes - * of the invalidated data. - * Returns 1 if sending is unblocked (can_send returns 1), 0 - * otherwise. + * The On Data Lost event. See OSSL_CC_LOSS_INFO structure for details + * of the information to be passed. + * + * Note: When the ACKM determines that a set of multiple packets has been + * lost, it is useful for a congestion control algorithm to be able to + * process this as a single loss event rather than multiple loss events. + * Thus, calling this function may cause the congestion controller to defer + * state updates under the assumption that subsequent calls to + * on_data_lost() representing further lost packets in the same loss event + * may be forthcoming. Always call on_data_lost_finished() after one or more + * calls to on_data_lost(). */ - int (*on_data_invalidated)(OSSL_CC_DATA *ccdata, - uint64_t num_retransmittable_bytes); + int (*on_data_lost)(OSSL_CC_DATA *ccdata, + const OSSL_CC_LOSS_INFO *info); /* - * To be called when sent data was acked. - * |time_now| is current time in microseconds. - * |largest_pn_acked| is the largest packet number of the acked - * packets. - * |num_retransmittable_bytes| is the number of retransmittable - * packet bytes that were newly acked. - * Returns 1 if sending is unblocked (can_send returns 1), 0 - * otherwise. + * To be called after a sequence of one or more on_data_lost() calls + * representing multiple packets in a single loss detection incident. + * + * Flags may be 0 or OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION. */ - int (*on_data_acked)(OSSL_CC_DATA *ccdata, - OSSL_TIME time_now, - uint64_t last_pn_acked, - uint64_t num_retransmittable_bytes); + int (*on_data_lost_finished)(OSSL_CC_DATA *ccdata, uint32_t flags); /* - * To be called when sent data is considered lost. - * |largest_pn_lost| is the largest packet number of the lost - * packets. - * |largest_pn_sent| is the largest packet number sent on this - * connection. - * |num_retransmittable_bytes| is the number of retransmittable - * packet bytes that are newly considered lost. - * |persistent_congestion| is 1 if the congestion is considered - * persistent (see RFC 9002 Section 7.6), 0 otherwise. + * For use when a PN space is invalidated or a packet must otherwise be + * 'undone' for congestion control purposes without acting as a loss signal. + * Only the size of the packet is needed. */ - void (*on_data_lost)(OSSL_CC_DATA *ccdata, - uint64_t largest_pn_lost, - uint64_t largest_pn_sent, - uint64_t num_retransmittable_bytes, - int persistent_congestion); + int (*on_data_invalidated)(OSSL_CC_DATA *ccdata, + uint64_t num_bytes); /* - * To be called when all lost data from the previous call to - * on_data_lost() was actually acknowledged. - * This reverts the size of the congestion window to the state - * before the on_data_lost() call. - * Returns 1 if sending is unblocked, 0 otherwise. + * Called from the ACKM when detecting an increased ECN-CE value in an ACK + * frame. This indicates congestion. + * + * Note that this differs from the RFC's conceptual segregation of the loss + * detection and congestion controller functions, as in our implementation + * the ACKM is responsible for detecting increases to ECN-CE and simply + * tells the congestion controller when ECN-triggered congestion has + * occurred. This allows a slightly more efficient implementation and + * narrower interface between the ACKM and CC. */ - int (*on_spurious_congestion_event)(OSSL_CC_DATA *ccdata); + int (*on_ecn)(OSSL_CC_DATA *ccdata, + const OSSL_CC_ECN_INFO *info); } OSSL_CC_METHOD; extern const OSSL_CC_METHOD ossl_cc_dummy_method; diff --git a/ssl/quic/cc_dummy.c b/ssl/quic/cc_dummy.c index 195c4324bf..af0d548379 100644 --- a/ssl/quic/cc_dummy.c +++ b/ssl/quic/cc_dummy.c @@ -8,15 +8,22 @@ */ #include "internal/quic_cc.h" +#include "internal/quic_types.h" typedef struct ossl_cc_dummy_st { - char dummy; + size_t max_dgram_len; } OSSL_CC_DUMMY; -static OSSL_CC_DATA *dummy_new(OSSL_PARAM *settings, OSSL_PARAM *options, - OSSL_PARAM *changeables) +static OSSL_CC_DATA *dummy_new(OSSL_TIME (*now_cb)(void *arg), + void *now_cb_arg) { - return OPENSSL_zalloc(sizeof(OSSL_CC_DUMMY)); + OSSL_CC_DUMMY *d = OPENSSL_zalloc(sizeof(*d)); + + if (d == NULL) + return NULL; + + d->max_dgram_len = QUIC_MIN_INITIAL_DGRAM_LEN; + return (OSSL_CC_DATA *)d; } static void dummy_free(OSSL_CC_DATA *cc) @@ -24,90 +31,97 @@ static void dummy_free(OSSL_CC_DATA *cc) OPENSSL_free(cc); } -static void dummy_reset(OSSL_CC_DATA *cc, int flags) +static void dummy_reset(OSSL_CC_DATA *cc) { } -static int dummy_set_exemption(OSSL_CC_DATA *cc, int numpackets) +static int dummy_set_option_uint(OSSL_CC_DATA *cc, + uint32_t option_id, + uint64_t value) { - return 1; -} + OSSL_CC_DUMMY *d = (OSSL_CC_DUMMY *)cc; -static int dummy_get_exemption(OSSL_CC_DATA *cc) -{ - return 0; -} + switch (option_id) { + case OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN: + if (value > SIZE_MAX) + return 0; -static int dummy_can_send(OSSL_CC_DATA *cc) -{ - return 1; + d->max_dgram_len = (size_t)value; + return 1; + + default: + return 0; + } } -static uint64_t dummy_get_send_allowance(OSSL_CC_DATA *cc, - OSSL_TIME time_since_last_send, - int time_valid) +static int dummy_get_option_uint(OSSL_CC_DATA *cc, + uint32_t option_id, + uint64_t *value) { - return SIZE_MAX; + OSSL_CC_DUMMY *d = (OSSL_CC_DUMMY *)cc; + + switch (option_id) { + case OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN: + *value = (uint64_t)d->max_dgram_len; + return 1; + + default: + return 0; + } } -static uint64_t dummy_get_bytes_in_flight_max(OSSL_CC_DATA *cc) +static uint64_t dummy_get_tx_allowance(OSSL_CC_DATA *cc) { return SIZE_MAX; } -static OSSL_TIME dummy_get_next_credit_time(OSSL_CC_DATA *cc_data) +static OSSL_TIME dummy_get_wakeup_deadline(OSSL_CC_DATA *cc) { return ossl_time_infinite(); } static int dummy_on_data_sent(OSSL_CC_DATA *cc, - uint64_t num_retransmittable_bytes) + uint64_t num_bytes) { return 1; } -static int dummy_on_data_invalidated(OSSL_CC_DATA *cc, - uint64_t num_retransmittable_bytes) +static int dummy_on_data_acked(OSSL_CC_DATA *cc, + const OSSL_CC_ACK_INFO *info) { return 1; } -static int dummy_on_data_acked(OSSL_CC_DATA *cc, OSSL_TIME time_now, - uint64_t last_pn_acked, - uint64_t num_retransmittable_bytes) +static int dummy_on_data_lost(OSSL_CC_DATA *cc, + const OSSL_CC_LOSS_INFO *info) { return 1; } -static void dummy_on_data_lost(OSSL_CC_DATA *cc, - uint64_t largest_pn_lost, - uint64_t largest_pn_sent, - uint64_t num_retransmittable_bytes, - int persistent_congestion) +static int dummy_on_data_lost_finished(OSSL_CC_DATA *cc, + uint32_t flags) { - + return 1; } -static int dummy_on_spurious_congestion_event(OSSL_CC_DATA *cc) +static int dummy_on_data_invalidated(OSSL_CC_DATA *cc, + uint64_t num_bytes) { return 1; } const OSSL_CC_METHOD ossl_cc_dummy_method = { - NULL, dummy_new, dummy_free, dummy_reset, - dummy_set_exemption, - dummy_get_exemption, - dummy_can_send, - dummy_get_send_allowance, - dummy_get_bytes_in_flight_max, - dummy_get_next_credit_time, + dummy_set_option_uint, + dummy_get_option_uint, + dummy_get_tx_allowance, + dummy_get_wakeup_deadline, dummy_on_data_sent, - dummy_on_data_invalidated, dummy_on_data_acked, dummy_on_data_lost, - dummy_on_spurious_congestion_event, + dummy_on_data_lost_finished, + dummy_on_data_invalidated, }; diff --git a/ssl/quic/quic_ackm.c b/ssl/quic/quic_ackm.c index 6061fb29f5..b798636571 100644 --- a/ssl/quic/quic_ackm.c +++ b/ssl/quic/quic_ackm.c @@ -911,7 +911,7 @@ static int ackm_set_loss_detection_timer(OSSL_ACKM *ackm) static int ackm_in_persistent_congestion(OSSL_ACKM *ackm, const OSSL_ACKM_TX_PKT *lpkt) { - /* Persistent congestion not currently implemented. */ + /* TODO(QUIC): Persistent congestion not currently implemented. */ return 0; } @@ -922,6 +922,8 @@ static void ackm_on_pkts_lost(OSSL_ACKM *ackm, int pkt_space, OSSL_RTT_INFO rtt; QUIC_PN largest_pn_lost = 0; uint64_t num_bytes = 0; + OSSL_CC_LOSS_INFO loss_info = {0}; + uint32_t flags = 0; for (p = lpkt; p != NULL; p = pnext) { pnext = p->lnext; @@ -938,41 +940,38 @@ static void ackm_on_pkts_lost(OSSL_ACKM *ackm, int pkt_space, num_bytes += p->num_bytes; } + if (!pseudo) { + /* + * If this is pseudo-loss (e.g. during connection retry) we do not + * inform the CC as it is not a real loss and not reflective of + * network conditions. + */ + loss_info.tx_time = p->time; + loss_info.tx_size = p->num_bytes; + + ackm->cc_method->on_data_lost(ackm->cc_data, &loss_info); + } + p->on_lost(p->cb_arg); } - if (pseudo) - /* - * If this is pseudo-loss (e.g. during connection retry) we do not - * inform the CC as it is not a real loss and not reflective of network - * conditions. - */ - return; - /* - * Only consider lost packets with regards to congestion after getting an - * RTT sample. + * Persistent congestion can only be considered if we have gotten at least + * one RTT sample. */ ossl_statm_get_rtt_info(ackm->statm, &rtt); + if (!ossl_time_is_zero(ackm->first_rtt_sample) + && ackm_in_persistent_congestion(ackm, lpkt)) + flags |= OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION; - if (ossl_time_is_zero(ackm->first_rtt_sample)) - return; - - ackm->cc_method->on_data_lost(ackm->cc_data, - largest_pn_lost, - ackm->tx_history[pkt_space].highest_sent, - num_bytes, - ackm_in_persistent_congestion(ackm, lpkt)); + ackm->cc_method->on_data_lost_finished(ackm->cc_data, flags); } static void ackm_on_pkts_acked(OSSL_ACKM *ackm, const OSSL_ACKM_TX_PKT *apkt) { const OSSL_ACKM_TX_PKT *anext; - OSSL_TIME now; - uint64_t num_retransmittable_bytes = 0; QUIC_PN last_pn_acked = 0; - - now = ackm->now(ackm->now_arg); + OSSL_CC_ACK_INFO ainfo = {0}; for (; apkt != NULL; apkt = anext) { if (apkt->is_inflight) { @@ -981,7 +980,6 @@ static void ackm_on_pkts_acked(OSSL_ACKM *ackm, const OSSL_ACKM_TX_PKT *apkt) ackm->ack_eliciting_bytes_in_flight[apkt->pkt_space] -= apkt->num_bytes; - num_retransmittable_bytes += apkt->num_bytes; if (apkt->pkt_num > last_pn_acked) last_pn_acked = apkt->pkt_num; @@ -997,10 +995,11 @@ static void ackm_on_pkts_acked(OSSL_ACKM *ackm, const OSSL_ACKM_TX_PKT *apkt) anext = apkt->anext; apkt->on_acked(apkt->cb_arg); /* may free apkt */ - } - ackm->cc_method->on_data_acked(ackm->cc_data, now, - last_pn_acked, num_retransmittable_bytes); + ainfo.tx_time = apkt->time; + ainfo.tx_size = apkt->num_bytes; + ackm->cc_method->on_data_acked(ackm->cc_data, &ainfo); + } } OSSL_ACKM *ossl_ackm_new(OSSL_TIME (*now)(void *arg), @@ -1096,16 +1095,12 @@ int ossl_ackm_on_rx_datagram(OSSL_ACKM *ackm, size_t num_bytes) return 1; } -static void ackm_on_congestion(OSSL_ACKM *ackm, OSSL_TIME send_time) -{ - /* Not currently implemented. */ -} - static void ackm_process_ecn(OSSL_ACKM *ackm, const OSSL_QUIC_FRAME_ACK *ack, int pkt_space) { struct tx_pkt_history_st *h; OSSL_ACKM_TX_PKT *pkt; + OSSL_CC_ECN_INFO ecn_info = {0}; /* * If the ECN-CE counter reported by the peer has increased, this could @@ -1119,7 +1114,8 @@ static void ackm_process_ecn(OSSL_ACKM *ackm, const OSSL_QUIC_FRAME_ACK *ack, if (pkt == NULL) return; - ackm_on_congestion(ackm, pkt->time); + ecn_info.largest_acked_time = pkt->time; + ackm->cc_method->on_ecn(ackm->cc_data, &ecn_info); } } @@ -1182,7 +1178,13 @@ int ossl_ackm_on_rx_ack_frame(OSSL_ACKM *ackm, const OSSL_QUIC_FRAME_ACK *ack, ossl_time_subtract(now, na_pkts->time)); } - /* Process ECN information if present. */ + /* + * Process ECN information if present. + * + * We deliberately do most ECN processing in the ACKM rather than the + * congestion controller to avoid having to give the congestion controller + * access to ACKM internal state. + */ if (ack->ecn_present) ackm_process_ecn(ackm, ack, pkt_space); diff --git a/ssl/quic/quic_channel.c b/ssl/quic/quic_channel.c index 460a7d77db..349e2f1055 100644 --- a/ssl/quic/quic_channel.c +++ b/ssl/quic/quic_channel.c @@ -150,7 +150,7 @@ static int ch_init(QUIC_CHANNEL *ch) ch->have_statm = 1; ch->cc_method = &ossl_cc_dummy_method; - if ((ch->cc_data = ch->cc_method->new(NULL, NULL, NULL)) == NULL) + if ((ch->cc_data = ch->cc_method->new(get_time, NULL)) == NULL) goto err; if ((ch->ackm = ossl_ackm_new(get_time, ch, &ch->statm, @@ -1687,7 +1687,7 @@ static OSSL_TIME ch_determine_next_tick_deadline(QUIC_CHANNEL *ch) if (ossl_quic_tx_packetiser_has_pending(ch->txp, TX_PACKETISER_ARCHETYPE_NORMAL, TX_PACKETISER_BYPASS_CC)) deadline = ossl_time_min(deadline, - ch->cc_method->get_next_credit_time(ch->cc_data)); + ch->cc_method->get_wakeup_deadline(ch->cc_data)); /* Is the terminating timer armed? */ if (ossl_quic_channel_is_terminating(ch)) diff --git a/ssl/quic/quic_txp.c b/ssl/quic/quic_txp.c index 2d49e64f7f..786d25ecf6 100644 --- a/ssl/quic/quic_txp.c +++ b/ssl/quic/quic_txp.c @@ -483,7 +483,8 @@ int ossl_quic_tx_packetiser_has_pending(OSSL_QUIC_TX_PACKETISER *txp, int cc_can_send; cc_can_send - = (bypass_cc || txp->args.cc_method->can_send(txp->args.cc_data)); + = (bypass_cc + || txp->args.cc_method->get_tx_allowance(txp->args.cc_data) > 0); for (enc_level = QUIC_ENC_LEVEL_INITIAL; enc_level < QUIC_ENC_LEVEL_NUM; @@ -512,7 +513,7 @@ int ossl_quic_tx_packetiser_generate(OSSL_QUIC_TX_PACKETISER *txp, /* * If CC says we cannot send we still may be able to send any queued probes. */ - cc_can_send = txp->args.cc_method->can_send(txp->args.cc_data); + cc_can_send = (txp->args.cc_method->get_tx_allowance(txp->args.cc_data) > 0); for (enc_level = QUIC_ENC_LEVEL_INITIAL; enc_level < QUIC_ENC_LEVEL_NUM; @@ -900,15 +901,8 @@ static int txp_generate_for_el(OSSL_QUIC_TX_PACKETISER *txp, uint32_t enc_level, size_t mdpl, hdr_len, pkt_overhead, cc_limit; uint64_t cc_limit_; QUIC_PKT_HDR phdr; - OSSL_TIME time_since_last; /* Determine the limit CC imposes on what we can send. */ - if (ossl_time_is_zero(txp->last_tx_time)) - time_since_last = ossl_time_zero(); - else - time_since_last = ossl_time_subtract(txp->args.now(txp->args.now_arg), - txp->last_tx_time); - if (!cc_can_send) { /* * If we are called when we cannot send, this must be because we want @@ -917,10 +911,7 @@ static int txp_generate_for_el(OSSL_QUIC_TX_PACKETISER *txp, uint32_t enc_level, cc_limit = SIZE_MAX; } else { /* Allow CC to clamp how much we can send. */ - cc_limit_ = txp->args.cc_method->get_send_allowance(txp->args.cc_data, - time_since_last, - ossl_time_is_zero(time_since_last)); - + cc_limit_ = txp->args.cc_method->get_tx_allowance(txp->args.cc_data); cc_limit = (cc_limit_ > SIZE_MAX ? SIZE_MAX : (size_t)cc_limit_); } diff --git a/test/quic_ackm_test.c b/test/quic_ackm_test.c index a743ecb5ee..b92e153d1a 100644 --- a/test/quic_ackm_test.c +++ b/test/quic_ackm_test.c @@ -98,7 +98,7 @@ static int helper_init(struct helper *h, size_t num_pkts) h->have_statm = 1; /* Initialise congestion controller. */ - h->ccdata = ossl_cc_dummy_method.new(NULL, NULL, NULL); + h->ccdata = ossl_cc_dummy_method.new(fake_now, NULL); if (!TEST_ptr(h->ccdata)) goto err; diff --git a/test/quic_fifd_test.c b/test/quic_fifd_test.c index c86ee83860..d3d13bf53f 100644 --- a/test/quic_fifd_test.c +++ b/test/quic_fifd_test.c @@ -318,7 +318,7 @@ static int test_fifd(int idx) cb_fail = 0; if (!TEST_true(ossl_statm_init(&info.statm)) - || !TEST_ptr(info.ccdata = ossl_cc_dummy_method.new(NULL, NULL, NULL)) + || !TEST_ptr(info.ccdata = ossl_cc_dummy_method.new(fake_now, NULL)) || !TEST_ptr(info.ackm = ossl_ackm_new(fake_now, NULL, &info.statm, &ossl_cc_dummy_method, diff --git a/test/quic_txp_test.c b/test/quic_txp_test.c index f06002f26a..265c23fabc 100644 --- a/test/quic_txp_test.c +++ b/test/quic_txp_test.c @@ -153,7 +153,7 @@ static int helper_init(struct helper *h) h->have_statm = 1; h->cc_method = &ossl_cc_dummy_method; - if (!TEST_ptr(h->cc_data = h->cc_method->new(NULL, NULL, NULL))) + if (!TEST_ptr(h->cc_data = h->cc_method->new(fake_now, NULL))) goto err; if (!TEST_ptr(h->args.ackm = ossl_ackm_new(fake_now, NULL, |