QUIC APL: Implement backpressure on stream creation

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21811)
This commit is contained in:
Hugo Landau 2023-08-22 16:59:57 +01:00 committed by Tomas Mraz
parent 10536b7f5b
commit 9d6bd3d30f
11 changed files with 142 additions and 16 deletions

View File

@ -1563,6 +1563,7 @@ SSL_R_SSL_SESSION_ID_HAS_BAD_LENGTH:303:ssl session id has bad length
SSL_R_SSL_SESSION_ID_TOO_LONG:408:ssl session id too long
SSL_R_SSL_SESSION_VERSION_MISMATCH:210:ssl session version mismatch
SSL_R_STILL_IN_INIT:121:still in init
SSL_R_STREAM_COUNT_LIMITED:395:stream count limited
SSL_R_STREAM_FINISHED:365:stream finished
SSL_R_STREAM_RECV_ONLY:366:stream recv only
SSL_R_STREAM_RESET:375:stream reset

View File

@ -2,13 +2,16 @@
=head1 NAME
SSL_new_stream, SSL_STREAM_FLAG_UNI - create a new locally-initiated QUIC stream
SSL_new_stream, SSL_STREAM_FLAG_UNI, SSL_STREAM_FLAG_NO_BLOCK,
SSL_STREAM_FLAG_ADVANCE - create a new locally-initiated QUIC stream
=head1 SYNOPSIS
#include <openssl/ssl.h>
#define SSL_STREAM_FLAG_UNI (1U << 0)
#define SSL_STREAM_FLAG_UNI (1U << 0)
#define SSL_STREAM_FLAG_NO_BLOCK (1U << 1)
#define SSL_STREAM_FLAG_ADVANCE (1U << 2)
SSL *SSL_new_stream(SSL *ssl, uint64_t flags);
=head1 DESCRIPTION
@ -38,6 +41,27 @@ L<SSL_accept_stream(3)>.
Calling SSL_new_stream() if there is no default stream already present
inhibits the future creation of a default stream. See L<openssl-quic(7)>.
The creation of new streams is subject to flow control by the QUIC protocol. If
it is currently not possible to create a new locally initiated stream of the
specified type, a call to SSL_new_stream() will either block (if the connection
is configured in blocking mode) until a new stream can be created, or otherwise
return NULL.
This function operates in blocking mode if the QUIC connection SSL object is
configured in blocking mode (see L<SSL_set_blocking_mode(3)>). It may also be
used in nonblocking mode on a connection configured in blocking mode by passing
the flag B<SSL_STREAM_FLAG_NO_BLOCK>.
The flag B<SSL_STREAM_FLAG_ADVANCE> may be used to create a QUIC stream SSL
object even if a new QUIC stream cannot yet be opened due to flow control. The
caller may begin to use the new stream and fill the write buffer of the stream
by calling L<SSL_write(3)>. However, no actual stream data (or QUIC frames
regarding the stream) will be sent until QUIC flow control allows it. Any queued
data will be sent as soon as a peer permits it. There is no guarantee the stream
will be eventually created; for example, the connection could fail, or a peer
might simply decide never to increase the number of allowed streams for the
remainder of the connection lifetime.
=head1 RETURN VALUES
SSL_new_stream() returns a new stream object, or NULL on error.

View File

@ -411,6 +411,12 @@ uint16_t ossl_quic_channel_get_diag_num_rx_ack(QUIC_CHANNEL *ch);
*/
void ossl_quic_channel_get_diag_local_cid(QUIC_CHANNEL *ch, QUIC_CONN_ID *cid);
/*
* Returns 1 if stream count flow control allows us to create a new
* locally-initiated stream.
*/
int ossl_quic_channel_is_new_local_stream_admissible(QUIC_CHANNEL *ch, int is_uni);
# endif
#endif

View File

