From: Hugo Landau Date: Wed, 23 Aug 2023 07:25:28 +0000 (+0100) Subject: QUIC APL: Support waiting for peer-initiated shutdown X-Git-Tag: openssl-3.2.0-alpha1~121 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=25a0c4b907b0dbef4f0e70bf35cd84c85aaee3ad;p=thirdparty%2Fopenssl.git QUIC APL: Support waiting for peer-initiated shutdown Reviewed-by: Tomas Mraz Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/21815) --- diff --git a/doc/man3/SSL_shutdown.pod b/doc/man3/SSL_shutdown.pod index b1a5c94fd9b..913b5559771 100644 --- a/doc/man3/SSL_shutdown.pod +++ b/doc/man3/SSL_shutdown.pod @@ -347,6 +347,25 @@ If an application calls SSL_shutdown_ex() with B, 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 specified in I. 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 in I 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: diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index 9448974403b..37d192f7558 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -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, diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c index da4d179ccb4..d4eabccd374 100644 --- a/ssl/quic/quic_impl.c +++ b/ssl/quic/quic_impl.c @@ -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;