如何优雅地关闭 boost asio ssl 客户端?
客户端执行一些 ssl::stream
/ssl::stream
调用和一些点需要退出,即需要关闭连接.
调用 ssl::stream<tcp_socket>::lowest_layer().close()
有效,但是(正如预期的那样)服务器(一个 openssl s_server -state ...
命令)在关闭连接时报告错误.
查看 API 的正确方法似乎是调用 ssl::stream
.
现在基本上有两种情况需要关机:
1) 客户端在 async_read_some()
回调中并对来自服务器的退出"命令做出反应.从那里调用 async_shutdown()
会在关闭回调中产生短读"错误.
这令人惊讶,但在谷歌搜索之后这似乎是正常行为 - 人们似乎必须检查它是否是一个真正的错误或不是这样的:
//const boost::system::error_code &ecif (ec.category() == asio::error::get_ssl_category() &&ec.value() == ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ)) {//->不是真正的错误,只是正常的 TLS 关闭}
TLS 服务器似乎很高兴,不过 - 它报告:
完成关闭 SSL连接已关闭
2) async_read_some()
处于活动状态 - 但用户决定退出客户端(例如,通过来自 stdin 的命令).当从该上下文调用 async_shutdown()
时,会发生以下情况:
async_read_some()
回调执行时带有短读"错误代码 - 现在是预期的async_shutdown()
回调执行时带有解密失败或记录错误 mac 错误代码 - 这是意外的
服务端不报错.
因此我的问题是如何使用 boost asio 正确关闭 TLS 客户端.
解决方案解决第二个上下文中解密失败或错误记录 mac"错误代码的一种方法是:
a) 来自 stdin 处理程序调用:
ssl::stream::lowest_layer()::shutdown(tcp::socket::shutdown_receive)
b) 这导致 async_read_some()
回调以短读"错误"代码执行
c) 在那个错误"条件下的回调中 async_shutdown()
被调用:
//const boost::system::error_code &ecif (ec.category() == asio::error::get_ssl_category() &&ec.value() == ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ)) {//->不是真正的错误:do_ssl_async_shutdown();}
d) async_shutdown()
回调执行时带有一个短读"错误代码,我们最终从这里调用:
这些步骤导致连接关闭,客户端或服务器端没有任何奇怪的错误消息.
例如,当使用 openssl s_server -state ...
作为服务器时,它会报告 sutdown:
(最后一行是因为该命令接受新连接)
替代方案
代替lowest_layer()::shutdown(tcp::socket::shutdown_receive)
我们也可以调用
ssl::stream::lowest_layer()::cancel()
启动适当的关闭.它具有相同的效果,即它产生预定的 async_read_some()
回调的执行(但带有 operation_aborted
错误代码).因此,可以从那里调用 async_shutdown()
:
if (ec.value() == asio::error::operation_aborted) {cout<<"(不是真正的错误)
";do_async_ssl_shutdown();}
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.
解决方案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)
";
do_async_ssl_shutdown();
}
相关文章