From: Hugo Landau Date: Tue, 22 Aug 2023 15:59:57 +0000 (+0100) Subject: QUIC APL: Implement backpressure on stream creation X-Git-Tag: openssl-3.2.0-alpha1~138 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d6bd3d30f8068a5558efa0bda2db570500ff364;p=thirdparty%2Fopenssl.git QUIC APL: Implement backpressure on stream creation Reviewed-by: Matt Caswell Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/21811) --- diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index f04a6b1a09f..b725032254e 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -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 diff --git a/doc/man3/SSL_new_stream.pod b/doc/man3/SSL_new_stream.pod index 7888cc44908..a3ca2b96a9e 100644 --- a/doc/man3/SSL_new_stream.pod +++ b/doc/man3/SSL_new_stream.pod @@ -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 - #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. Calling SSL_new_stream() if there is no default stream already present inhibits the future creation of a default stream. See L. +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). It may also be +used in nonblocking mode on a connection configured in blocking mode by passing +the flag B. + +The flag B 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. 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. diff --git a/include/internal/quic_channel.h b/include/internal/quic_channel.h index 2524a65fe77..44009d1c209 100644 --- a/include/internal/quic_channel.h +++ b/include/internal/quic_channel.h @@ -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 diff --git a/include/internal/quic_stream_map.h b/include/internal/quic_stream_map.h index 10b3cfa32c1..cc071dba4c7 100644 --- a/include/internal/quic_stream_map.h +++ b/include/internal/quic_stream_map.h @@ -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 * ================ diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index 1738d97021c..9448974403b 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -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 diff --git a/include/openssl/sslerr.h b/include/openssl/sslerr.h index adaa91d7ff7..0a4079b5c80 100644 --- a/include/openssl/sslerr.h +++ b/include/openssl/sslerr.h @@ -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 diff --git a/ssl/quic/quic_channel.c b/ssl/quic/quic_channel.c index 5cee9f75320..98e1a0110fc 100644 --- a/ssl/quic/quic_channel.c +++ b/ssl/quic/quic_channel.c @@ -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; diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c index 6bb6b465838..5073bb1e6fa 100644 --- a/ssl/quic/quic_impl.c +++ b/ssl/quic/quic_impl.c @@ -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); diff --git a/ssl/quic/quic_stream_map.c b/ssl/quic/quic_stream_map.c index 5d4354a2df1..4b595f50e7c 100644 --- a/ssl/quic/quic_stream_map.c +++ b/ssl/quic/quic_stream_map.c @@ -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; - - stream_limit - = qsm->get_stream_limit_cb(uni, qsm->get_stream_limit_cb_arg); + 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; - 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 diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index 7bbc55dc690..1cda4a0aef2 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -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"}, diff --git a/util/other.syms b/util/other.syms index 11393fe7ec6..ea48770ea04 100644 --- a/util/other.syms +++ b/util/other.syms @@ -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