I try to write function to handle SSL shutdown. Below is part of the server code, where I use non-block mode.
int r = SSL_shutdown(ssl);
if (r == 1)
{
return Success;
}
// according to man/SSL_shutdown, 0 means "in progress"
if (r == 0)
{
switch ((r = SSL_get_error(ssl, r)))
{
case SSL_ERROR_WANT_READ:
std::cout << "shutdown want read" << std::endl;
...
return ...;
case SSL_ERROR_WANT_WRITE:
std::cout << "shutdown want write" << std::endl;
...
return ...;
default:
std::cout << r << " " << strerror(errno) << std::endl;
...
return ...;
}
}
This is part of the client code, where I use blocking mode. According to man, I should call it twice.
if (SSL_shutdown(ssl) == 0)
{
if (SSL_shutdown(ssl) != 1)
{
report_error_and_exit("SSL_shutdown");
}
}
In the server, the SSL_shutdown returns 0. According to man page, then SSL_get_error should return either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
Quote: https://www.openssl.org/docs/manmaster/man3/SSL_shutdown.html
If the underlying BIO is nonblocking and the shutdown process is not yet complete (for example, because a close_notify alert message has not yet been received from the peer, or because a close_notify alert message needs to be sent but would currently block), SSL_shutdown() returns 0 to indicate that the shutdown process is still ongoing; in this case, a call to SSL_get_error(3) will yield SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
However, I get print from default with r = 5 (SSL_ERROR_SYSCALL). In such case, the man says should check errno, but strerror returns "Success". Everything is so confusing.
OK, I think I can answer this now, because I found some better documentation (good old Linux):
https://linux.die.net/man/3/ssl_shutdown
So, quoting from there:
The shutdown procedure consists of 2 steps: the sending of the "close notify" shutdown alert and the reception of the peer's "close notify" shutdown alert.
According to the TLS standard, it is acceptable for an application to only send its shutdown alert and then close the underlying connection without waiting for the peer's response (this way resources can be saved, as the process can already terminate or serve another connection). When the underlying connection shall be used for more communications, the complete shutdown procedure (bidirectional "close notify" alerts) must be performed, so that the peers stay synchronized.
So SSL_shutdown is indeed a two-step process. It then goes on to say (commentary and minor clarifying edits mine):
When the application is the first party to send the "close notify" alert [as, I assume, is the case here],
SSL_shutdown()
will only send the alert and then set theSSL_SENT_SHUTDOWN
flag (so that the session is considered good and will be kept in cache).
SSL_shutdown()
will then return with0
. If a unidirectional shutdown is enough (i.e. the underlying connection shall be closed anyway), this first call toSSL_shutdown()
is sufficientIn order to complete the bidirectional shutdown handshake,
SSL_shutdown()
must be called again. The second call will makeSSL_shutdown()
wait for the peer's "close notify" shutdown alert. On success, the second call toSSL_shutdown()
will return with1
.
In other words, if you're planning to dispose of ssl
(in the code as posted), then calling SSL_shutdown
once is sufficient (and this is what I do). OTOH, if you plan to use ssl
again, you might have to call it twice.
There's some other bumph but what it boils down to is that if SSL_shutdown
returns 0
, and you want to reuse ssl
, then you have to call it again. If it returns 1
, you don't (regardless of your future plans for ssl
).
Finally, it says this:
If the underlying BIO is blocking,
SSL_shutdown()
will only return once the handshake step has been finished or an error occurred.
Which I take to mean that, for blocking sockets, you ever need to call it once. For non-blocking sockets, you need to take a bit more care. But, my reading of the docs tells me that the most robust way to code this (if you plan to reuse ssl
) would be:
int rc = SSL_shutdown (ssl);
if (rc == 0)
rc = SSL_shutdown (ssl);
...
Personally, I don't use non-blocking sockets with OpenSSL anyway, there are too many gotchas. Instead, I spin up a thread, but I can see for a server application that that might not be acceptable. And sorry I didn't read your question properly first time round, OP.
Happy to be corrected by wiser heads on any of the above. Thanks.