Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to gracefully shutdown a boost asio ssl client?

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:

  • the async_read_some() callback is executed with a 'short read' error code - kind of expected now
  • the async_shutdown() callback is executed with a decryption failed or bad record mac error code - this is unexpected

The server side does not report an error.

Thus my question how to properly shutdown a TLS client with boost asio.

like image 411
maxschlepzig Avatar asked Mar 22 '14 08:03

maxschlepzig


1 Answers

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)

Alternative

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();
}
like image 148
maxschlepzig Avatar answered Sep 21 '22 01:09

maxschlepzig