The client does some ssl::stream<tcp_socket>::async_read_some()
/ssl::stream<tcp_socket>::async_write()
calls and at some point needs to exit, i.e. it needs to shutdown the connection.
Calling ssl::stream<tcp_socket>::lowest_layer().close()
works, but (as it is expected) the server (a openssl s_server -state ...
command) reports an error on closing the connection.
Looking at the API the right way seems to be to call ssl::stream<tcp_socket>::async_shutdown()
.
Now there are basically 2 situation where a shutdown is needed:
1) Client is in the async_read_some()
callback and reacts on a 'quit' command from the server. Calling from there async_shutdown()
yields a 'short read' error in the shutdown callback.
This is surprising but after googling around this seems to be normal behaviour - one seem to have to check if it is a real error or not like this:
// const boost::system::error_code &ec
if (ec.category() == asio::error::get_ssl_category() &&
ec.value() == ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ)) {
// -> not a real error, just a normal TLS shutdown
}
The TLS server seems to be happy, though - it reports:
DONE
shutting down SSL
CONNECTION CLOSED
2) A async_read_some()
is active - but a user decides to exit the client (e.g. via a command from stdin). When calling async_shutdown()
from that context following happens:
async_read_some()
callback is executed with a 'short read' error code - kind of expected nowasync_shutdown()
callback is executed with a decryption failed or bad record mac error code - this is unexpectedThe server side does not report an error.
Thus my question how to properly shutdown a TLS client with boost asio.
One way to resolve the 'decryption failed or bad record mac' error code from the 2nd context is:
a) from inside the stdin handler call:
ssl::stream<tcp_socket>::lowest_layer()::shutdown(tcp::socket::shutdown_receive)
b) this results in the async_read_some()
callback getting executed with a 'short read' 'error' code
c) in that callback under that 'error' condition async_shutdown()
is called:
// const boost::system::error_code &ec
if (ec.category() == asio::error::get_ssl_category() &&
ec.value() == ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ)) {
// -> not a real error:
do_ssl_async_shutdown();
}
d) the async_shutdown()
callback is executed with a 'short read' error code, from where we finally call:
ssl::stream::lowest_layer()::close()
These steps result in a connection shutdown without any weird error messages on the client or server side.
For example, when using openssl s_server -state ...
as server it reports on sutdown:
SSL3 alert read:warning:close notify DONE shutting down SSL CONNECTION CLOSED ACCEPT
(the last line is because the command accepts new connections)
Instead of lowest_layer()::shutdown(tcp::socket::shutdown_receive)
we can also call
ssl::stream<tcp_socket>::lowest_layer()::cancel()
to initiate a proper shutdown. It has the same effect, i.e. it yields the execution of the scheduled async_read_some()
callback (but with operation_aborted
error code). Thus, one can call async_shutdown()
from there:
if (ec.value() == asio::error::operation_aborted) {
cout << "(not really an error)\n";
do_async_ssl_shutdown();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With