Skip to content

Commit

Permalink
Update demos/tutorial to distinguish between stream and connection er…
Browse files Browse the repository at this point in the history
…rors

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 #21765)
  • Loading branch information
mattcaswell committed Aug 25, 2023
1 parent a855ee8 commit 02e36ed
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 28 deletions.
32 changes: 28 additions & 4 deletions demos/guide/quic-client-block.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/*
Expand Down
61 changes: 53 additions & 8 deletions demos/guide/quic-multi-stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/*
Expand Down Expand Up @@ -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;
}

/*
Expand Down
69 changes: 67 additions & 2 deletions doc/man7/ossl-guide-quic-client-block.pod
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
47 changes: 33 additions & 14 deletions doc/man7/ossl-guide-quic-multi-stream.pod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 02e36ed

Please sign in to comment.