mirror of
https://github.com/openssl/openssl.git
synced 2025-02-17 14:32:04 +08:00
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:
parent
91d39be797
commit
90699176b0
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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_);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user