Update demos/tutorial to distinguish between stream and connection errors
authorMatt Caswell <matt@openssl.org>
Mon, 21 Aug 2023 14:11:17 +0000 (15:11 +0100)
committerMatt Caswell <matt@openssl.org>
Fri, 25 Aug 2023 10:42:51 +0000 (11:42 +0100)
We can use SSL_get_stream_read_state() to distinguish these cases.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21765)

demos/guide/quic-client-block.c
demos/guide/quic-multi-stream.c
doc/man7/ossl-guide-quic-client-block.pod
doc/man7/ossl-guide-quic-multi-stream.pod

index be797707f1429be02ef1dd99c0acc709454ba552..54e52d5c28e0c29317425d882db711d0d5b06187 100644 (file)
@@ -251,13 +251,37 @@ int main(void)
      * QUIC terms this means that the peer has sent FIN on the stream to
      * indicate that no further data will be sent.
      */
-    if (SSL_get_error(ssl, 0) != SSL_ERROR_ZERO_RETURN) {
+    switch (SSL_get_error(ssl, 0)) {
+    case SSL_ERROR_ZERO_RETURN:
+        /* Normal completion of the stream */
+        break;
+
+    case SSL_ERROR_SSL:
         /*
-         * Some error occurred other than a graceful close down by the
-         * peer.
+         * Some stream fatal error occurred. This could be because of a stream
+         * reset - or some failure occurred on the underlying connection.
          */
+        switch (SSL_get_stream_read_state(ssl)) {
+        case SSL_STREAM_STATE_RESET_REMOTE:
+            printf("Stream reset occurred\n");
+            /* The stream has been reset but the connection is still healthy. */
+            break;
+
+        case SSL_STREAM_STATE_CONN_CLOSED:
+            printf("Connection closed\n");
+            /* Connection is already closed. Skip SSL_shutdown() */
+            goto end;
+
+        default:
+            printf("Unknown stream failure\n");
+            break;
+        }
+        break;
+
+    default:
+        /* Some other unexpected error occurred */
         printf ("Failed reading remaining data\n");
-        goto end;
+        break;
     }
 
     /*
index 7a40d61ad4529c614de3c38dac5be9197eb2c4b0..86dc6e3502c1c6a8f34904c0d20ecdadc4bd8455 100644 (file)
@@ -288,13 +288,37 @@ int main(void)
      * QUIC terms this means that the peer has sent FIN on the stream to
      * indicate that no further data will be sent.
      */
-    if (SSL_get_error(stream1, 0) != SSL_ERROR_ZERO_RETURN) {
+    switch (SSL_get_error(stream1, 0)) {
+    case SSL_ERROR_ZERO_RETURN:
+        /* Normal completion of the stream */
+        break;
+
+    case SSL_ERROR_SSL:
         /*
-         * Some error occurred other than a graceful close down by the
-         * peer.
+         * Some stream fatal error occurred. This could be because of a stream
+         * reset - or some failure occurred on the underlying connection.
          */
-        printf ("Failed reading remaining data from stream 1\n");
-        goto end;
+        switch (SSL_get_stream_read_state(stream1)) {
+        case SSL_STREAM_STATE_RESET_REMOTE:
+            printf("Stream reset occurred\n");
+            /* The stream has been reset but the connection is still healthy. */
+            break;
+
+        case SSL_STREAM_STATE_CONN_CLOSED:
+            printf("Connection closed\n");
+            /* Connection is already closed. Skip SSL_shutdown() */
+            goto end;
+
+        default:
+            printf("Unknown stream failure\n");
+            break;
+        }
+        break;
+
+    default:
+        /* Some other unexpected error occurred */
+        printf ("Failed reading remaining data\n");
+        break;
     }
 
     /*
@@ -325,9 +349,30 @@ int main(void)
     printf("\n");
 
     /* Check for errors on the stream */
-    if (SSL_get_error(stream3, 0) != SSL_ERROR_ZERO_RETURN) {
-        printf ("Failed reading remaining data from stream 3\n");
-        goto end;
+    switch (SSL_get_error(stream3, 0)) {
+    case SSL_ERROR_ZERO_RETURN:
+        /* Normal completion of the stream */
+        break;
+
+    case SSL_ERROR_SSL:
+        switch (SSL_get_stream_read_state(stream3)) {
+        case SSL_STREAM_STATE_RESET_REMOTE:
+            printf("Stream reset occurred\n");
+            break;
+
+        case SSL_STREAM_STATE_CONN_CLOSED:
+            printf("Connection closed\n");
+            goto end;
+
+        default:
+            printf("Unknown stream failure\n");
+            break;
+        }
+        break;
+
+    default:
+        printf ("Failed reading remaining data\n");
+        break;
     }
 
     /*
index 6ae193567f3fbf0f1bff157793ed39b32901eaca..595135c69668b18c2f780227fd9f110ed0c22e62 100644 (file)
@@ -241,8 +241,73 @@ client, so we won't repeat it here.
 
 We can also perform data transfer using a default QUIC stream that is
 automatically associated with the B<SSL> object for us. We can transmit data
-using L<SSL_write_ex(3)>, and receive data using L<SSL_read_ex(3)> in exactly
-the same way as for TLS. Again, we won't repeat it here.
+using L<SSL_write_ex(3)>, and receive data using L<SSL_read_ex(3)> in the same
+way as for TLS. The main difference is that we have to account for failures
+slightly differently. With QUIC the stream can be reset by the peer (which is
+fatal for that stream), but the underlying connection itself may still be
+healthy.
+
+    /*
+     * Get up to sizeof(buf) bytes of the response. We keep reading until the
+     * server closes the connection.
+     */
+    while (SSL_read_ex(ssl, buf, sizeof(buf), &readbytes)) {
+        /*
+        * OpenSSL does not guarantee that the returned data is a string or
+        * that it is NUL terminated so we use fwrite() to write the exact
+        * number of bytes that we read. The data could be non-printable or
+        * have NUL characters in the middle of it. For this simple example
+        * we're going to print it to stdout anyway.
+        */
+        fwrite(buf, 1, readbytes, stdout);
+    }
+    /* In case the response didn't finish with a newline we add one now */
+    printf("\n");
+
+    /*
+     * Check whether we finished the while loop above normally or as the
+     * result of an error. The 0 argument to SSL_get_error() is the return
+     * code we received from the SSL_read_ex() call. It must be 0 in order
+     * to get here. Normal completion is indicated by SSL_ERROR_ZERO_RETURN. In
+     * QUIC terms this means that the peer has sent FIN on the stream to
+     * indicate that no further data will be sent.
+     */
+    switch (SSL_get_error(ssl, 0)) {
+    case SSL_ERROR_ZERO_RETURN:
+        /* Normal completion of the stream */
+        break;
+
+    case SSL_ERROR_SSL:
+        /*
+         * Some stream fatal error occurred. This could be because of a stream
+         * reset - or some failure occurred on the underlying connection.
+         */
+        switch (SSL_get_stream_read_state(ssl)) {
+        case SSL_STREAM_STATE_RESET_REMOTE:
+            printf("Stream reset occurred\n");
+            /* The stream has been reset but the connection is still healthy. */
+            break;
+
+        case SSL_STREAM_STATE_CONN_CLOSED:
+            printf("Connection closed\n");
+            /* Connection is already closed. Skip SSL_shutdown() */
+            goto end;
+
+        default:
+            printf("Unknown stream failure\n");
+            break;
+        }
+        break;
+
+    default:
+        /* Some other unexpected error occurred */
+        printf ("Failed reading remaining data\n");
+        break;
+    }
+
+In the above code example you can see that B<SSL_ERROR_SSL> indicates a stream
+fatal error. We can use L<SSL_get_stream_read_state(3)> to determine whether the
+stream has been reset, or if some other fatal error has occurred.
 
 =head2 Shutting down the connection
 
index 9956fff0949ad4df12ec5c577cf208e0c32e3632..4291c30fa72ce3732c3a0889b5261badc4fe8ae2 100644 (file)
@@ -131,14 +131,6 @@ send more data on a stream after L<SSL_stream_conclude(3)> has been called.
 It is also possible to abandon a stream abnormally by calling
 L<SSL_stream_reset(3)>.
 
-=begin comment
-
-TODO(QUIC): What does SSL_get_error() return in the event that the
-peer has reset a stream? If SSL_ERROR_SSL how does an application distinguish
-between a stream reset and a connection level fatal error?
-
-=end comment
-
 Once a stream object is no longer needed it should be freed via a call to
 L<SSL_free(3)>. An application should not call L<SSL_shutdown(3)> on it since
 this is only meaningful for connection level B<SSL> objects. Freeing the stream
@@ -277,7 +269,10 @@ function to find out more details. Since this is a blocking application this
 will either return B<SSL_ERROR_SYSCALL> or B<SSL_ERROR_SSL> indicating a
 fundamental problem, or it will return B<SSL_ERROR_ZERO_RETURN> indicating that
 the stream is concluded and there will be no more data available to read from
-it.
+it. Care must be taken to distinguish between an error at the stream level (i.e.
+a stream reset) and an error at the connection level (i.e. a connection closed).
+The L<SSL_get_stream_read_state(3)> function can be used to distinguish between
+these different cases.
 
     /*
      * Check whether we finished the while loop above normally or as the
@@ -287,13 +282,37 @@ it.
      * QUIC terms this means that the peer has sent FIN on the stream to
      * indicate that no further data will be sent.
      */
-    if (SSL_get_error(stream1, 0) != SSL_ERROR_ZERO_RETURN) {
+    switch (SSL_get_error(ssl, 0)) {
+    case SSL_ERROR_ZERO_RETURN:
+        /* Normal completion of the stream */
+        break;
+
+    case SSL_ERROR_SSL:
         /*
-         * Some error occurred other than a graceful close down by the
-         * peer.
+         * Some stream fatal error occurred. This could be because of a stream
+         * reset - or some failure occurred on the underlying connection.
          */
-        printf ("Failed reading remaining data from stream 1\n");
-        goto end;
+        switch (SSL_get_stream_read_state(ssl)) {
+        case SSL_STREAM_STATE_RESET_REMOTE:
+            printf("Stream reset occurred\n");
+            /* The stream has been reset but the connection is still healthy. */
+            break;
+
+        case SSL_STREAM_STATE_CONN_CLOSED:
+            printf("Connection closed\n");
+            /* Connection is already closed. Skip SSL_shutdown() */
+            goto end;
+
+        default:
+            printf("Unknown stream failure\n");
+            break;
+        }
+        break;
+
+    default:
+        /* Some other unexpected error occurred */
+        printf ("Failed reading remaining data\n");
+        break;
     }
 
 =head2 Accepting an incoming stream