@ -607,6 +607,17 @@ void ossl_quic_stream_map_update_state(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s);
*/
void ossl_quic_stream_map_set_rr_stepping(QUIC_STREAM_MAP *qsm, size_t stepping);
/*
* Returns 1 if the stream ordinal given is allowed by the current stream count
* flow control limit, assuming a locally initiated stream of a type described
* by is_uni.
*
* Note that stream_ordinal is a stream ordinal, not a stream ID.
*/
int ossl_quic_stream_map_is_local_allowed_by_stream_limit(QUIC_STREAM_MAP *qsm,
uint64_t stream_ordinal,
int is_uni);
/*
* Stream Send Part
* ================

View File

@ -2284,7 +2284,9 @@ __owur uint64_t SSL_get_stream_id(SSL *s);
#define SSL_DEFAULT_STREAM_MODE_AUTO_UNI 2
__owur int SSL_set_default_stream_mode(SSL *s, uint32_t mode);
#define SSL_STREAM_FLAG_UNI (1U << 0)
#define SSL_STREAM_FLAG_UNI (1U << 0)
#define SSL_STREAM_FLAG_NO_BLOCK (1U << 1)
#define SSL_STREAM_FLAG_ADVANCE (1U << 2)
__owur SSL *SSL_new_stream(SSL *s, uint64_t flags);
#define SSL_INCOMING_STREAM_POLICY_AUTO 0

View File

@ -290,6 +290,7 @@
# define SSL_R_SSL_SESSION_ID_TOO_LONG 408
# define SSL_R_SSL_SESSION_VERSION_MISMATCH 210
# define SSL_R_STILL_IN_INIT 121
# define SSL_R_STREAM_COUNT_LIMITED 395
# define SSL_R_STREAM_FINISHED 365
# define SSL_R_STREAM_RECV_ONLY 366
# define SSL_R_STREAM_RESET 375

View File

@ -3434,6 +3434,23 @@ err:
return 0;
}
static uint64_t *ch_get_local_stream_next_ordinal_ptr(QUIC_CHANNEL *ch,
int is_uni)
{
return is_uni ? &ch->next_local_stream_ordinal_uni
: &ch->next_local_stream_ordinal_bidi;
}
int ossl_quic_channel_is_new_local_stream_admissible(QUIC_CHANNEL *ch,
int is_uni)
{
uint64_t *p_next_ordinal = ch_get_local_stream_next_ordinal_ptr(ch, is_uni);
return ossl_quic_stream_map_is_local_allowed_by_stream_limit(&ch->qsm,
*p_next_ordinal,
is_uni);
}
QUIC_STREAM *ossl_quic_channel_new_stream_local(QUIC_CHANNEL *ch, int is_uni)
{
QUIC_STREAM *qs;
@ -3443,13 +3460,12 @@ QUIC_STREAM *ossl_quic_channel_new_stream_local(QUIC_CHANNEL *ch, int is_uni)
type = ch->is_server ? QUIC_STREAM_INITIATOR_SERVER
: QUIC_STREAM_INITIATOR_CLIENT;
if (is_uni) {
p_next_ordinal = &ch->next_local_stream_ordinal_uni;
p_next_ordinal = ch_get_local_stream_next_ordinal_ptr(ch, is_uni);
if (is_uni)
type |= QUIC_STREAM_DIR_UNI;
} else {
p_next_ordinal = &ch->next_local_stream_ordinal_bidi;
else
type |= QUIC_STREAM_DIR_BIDI;
}
if (*p_next_ordinal >= ((uint64_t)1) << 62)
return NULL;

View File

@ -1717,13 +1717,35 @@ err:
return NULL;
}
struct quic_new_stream_wait_args {
QUIC_CONNECTION *qc;
int is_uni;
};
static int quic_new_stream_wait(void *arg)
{
struct quic_new_stream_wait_args *args = arg;
QUIC_CONNECTION *qc = args->qc;
if (!quic_mutation_allowed(qc, /*req_active=*/1))
return -1;
if (ossl_quic_channel_is_new_local_stream_admissible(qc->ch, args->is_uni))
return 1;
return 0;
}
/* locking depends on need_lock */
static SSL *quic_conn_stream_new(QCTX *ctx, uint64_t flags, int need_lock)
{
int ret;
QUIC_CONNECTION *qc = ctx->qc;
QUIC_XSO *xso = NULL;
QUIC_STREAM *qs = NULL;
int is_uni = ((flags & SSL_STREAM_FLAG_UNI) != 0);
int no_blocking = ((flags & SSL_STREAM_FLAG_NO_BLOCK) != 0);
int advance = ((flags & SSL_STREAM_FLAG_ADVANCE) != 0);
if (need_lock)
quic_lock(qc);
@ -1733,6 +1755,33 @@ static SSL *quic_conn_stream_new(QCTX *ctx, uint64_t flags, int need_lock)
goto err;
}
if (!advance
&& !ossl_quic_channel_is_new_local_stream_admissible(qc->ch, is_uni)) {
struct quic_new_stream_wait_args args;
/*
* Stream count flow control currently doesn't permit this stream to be
* opened.
*/
if (no_blocking || !qc_blocking_mode(qc)) {
QUIC_RAISE_NON_NORMAL_ERROR(ctx, SSL_R_STREAM_COUNT_LIMITED, NULL);
goto err;
}
args.qc = qc;
args.is_uni = is_uni;
/* Blocking mode - wait until we can get a stream. */
ret = block_until_pred(ctx->qc, quic_new_stream_wait, &args, 0);
if (!quic_mutation_allowed(qc, /*req_active=*/1)) {
QUIC_RAISE_NON_NORMAL_ERROR(ctx, SSL_R_PROTOCOL_IS_SHUTDOWN, NULL);
goto err; /* Shutdown before completion */
} else if (ret <= 0) {
QUIC_RAISE_NON_NORMAL_ERROR(ctx, ERR_R_INTERNAL_ERROR, NULL);
goto err; /* Non-protocol error */
}
}
qs = ossl_quic_channel_new_stream_local(qc->ch, is_uni);
if (qs == NULL) {
QUIC_RAISE_NON_IO_ERROR(ctx, ERR_R_INTERNAL_ERROR, NULL);

View File

@ -311,19 +311,31 @@ static int qsm_ready_for_gc(QUIC_STREAM_MAP *qsm, QUIC_STREAM *qs)
|| qs->send_state == QUIC_SSTREAM_STATE_RESET_RECVD);
}
int ossl_quic_stream_map_is_local_allowed_by_stream_limit(QUIC_STREAM_MAP *qsm,
uint64_t stream_ordinal,
int is_uni)
{
uint64_t stream_limit;
if (qsm->get_stream_limit_cb == NULL)
return 1;
stream_limit = qsm->get_stream_limit_cb(is_uni, qsm->get_stream_limit_cb_arg);
return stream_ordinal < stream_limit;
}
void ossl_quic_stream_map_update_state(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s)
{
int should_be_active, allowed_by_stream_limit = 1;
if (qsm->get_stream_limit_cb != NULL
&& ossl_quic_stream_is_server_init(s) == qsm->is_server) {
int uni = !ossl_quic_stream_is_bidi(s);
uint64_t stream_limit, stream_ordinal = s->id >> 2;
if (ossl_quic_stream_is_server_init(s) == qsm->is_server) {
int is_uni = !ossl_quic_stream_is_bidi(s);
uint64_t stream_ordinal = s->id >> 2;
stream_limit
= qsm->get_stream_limit_cb(uni, qsm->get_stream_limit_cb_arg);
allowed_by_stream_limit = (stream_ordinal < stream_limit);
allowed_by_stream_limit
= ossl_quic_stream_map_is_local_allowed_by_stream_limit(qsm,
stream_ordinal,
is_uni);
}
if (s->send_state == QUIC_SSTREAM_STATE_DATA_SENT

View File

@ -466,6 +466,8 @@ static const ERR_STRING_DATA SSL_str_reasons[] = {
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SSL_SESSION_VERSION_MISMATCH),
"ssl session version mismatch"},
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_STILL_IN_INIT), "still in init"},
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_STREAM_COUNT_LIMITED),
"stream count limited"},
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_STREAM_FINISHED), "stream finished"},
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_STREAM_RECV_ONLY), "stream recv only"},
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_STREAM_RESET), "stream reset"},

View File

@ -647,6 +647,8 @@ SSLv23_client_method define
SSLv23_method define
SSLv23_server_method define
SSL_STREAM_FLAG_UNI define
SSL_STREAM_FLAG_NO_BLOCK define
SSL_STREAM_FLAG_ADVANCE define
SSL_STREAM_TYPE_NONE define
SSL_STREAM_TYPE_READ define
SSL_STREAM_TYPE_WRITE define