QUIC APL: Implement backpressure on stream creation
authorHugo Landau <hlandau@openssl.org>
Tue, 22 Aug 2023 15:59:57 +0000 (16:59 +0100)
committerTomas Mraz <tomas@openssl.org>
Fri, 25 Aug 2023 13:10:43 +0000 (15:10 +0200)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21811)

crypto/err/openssl.txt
doc/man3/SSL_new_stream.pod
include/internal/quic_channel.h
include/internal/quic_stream_map.h
include/openssl/ssl.h.in
include/openssl/sslerr.h
ssl/quic/quic_channel.c
ssl/quic/quic_impl.c
ssl/quic/quic_stream_map.c
ssl/ssl_err.c
util/other.syms

index f04a6b1a09faface3e02d5b8271c6bb43fe36800..b725032254e1c2e596084b71d6d7afeb2e6128fb 100644 (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
index 7888cc44908a6f407f2db6a716df5218e396e9d7..a3ca2b96a9ee2a781562a3e6c617a9ba8a2935ab 100644 (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.
index 2524a65fe770b3db84d2c8f22c522345bf019273..44009d1c209ed1633266c0c1c9fc33a39b964854 100644 (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
index 10b3cfa32c1a45c239791796ebcafb3c82224936..cc071dba4c7732ef9ca1893d62d9f3e2a9f5e082 100644 (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
  * ================
index 1738d97021c41dcd228973015994f0edf7ccbfc0..9448974403be1ab9f71ec0e23b67b1c4ffd4e6d6 100644 (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
index adaa91d7ff71e11ea50556be0a396be4f4fd4b79..0a4079b5c809324346bc910173362458d1fb04dc 100644 (file)
 # 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
index 5cee9f753208430dc70d26ac7f1fed17b45af827..98e1a0110fc8e562bb59ce6f4197a0a87badd24a 100644 (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;
index 6bb6b465838e7d8cb15130c951a43e272fa5f129..5073bb1e6fadef2281d18f91449c681fd8152298 100644 (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);
index 5d4354a2df175e7d09481c0332602ac9a16fed4f..4b595f50e7c821887407313034495ee3037653ff 100644 (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;
-
-        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
index 7bbc55dc6906aa6316fa29e68b519ceffdbe7621..1cda4a0aef2555309e7803ca4166565fa113be72 100644 (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"},
index 11393fe7ec69a392fb56205928a17880c703c81a..ea48770ea0445b3fe5c5c1bd3d4932f3c32200b4 100644 (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