mirror of
https://github.com/openssl/openssl.git
synced 2024-12-21 06:09:35 +08:00
e57bf6b3bf
If a single packet contains data from multiple streams we need to keep track of the cummulative connection level credit consumed across all of the streams. Once the connection level credit has been consumed we must stop adding stream data. Fixes #22706 Reviewed-by: Hugo Landau <hlandau@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.org> (Merged from https://github.com/openssl/openssl/pull/22718)
407 lines
10 KiB
C
407 lines
10 KiB
C
/*
|
|
* Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
* this file except in compliance with the License. You can obtain a copy
|
|
* in the file LICENSE in the source distribution or at
|
|
* https://www.openssl.org/source/license.html
|
|
*/
|
|
|
|
#include "internal/quic_fc.h"
|
|
#include "internal/quic_error.h"
|
|
#include "internal/common.h"
|
|
#include "internal/safe_math.h"
|
|
#include <assert.h>
|
|
|
|
OSSL_SAFE_MATH_UNSIGNED(uint64_t, uint64_t)
|
|
|
|
/*
|
|
* TX Flow Controller (TXFC)
|
|
* =========================
|
|
*/
|
|
|
|
int ossl_quic_txfc_init(QUIC_TXFC *txfc, QUIC_TXFC *conn_txfc)
|
|
{
|
|
if (conn_txfc != NULL && conn_txfc->parent != NULL)
|
|
return 0;
|
|
|
|
txfc->swm = 0;
|
|
txfc->cwm = 0;
|
|
txfc->parent = conn_txfc;
|
|
txfc->has_become_blocked = 0;
|
|
return 1;
|
|
}
|
|
|
|
QUIC_TXFC *ossl_quic_txfc_get_parent(QUIC_TXFC *txfc)
|
|
{
|
|
return txfc->parent;
|
|
}
|
|
|
|
int ossl_quic_txfc_bump_cwm(QUIC_TXFC *txfc, uint64_t cwm)
|
|
{
|
|
if (cwm <= txfc->cwm)
|
|
return 0;
|
|
|
|
txfc->cwm = cwm;
|
|
return 1;
|
|
}
|
|
|
|
uint64_t ossl_quic_txfc_get_credit_local(QUIC_TXFC *txfc, uint64_t consumed)
|
|
{
|
|
assert((txfc->swm + consumed) <= txfc->cwm);
|
|
return txfc->cwm - (consumed + txfc->swm);
|
|
}
|
|
|
|
uint64_t ossl_quic_txfc_get_credit(QUIC_TXFC *txfc, uint64_t consumed)
|
|
{
|
|
uint64_t r, conn_r;
|
|
|
|
r = ossl_quic_txfc_get_credit_local(txfc, 0);
|
|
|
|
if (txfc->parent != NULL) {
|
|
assert(txfc->parent->parent == NULL);
|
|
conn_r = ossl_quic_txfc_get_credit_local(txfc->parent, consumed);
|
|
if (conn_r < r)
|
|
r = conn_r;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int ossl_quic_txfc_consume_credit_local(QUIC_TXFC *txfc, uint64_t num_bytes)
|
|
{
|
|
int ok = 1;
|
|
uint64_t credit = ossl_quic_txfc_get_credit_local(txfc, 0);
|
|
|
|
if (num_bytes > credit) {
|
|
ok = 0;
|
|
num_bytes = credit;
|
|
}
|
|
|
|
if (num_bytes > 0 && num_bytes == credit)
|
|
txfc->has_become_blocked = 1;
|
|
|
|
txfc->swm += num_bytes;
|
|
return ok;
|
|
}
|
|
|
|
int ossl_quic_txfc_consume_credit(QUIC_TXFC *txfc, uint64_t num_bytes)
|
|
{
|
|
int ok = ossl_quic_txfc_consume_credit_local(txfc, num_bytes);
|
|
|
|
if (txfc->parent != NULL) {
|
|
assert(txfc->parent->parent == NULL);
|
|
if (!ossl_quic_txfc_consume_credit_local(txfc->parent, num_bytes))
|
|
return 0;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
int ossl_quic_txfc_has_become_blocked(QUIC_TXFC *txfc, int clear)
|
|
{
|
|
int r = txfc->has_become_blocked;
|
|
|
|
if (clear)
|
|
txfc->has_become_blocked = 0;
|
|
|
|
return r;
|
|
}
|
|
|
|
uint64_t ossl_quic_txfc_get_cwm(QUIC_TXFC *txfc)
|
|
{
|
|
return txfc->cwm;
|
|
}
|
|
|
|
uint64_t ossl_quic_txfc_get_swm(QUIC_TXFC *txfc)
|
|
{
|
|
return txfc->swm;
|
|
}
|
|
|
|
/*
|
|
* RX Flow Controller (RXFC)
|
|
* =========================
|
|
*/
|
|
|
|
int ossl_quic_rxfc_init(QUIC_RXFC *rxfc, QUIC_RXFC *conn_rxfc,
|
|
uint64_t initial_window_size,
|
|
uint64_t max_window_size,
|
|
OSSL_TIME (*now)(void *now_arg),
|
|
void *now_arg)
|
|
{
|
|
if (conn_rxfc != NULL && conn_rxfc->parent != NULL)
|
|
return 0;
|
|
|
|
rxfc->swm = 0;
|
|
rxfc->cwm = initial_window_size;
|
|
rxfc->rwm = 0;
|
|
rxfc->esrwm = 0;
|
|
rxfc->hwm = 0;
|
|
rxfc->cur_window_size = initial_window_size;
|
|
rxfc->max_window_size = max_window_size;
|
|
rxfc->parent = conn_rxfc;
|
|
rxfc->error_code = 0;
|
|
rxfc->has_cwm_changed = 0;
|
|
rxfc->epoch_start = ossl_time_zero();
|
|
rxfc->now = now;
|
|
rxfc->now_arg = now_arg;
|
|
rxfc->is_fin = 0;
|
|
rxfc->standalone = 0;
|
|
return 1;
|
|
}
|
|
|
|
int ossl_quic_rxfc_init_standalone(QUIC_RXFC *rxfc,
|
|
uint64_t initial_window_size,
|
|
OSSL_TIME (*now)(void *arg),
|
|
void *now_arg)
|
|
{
|
|
if (!ossl_quic_rxfc_init(rxfc, NULL,
|
|
initial_window_size, initial_window_size,
|
|
now, now_arg))
|
|
return 0;
|
|
|
|
rxfc->standalone = 1;
|
|
return 1;
|
|
}
|
|
|
|
QUIC_RXFC *ossl_quic_rxfc_get_parent(QUIC_RXFC *rxfc)
|
|
{
|
|
return rxfc->parent;
|
|
}
|
|
|
|
void ossl_quic_rxfc_set_max_window_size(QUIC_RXFC *rxfc,
|
|
size_t max_window_size)
|
|
{
|
|
rxfc->max_window_size = max_window_size;
|
|
}
|
|
|
|
static void rxfc_start_epoch(QUIC_RXFC *rxfc)
|
|
{
|
|
rxfc->epoch_start = rxfc->now(rxfc->now_arg);
|
|
rxfc->esrwm = rxfc->rwm;
|
|
}
|
|
|
|
static int on_rx_controlled_bytes(QUIC_RXFC *rxfc, uint64_t num_bytes)
|
|
{
|
|
int ok = 1;
|
|
uint64_t credit = rxfc->cwm - rxfc->swm;
|
|
|
|
if (num_bytes > credit) {
|
|
ok = 0;
|
|
num_bytes = credit;
|
|
rxfc->error_code = QUIC_ERR_FLOW_CONTROL_ERROR;
|
|
}
|
|
|
|
rxfc->swm += num_bytes;
|
|
return ok;
|
|
}
|
|
|
|
int ossl_quic_rxfc_on_rx_stream_frame(QUIC_RXFC *rxfc, uint64_t end, int is_fin)
|
|
{
|
|
uint64_t delta;
|
|
|
|
if (!rxfc->standalone && rxfc->parent == NULL)
|
|
return 0;
|
|
|
|
if (rxfc->is_fin && ((is_fin && rxfc->hwm != end) || end > rxfc->hwm)) {
|
|
/* Stream size cannot change after the stream is finished */
|
|
rxfc->error_code = QUIC_ERR_FINAL_SIZE_ERROR;
|
|
return 1; /* not a caller error */
|
|
}
|
|
|
|
if (is_fin)
|
|
rxfc->is_fin = 1;
|
|
|
|
if (end > rxfc->hwm) {
|
|
delta = end - rxfc->hwm;
|
|
rxfc->hwm = end;
|
|
|
|
on_rx_controlled_bytes(rxfc, delta); /* result ignored */
|
|
if (rxfc->parent != NULL)
|
|
on_rx_controlled_bytes(rxfc->parent, delta); /* result ignored */
|
|
} else if (end < rxfc->hwm && is_fin) {
|
|
rxfc->error_code = QUIC_ERR_FINAL_SIZE_ERROR;
|
|
return 1; /* not a caller error */
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* threshold = 3/4 */
|
|
#define WINDOW_THRESHOLD_NUM 3
|
|
#define WINDOW_THRESHOLD_DEN 4
|
|
|
|
static int rxfc_cwm_bump_desired(QUIC_RXFC *rxfc)
|
|
{
|
|
int err = 0;
|
|
uint64_t window_rem = rxfc->cwm - rxfc->rwm;
|
|
uint64_t threshold
|
|
= safe_muldiv_uint64_t(rxfc->cur_window_size,
|
|
WINDOW_THRESHOLD_NUM, WINDOW_THRESHOLD_DEN, &err);
|
|
|
|
if (err)
|
|
/*
|
|
* Extremely large window should never occur, but if it does, just use
|
|
* 1/2 as the threshold.
|
|
*/
|
|
threshold = rxfc->cur_window_size / 2;
|
|
|
|
/*
|
|
* No point emitting a new MAX_STREAM_DATA frame if the stream has a final
|
|
* size.
|
|
*/
|
|
return !rxfc->is_fin && window_rem <= threshold;
|
|
}
|
|
|
|
static int rxfc_should_bump_window_size(QUIC_RXFC *rxfc, OSSL_TIME rtt)
|
|
{
|
|
/*
|
|
* dt: time since start of epoch
|
|
* b: bytes of window consumed since start of epoch
|
|
* dw: proportion of window consumed since start of epoch
|
|
* T_window: time it will take to use up the entire window, based on dt, dw
|
|
* RTT: The current estimated RTT.
|
|
*
|
|
* b = rwm - esrwm
|
|
* dw = b / window_size
|
|
* T_window = dt / dw
|
|
* T_window = dt / (b / window_size)
|
|
* T_window = (dt * window_size) / b
|
|
*
|
|
* We bump the window size if T_window < 4 * RTT.
|
|
*
|
|
* We leave the division by b on the LHS to reduce the risk of overflowing
|
|
* our 64-bit nanosecond representation, which will afford plenty of
|
|
* precision left over after the division anyway.
|
|
*/
|
|
uint64_t b = rxfc->rwm - rxfc->esrwm;
|
|
OSSL_TIME now, dt, t_window;
|
|
|
|
if (b == 0)
|
|
return 0;
|
|
|
|
now = rxfc->now(rxfc->now_arg);
|
|
dt = ossl_time_subtract(now, rxfc->epoch_start);
|
|
t_window = ossl_time_muldiv(dt, rxfc->cur_window_size, b);
|
|
|
|
return ossl_time_compare(t_window, ossl_time_multiply(rtt, 4)) < 0;
|
|
}
|
|
|
|
static void rxfc_adjust_window_size(QUIC_RXFC *rxfc, uint64_t min_window_size,
|
|
OSSL_TIME rtt)
|
|
{
|
|
/* Are we sending updates too often? */
|
|
uint64_t new_window_size;
|
|
|
|
new_window_size = rxfc->cur_window_size;
|
|
|
|
if (rxfc_should_bump_window_size(rxfc, rtt))
|
|
new_window_size *= 2;
|
|
|
|
if (new_window_size < min_window_size)
|
|
new_window_size = min_window_size;
|
|
if (new_window_size > rxfc->max_window_size) /* takes precedence over min size */
|
|
new_window_size = rxfc->max_window_size;
|
|
|
|
rxfc->cur_window_size = new_window_size;
|
|
rxfc_start_epoch(rxfc);
|
|
}
|
|
|
|
static void rxfc_update_cwm(QUIC_RXFC *rxfc, uint64_t min_window_size,
|
|
OSSL_TIME rtt)
|
|
{
|
|
uint64_t new_cwm;
|
|
|
|
if (!rxfc_cwm_bump_desired(rxfc))
|
|
return;
|
|
|
|
rxfc_adjust_window_size(rxfc, min_window_size, rtt);
|
|
|
|
new_cwm = rxfc->rwm + rxfc->cur_window_size;
|
|
if (new_cwm > rxfc->cwm) {
|
|
rxfc->cwm = new_cwm;
|
|
rxfc->has_cwm_changed = 1;
|
|
}
|
|
}
|
|
|
|
static int rxfc_on_retire(QUIC_RXFC *rxfc, uint64_t num_bytes,
|
|
uint64_t min_window_size,
|
|
OSSL_TIME rtt)
|
|
{
|
|
if (ossl_time_is_zero(rxfc->epoch_start))
|
|
/* This happens when we retire our first ever bytes. */
|
|
rxfc_start_epoch(rxfc);
|
|
|
|
rxfc->rwm += num_bytes;
|
|
rxfc_update_cwm(rxfc, min_window_size, rtt);
|
|
return 1;
|
|
}
|
|
|
|
int ossl_quic_rxfc_on_retire(QUIC_RXFC *rxfc,
|
|
uint64_t num_bytes,
|
|
OSSL_TIME rtt)
|
|
{
|
|
if (rxfc->parent == NULL && !rxfc->standalone)
|
|
return 0;
|
|
|
|
if (num_bytes == 0)
|
|
return 1;
|
|
|
|
if (rxfc->rwm + num_bytes > rxfc->swm)
|
|
/* Impossible for us to retire more bytes than we have received. */
|
|
return 0;
|
|
|
|
rxfc_on_retire(rxfc, num_bytes, 0, rtt);
|
|
|
|
if (!rxfc->standalone)
|
|
rxfc_on_retire(rxfc->parent, num_bytes, rxfc->cur_window_size, rtt);
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint64_t ossl_quic_rxfc_get_cwm(QUIC_RXFC *rxfc)
|
|
{
|
|
return rxfc->cwm;
|
|
}
|
|
|
|
uint64_t ossl_quic_rxfc_get_swm(QUIC_RXFC *rxfc)
|
|
{
|
|
return rxfc->swm;
|
|
}
|
|
|
|
uint64_t ossl_quic_rxfc_get_rwm(QUIC_RXFC *rxfc)
|
|
{
|
|
return rxfc->rwm;
|
|
}
|
|
|
|
int ossl_quic_rxfc_has_cwm_changed(QUIC_RXFC *rxfc, int clear)
|
|
{
|
|
int r = rxfc->has_cwm_changed;
|
|
|
|
if (clear)
|
|
rxfc->has_cwm_changed = 0;
|
|
|
|
return r;
|
|
}
|
|
|
|
int ossl_quic_rxfc_get_error(QUIC_RXFC *rxfc, int clear)
|
|
{
|
|
int r = rxfc->error_code;
|
|
|
|
if (clear)
|
|
rxfc->error_code = 0;
|
|
|
|
return r;
|
|
}
|
|
|
|
int ossl_quic_rxfc_get_final_size(const QUIC_RXFC *rxfc, uint64_t *final_size)
|
|
{
|
|
if (!rxfc->is_fin)
|
|
return 0;
|
|
|
|
if (final_size != NULL)
|
|
*final_size = rxfc->hwm;
|
|
|
|
return 1;
|
|
}
|