]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
quic-hq-interop: Allow for retries if we've reached our max stream limit
authorNeil Horman <nhorman@openssl.org>
Wed, 22 Jan 2025 18:19:52 +0000 (13:19 -0500)
committerNeil Horman <nhorman@openssl.org>
Mon, 17 Feb 2025 16:27:33 +0000 (11:27 -0500)
Several servers defer the sending of max stream frames.  For instance
quic-go uses a go-routine to do the sending after sufficient existing
streams have finished, while mvfst seems to wait for all outstanding
streams to be closed before issuing a new batch.  This result in the
client, if all streams are in use, getting a transient NULL return from
SSL_new_stream().  Check for the stream limit being reached and allow a
number of retries before giving up to give the server a chance to issue
us more streams.  Also dead-reckon the batch count of streams we use in
parallel to be 1/4 of our total number of available streams (generally
hard coded to 100 for most servers) to avoid using all our streams at
once.  It would be really nice to have an api to expose our negotiated
transport parameters so that the application can know what this limit
is, but until then we have to just guess.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26527)

demos/guide/quic-hq-interop.c
doc/man3/SSL_new_stream.pod

index a798476cc877452456d71f1649d90e2bb3000920..bedfeb2bc73acbce0158a248c2cb63f69a8856f6 100644 (file)
@@ -529,6 +529,8 @@ static size_t build_request_set(SSL *ssl)
     char req_string[REQ_STRING_SZ];
     SSL *new_stream;
     size_t written;
+    unsigned long error;
+    size_t retry_count;
 
     /*
      * Free any previous poll list
@@ -606,12 +608,29 @@ static size_t build_request_set(SSL *ssl)
         new_stream = NULL;
 
         /*
-         * We don't strictly have to do this check, but our quic client limits
-         * our max data streams to 100, so we're just batching in groups of 100
-         * for now
+         * NOTE: We are doing groups of 25 because thats 1/4 of the initial max
+         * stream count that most servers advertise.  This gives the server an
+         * opportunity to send us updated MAX_STREAM frames to extend our stream
+         * allotment before we run out, which many servers defer doing.
          */
-        if (poll_count <= 99)
-            new_stream = SSL_new_stream(ssl, 0);
+        if (poll_count <= 25) {
+            for (retry_count = 0; retry_count < 10; retry_count++) {
+                ERR_clear_error();
+                new_stream = SSL_new_stream(ssl, 0);
+                if (new_stream == NULL
+                    && (error = ERR_get_error()) != 0
+                    && ERR_GET_REASON(error) == SSL_R_STREAM_COUNT_LIMITED) {
+                    /*
+                     * Kick the SSL state machine in the hopes that
+                     * the server has a MAX_STREAM frame for us to process
+                     */
+                    fprintf(stderr, "Stream limit reached, retrying\n");
+                    SSL_handle_events(ssl);
+                    continue;
+                }
+                break;
+            }
+        }
 
         if (new_stream == NULL) {
             /*
index a3ca2b96a9ee2a781562a3e6c617a9ba8a2935ab..0c2fb6914eca1c50161f7afa81a668bf990f39b4 100644 (file)
@@ -69,6 +69,18 @@ SSL_new_stream() returns a new stream object, or NULL on error.
 This function fails if called on a QUIC stream SSL object or on a non-QUIC SSL
 object.
 
+SSL_new_stream() may also fail if the connection that the stream is being
+allocated in relation to has reached the maximum number of streams allowed by
+the connection (as dictated by the B<max_streams_bidi> or B<max_streams_uni>
+transport parameter values negotiated by the connection with the server.  In
+this event the NULL return will be accompanied by an error on the error stack
+(obtainable via ERR_get_error()), which will contain a reason code of
+B<SSL_R_STREAM_COUNT_LIMITED>.  When this error is encountered, The operation
+may be retried.  It is recommended that, prior to retry, the error stack be
+cleared via a call to ERR_clear_error(), and that the TLS state machine be
+triggered via a call to SSL_handle_events() to process any potential updates
+from the server allowing additional streams to be created.
+
 =head1 SEE ALSO
 
 L<SSL_accept_stream(3)>, L<SSL_free(3)>