Подтвердить что ты не робот

Как изящно закрыть клиент asio ssl?

Клиент выполняет несколько вызовов ssl::stream<tcp_socket>::async_read_some()/ssl::stream<tcp_socket>::async_write() и в какой-то момент должен выйти, т.е. ему нужно отключить соединение.

Вызов ssl::stream<tcp_socket>::lowest_layer().close() работает, но (как и ожидалось) сервер (команда openssl s_server -state ...) сообщает об ошибке при закрытии соединения.

Посмотрев на API, правильным способом является вызов ssl::stream<tcp_socket>::async_shutdown().

Теперь есть в основном 2 ситуации, когда требуется выключение:

1) Клиент находится в обратном вызове async_read_some() и реагирует на команду "quit" с сервера. Вызов оттуда async_shutdown() дает ошибку "короткого чтения" в обратном вызове выключения.

Это удивительно, но после того, как googling вокруг этого, похоже, является нормальным поведением - кажется, нужно проверить, является ли это реальной ошибкой или нет:

// 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
}

Сервер TLS, кажется, счастлив, хотя - он сообщает:

DONE
shutting down SSL
CONNECTION CLOSED

2) A async_read_some() активен, но пользователь решает выйти из клиента (например, с помощью команды из stdin). При вызове async_shutdown() из этого контекста происходит следующее:

  • обратный вызов async_read_some() выполняется с кодом ошибки "короткого чтения" - вид ожидаемого сейчас
  • Обратный вызов async_shutdown() выполняется с кодом ошибки дешифрования или неудачной записью mac - это неожиданно.

Серверная сторона не сообщает об ошибке.

Таким образом, мой вопрос, как правильно закрыть клиент TLS с boost asio.

4b9b3361

Ответ 1

Один из способов устранения кода ошибки с дешифровкой или неудачей записи из второго контекста:

a) внутри вызова обработчика stdin:

ssl::stream<tcp_socket>::lowest_layer()::shutdown(tcp::socket::shutdown_receive)

b) это приводит к выполнению обратного вызова async_read_some() с кодом ошибки "короткого чтения"

c) в этом обратном вызове в соответствии с условием "ошибка" async_shutdown() вызывается:

// 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) обратный вызов async_shutdown() выполняется с кодом ошибки "короткого чтения", откуда мы наконец вызываем:

ssl::stream::lowest_layer()::close()

Эти шаги приводят к отключению подключения без каких-либо странных сообщений об ошибках на стороне клиента или сервера.

Например, при использовании openssl s_server -state ... в качестве сервера он сообщает об отключении:

SSL3 alert read:warning:close notify
DONE
shutting down SSL
CONNECTION CLOSED
ACCEPT

(последняя строка состоит в том, что команда принимает новые соединения)

Alternative

Вместо lowest_layer()::shutdown(tcp::socket::shutdown_receive) мы также можем назвать

ssl::stream<tcp_socket>::lowest_layer()::cancel()

чтобы инициировать надлежащее завершение работы. Он имеет тот же эффект, т.е. Дает выполнение запланированного обратного вызова async_read_some() (но с кодом ошибки operation_aborted). Таким образом, оттуда можно называть async_shutdown():

if (ec.value() == asio::error::operation_aborted) {
  cout << "(not really an error)\n";
  do_async_ssl_shutdown();
}