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)
This commit is contained in:
Hugo Landau 2023-03-01 16:52:40 +00:00 committed by Matt Caswell
parent 91d39be797
commit 90699176b0
9 changed files with 293 additions and 447 deletions

View File

@ -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.
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.
The API is designed in a way to be later easily transformed into a
a fetchable implementation API.
`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.
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.
For details on the API, see the comments in `include/internal/quic_cc.h`.
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).
Congestion controllers are not thread safe; the caller is responsible for
synchronisation.
Changeables
-----------
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.
As some parameters that the congestion control algorithm needs might
be updated during the lifetime of the connection these parameters
are called changeables.
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.
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.
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.
Some examples of changeables
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.
- `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);
};
```
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.

View File

@ -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_ack_info_st {
/* The time the packet being acknowledged was originally sent. */
OSSL_TIME tx_time;
/* 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 {
/*
* The time at which the largest acked PN (in the incoming ACK frame) was
* sent.
*/
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 {
void *dummy;
/*
* 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.
* Instantiation.
*/
OSSL_CC_DATA *(*new)(OSSL_PARAM *settings, OSSL_PARAM *options,
OSSL_PARAM *changeables);
OSSL_CC_DATA *(*new)(OSSL_TIME (*now_cb)(void *arg),
void *now_cb_arg);
/*
* 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).
* Reset of state.
*/
void (*reset)(OSSL_CC_DATA *ccdata, int flags);
void (*reset)(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.
* 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 (*set_exemption)(OSSL_CC_DATA *ccdata, int numpackets);
int (*set_option_uint)(OSSL_CC_DATA *ccdata,
uint32_t option_id,
uint64_t value);
/*
* Get current number of packets exempted from CC.
* Returns negative value on error, the number otherwise.
* On success, returns 1 and writes the current value of the given option to
* *value. Otherwise, returns 0.
*/
int (*get_exemption)(OSSL_CC_DATA *ccdata);
int (*get_option_uint)(OSSL_CC_DATA *ccdata,
uint32_t option_id,
uint64_t *value);
/*
* Returns 1 if sending is allowed, 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.
*/
int (*can_send)(OSSL_CC_DATA *ccdata);
uint64_t (*get_tx_allowance)(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.
* 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_send_allowance)(OSSL_CC_DATA *ccdata,
OSSL_TIME time_since_last_send,
int time_valid);
OSSL_TIME (*get_wakeup_deadline)(OSSL_CC_DATA *ccdata);
/*
* Returns the maximum number of bytes allowed to be in flight.
*/
uint64_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.
* 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).
*/
int (*on_data_sent)(OSSL_CC_DATA *ccdata,
uint64_t num_retransmittable_bytes);
uint64_t num_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,
uint64_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.
* The On Data Acked event. See OSSL_CC_ACK_INFO structure for details
* of the information to be passed.
*/
int (*on_data_acked)(OSSL_CC_DATA *ccdata,
OSSL_TIME time_now,
uint64_t last_pn_acked,
uint64_t num_retransmittable_bytes);
const OSSL_CC_ACK_INFO *info);
/*
* 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.
* 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().
*/
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_lost)(OSSL_CC_DATA *ccdata,
const OSSL_CC_LOSS_INFO *info);
/*
* 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.
* 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_spurious_congestion_event)(OSSL_CC_DATA *ccdata);
int (*on_data_lost_finished)(OSSL_CC_DATA *ccdata, uint32_t flags);
/*
* 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.
*/
int (*on_data_invalidated)(OSSL_CC_DATA *ccdata,
uint64_t num_bytes);
/*
* 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_ecn)(OSSL_CC_DATA *ccdata,
const OSSL_CC_ECN_INFO *info);
} OSSL_CC_METHOD;
extern const OSSL_CC_METHOD ossl_cc_dummy_method;

View File

@ -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;
switch (option_id) {
case OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN:
if (value > SIZE_MAX)
return 0;
d->max_dgram_len = (size_t)value;
return 1;
default:
return 0;
}
}
static int dummy_get_exemption(OSSL_CC_DATA *cc)
static int dummy_get_option_uint(OSSL_CC_DATA *cc,
uint32_t option_id,
uint64_t *value)
{
return 0;
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 int dummy_can_send(OSSL_CC_DATA *cc)
{
return 1;
}
static uint64_t dummy_get_send_allowance(OSSL_CC_DATA *cc,
OSSL_TIME time_since_last_send,
int time_valid)
static uint64_t dummy_get_tx_allowance(OSSL_CC_DATA *cc)
{
return SIZE_MAX;
}
static uint64_t dummy_get_bytes_in_flight_max(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_acked(OSSL_CC_DATA *cc,
const OSSL_CC_ACK_INFO *info)
{
return 1;
}
static int dummy_on_data_lost(OSSL_CC_DATA *cc,
const OSSL_CC_LOSS_INFO *info)
{
return 1;
}
static int dummy_on_data_lost_finished(OSSL_CC_DATA *cc,
uint32_t flags)
{
return 1;
}
static int dummy_on_data_invalidated(OSSL_CC_DATA *cc,
uint64_t num_retransmittable_bytes)
{
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)
{
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_spurious_congestion_event(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,
};

View File

@ -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);

View File

@ -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))

View File

@ -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_);
}

View File

@ -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;

View File

@ -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,

View File

@ -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,