QUIC APL: Support waiting for peer-initiated shutdown
authorHugo Landau <hlandau@openssl.org>
Wed, 23 Aug 2023 07:25:28 +0000 (08:25 +0100)
committerHugo Landau <hlandau@openssl.org>
Wed, 30 Aug 2023 07:28:22 +0000 (08:28 +0100)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21815)

doc/man3/SSL_shutdown.pod
include/openssl/ssl.h.in
ssl/quic/quic_impl.c

index b1a5c94fd9bcd11d87768932cf8aef0ca13422d4..913b5559771ab4cdb9a13c903e921d081b42f5bc 100644 (file)
@@ -347,6 +347,25 @@ If an application calls SSL_shutdown_ex() with B<SSL_SHUTDOWN_FLAG_RAPID>, an
 application can subsequently change its mind about performing a rapid shutdown
 by making a subsequent call to SSL_shutdown_ex() without the flag set.
 
+=head2 Peer-Initiated Shutdown
+
+In some cases, an application may wish to wait for a shutdown initiated by the
+peer rather than triggered locally. To do this, call SSL_shutdown_ex() with
+I<SSL_SHUTDOWN_FLAG_WAIT_PEER> specified in I<flags>. In blocking mode, this
+waits until the peer initiates a shutdown or the connection otherwise becomes
+terminated for another reason. In nonblocking mode it exits immediately with
+either success or failure depending on whether a shutdown has occurred.
+
+If a locally initiated shutdown has already been triggered or the connection has
+started terminating for another reason, this flag has no effect.
+
+=head2 Nonblocking Mode
+
+SSL_shutdown() and SSL_shutdown_ex() block if the connection is configured in
+blocking mode. This may be overridden by specifying
+B<SSL_SHUTDOWN_FLAG_NO_BLOCK> in I<flags> when calling SSL_shutdown_ex(), which
+causes the call to operate as though in nonblocking mode.
+
 =head1 RETURN VALUES
 
 For both SSL_shutdown() and SSL_shutdown_ex() the following return values can occur:
index 9448974403be1ab9f71ec0e23b67b1c4ffd4e6d6..37d192f7558e19fff06955c764e0994b8bba7121 100644 (file)
@@ -2312,6 +2312,8 @@ typedef struct ssl_shutdown_ex_args_st {
 
 #define SSL_SHUTDOWN_FLAG_RAPID             (1U << 0)
 #define SSL_SHUTDOWN_FLAG_NO_STREAM_FLUSH   (1U << 1)
+#define SSL_SHUTDOWN_FLAG_NO_BLOCK          (1U << 2)
+#define SSL_SHUTDOWN_FLAG_WAIT_PEER         (1U << 3)
 
 __owur int SSL_shutdown_ex(SSL *ssl, uint64_t flags,
                            const SSL_SHUTDOWN_EX_ARGS *args,
index da4d179ccb453a1706929512f1f170cead55d24f..d4eabccd37474590b6bc421e13027862076c0c1d 100644 (file)
@@ -1175,6 +1175,12 @@ static int quic_shutdown_flush_wait(void *arg)
         || qc_shutdown_flush_finished(qc);
 }
 
+static int quic_shutdown_peer_wait(void *arg)
+{
+    QUIC_CONNECTION *qc = arg;
+    return ossl_quic_channel_is_term_any(qc->ch);
+}
+
 QUIC_TAKES_LOCK
 int ossl_quic_conn_shutdown(SSL *s, uint64_t flags,
                             const SSL_SHUTDOWN_EX_ARGS *args,
@@ -1183,6 +1189,8 @@ int ossl_quic_conn_shutdown(SSL *s, uint64_t flags,
     int ret;
     QCTX ctx;
     int stream_flush = ((flags & SSL_SHUTDOWN_FLAG_NO_STREAM_FLUSH) == 0);
+    int no_block = ((flags & SSL_SHUTDOWN_FLAG_NO_BLOCK) != 0);
+    int wait_peer = ((flags & SSL_SHUTDOWN_FLAG_WAIT_PEER) != 0);
 
     if (!expect_quic(s, &ctx))
         return -1;
@@ -1200,11 +1208,11 @@ int ossl_quic_conn_shutdown(SSL *s, uint64_t flags,
     }
 
     /* Phase 1: Stream Flushing */
-    if (stream_flush) {
+    if (!wait_peer && stream_flush) {
         qc_shutdown_flush_init(ctx.qc);
 
         if (!qc_shutdown_flush_finished(ctx.qc)) {
-            if (qc_blocking_mode(ctx.qc)) {
+            if (!no_block && qc_blocking_mode(ctx.qc)) {
                 ret = block_until_pred(ctx.qc, quic_shutdown_flush_wait, ctx.qc, 0);
                 if (ret < 1) {
                     ret = 0;
@@ -1222,6 +1230,35 @@ int ossl_quic_conn_shutdown(SSL *s, uint64_t flags,
     }
 
     /* Phase 2: Connection Closure */
+    if (wait_peer && !ossl_quic_channel_is_term_any(ctx.qc->ch)) {
+        if (!no_block && qc_blocking_mode(ctx.qc)) {
+            ret = block_until_pred(ctx.qc, quic_shutdown_peer_wait, ctx.qc, 0);
+            if (ret < 1) {
+                ret = 0;
+                goto err;
+            }
+        } else {
+            ossl_quic_reactor_tick(ossl_quic_channel_get_reactor(ctx.qc->ch), 0);
+        }
+
+        if (!ossl_quic_channel_is_term_any(ctx.qc->ch)) {
+            ret = 0; /* peer hasn't closed yet - still not done */
+            goto err;
+        }
+
+        /*
+         * We are at least terminating - go through the normal process of
+         * waiting until we are in the TERMINATED state.
+         */
+    }
+
+    /* Block mutation ops regardless of if we did stream flush. */
+    ctx.qc->shutting_down = 1;
+
+    /*
+     * This call is a no-op if we are already terminating, so it doesn't
+     * affect the wait_peer case.
+     */
     ossl_quic_channel_local_close(ctx.qc->ch,
                                   args != NULL ? args->quic_error_code : 0,
                                   args != NULL ? args->quic_reason : NULL);
@@ -1234,7 +1271,8 @@ int ossl_quic_conn_shutdown(SSL *s, uint64_t flags,
     }
 
     /* Phase 3: Terminating Wait Time */
-    if (qc_blocking_mode(ctx.qc) && (flags & SSL_SHUTDOWN_FLAG_RAPID) == 0) {
+    if (!no_block && qc_blocking_mode(ctx.qc)
+        && (flags & SSL_SHUTDOWN_FLAG_RAPID) == 0) {
         ret = block_until_pred(ctx.qc, quic_shutdown_wait, ctx.qc, 0);
         if (ret < 1) {
             ret = 0;