From ee03167e43b942ca6daf076aac007b2ba134028c Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Sun, 21 Sep 2025 10:32:53 -0500 Subject: [PATCH] added handshake for dos protection --- CHANGELOG.md | 1 + .../include/comm/packet/common.hpp | 77 ++++++ .../include/comm/packet/packet.hpp | 2 +- .../include/comm/packet/packet_server.hpp | 7 +- .../librepertory/src/comm/packet/common.cpp | 191 ++++++++++++++ .../librepertory/src/comm/packet/packet.cpp | 6 +- .../src/comm/packet/packet_client.cpp | 241 ++---------------- .../src/comm/packet/packet_server.cpp | 70 +++-- 8 files changed, 325 insertions(+), 270 deletions(-) create mode 100644 repertory/librepertory/include/comm/packet/common.hpp create mode 100644 repertory/librepertory/src/comm/packet/common.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 1369c448..83d8987b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### BREAKING CHANGES * Remote mounts must be upgraded to v2.1.0+ to support new authentication scheme + * Protocol handshake added for DoS protection ### Issues diff --git a/repertory/librepertory/include/comm/packet/common.hpp b/repertory/librepertory/include/comm/packet/common.hpp new file mode 100644 index 00000000..5638ab58 --- /dev/null +++ b/repertory/librepertory/include/comm/packet/common.hpp @@ -0,0 +1,77 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef REPERTORY_INCLUDE_COMM_PACKET_COMMON_HPP_ +#define REPERTORY_INCLUDE_COMM_PACKET_COMMON_HPP_ + +#include "utils/common.hpp" + +namespace repertory::comm { +constexpr const std::uint8_t max_read_attempts{5U}; +constexpr const std::uint16_t packet_nonce_size{256U}; + +struct non_blocking_guard final { + boost::asio::ip::tcp::socket &sock; + bool non_blocking{}; + + non_blocking_guard(const non_blocking_guard &) = delete; + non_blocking_guard(non_blocking_guard &&) = delete; + + auto operator=(const non_blocking_guard &) -> non_blocking_guard & = delete; + auto operator=(non_blocking_guard &&) -> non_blocking_guard & = delete; + + explicit non_blocking_guard(boost::asio::ip::tcp::socket &sock_) + : sock(sock_), non_blocking(sock_.non_blocking()) { + boost::system::error_code err; + [[maybe_unused]] auto ret = sock_.non_blocking(true, err); + } + + ~non_blocking_guard() { + if (not sock.is_open()) { + return; + } + + boost::system::error_code err; + [[maybe_unused]] auto ret = sock.non_blocking(non_blocking, err); + } +}; + +[[nodiscard]] auto is_socket_still_alive(boost::asio::ip::tcp::socket &sock) + -> bool; + +void connect_with_deadline( + boost::asio::io_context &io_ctx, boost::asio::ip::tcp::socket &sock, + boost::asio::ip::basic_resolver::results_type + &endpoints, + std::chrono::milliseconds deadline); + +void read_exact_with_deadline(boost::asio::io_context &io_ctx, + boost::asio::ip::tcp::socket &sock, + boost::asio::mutable_buffer buf, + std::chrono::milliseconds deadline); + +void write_all_with_deadline(boost::asio::io_context &io_ctx, + boost::asio::ip::tcp::socket &sock, + boost::asio::mutable_buffer buf, + std::chrono::milliseconds deadline); +} // namespace repertory::comm + +#endif // REPERTORY_INCLUDE_COMM_PACKET_COMMON_HPP_ diff --git a/repertory/librepertory/include/comm/packet/packet.hpp b/repertory/librepertory/include/comm/packet/packet.hpp index 61b5bcbe..32e0ac50 100644 --- a/repertory/librepertory/include/comm/packet/packet.hpp +++ b/repertory/librepertory/include/comm/packet/packet.hpp @@ -200,7 +200,7 @@ public: void encode_top(remote::file_info val); - void encrypt(std::string_view token); + void encrypt(std::string_view token, bool include_size = true); [[nodiscard]] auto get_size() const -> std::uint32_t { return static_cast(buffer_.size()); diff --git a/repertory/librepertory/include/comm/packet/packet_server.hpp b/repertory/librepertory/include/comm/packet/packet_server.hpp index ff7a4a12..cb33c4a1 100644 --- a/repertory/librepertory/include/comm/packet/packet_server.hpp +++ b/repertory/librepertory/include/comm/packet/packet_server.hpp @@ -23,6 +23,7 @@ #define REPERTORY_INCLUDE_COMM_PACKET_PACKET_SERVER_HPP_ #include "comm/packet/client_pool.hpp" +#include "comm/packet/common.hpp" #include "utils/common.hpp" using namespace boost::asio; @@ -61,14 +62,16 @@ private: std::string client_id; std::string nonce; - void generate_nonce() { nonce = utils::generate_random_string(256U); } + void generate_nonce() { + nonce = utils::generate_random_string(comm::packet_nonce_size); + } }; private: std::string encryption_token_; closed_callback closed_; message_handler_callback message_handler_; - io_context io_context_; + mutable io_context io_context_; std::unique_ptr server_thread_; std::vector service_threads_; std::recursive_mutex connection_mutex_; diff --git a/repertory/librepertory/src/comm/packet/common.cpp b/repertory/librepertory/src/comm/packet/common.cpp new file mode 100644 index 00000000..07f5549b --- /dev/null +++ b/repertory/librepertory/src/comm/packet/common.cpp @@ -0,0 +1,191 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "comm/packet/common.hpp" + +#include "events/event_system.hpp" +#include "events/types/packet_client_timeout.hpp" + +namespace repertory::comm { +auto is_socket_still_alive(boost::asio::ip::tcp::socket &sock) -> bool { + if (not sock.is_open()) { + return false; + } + + non_blocking_guard guard{sock}; + + boost::system::error_code err{}; + std::array tmp{}; + auto available = sock.receive(boost::asio::buffer(tmp), + boost::asio::socket_base::message_peek, err); + + if (err == boost::asio::error::would_block || + err == boost::asio::error::try_again) { + return true; + } + + if (err == boost::asio::error::eof || + err == boost::asio::error::connection_reset || + err == boost::asio::error::operation_aborted || + err == boost::asio::error::not_connected || + err == boost::asio::error::bad_descriptor || + err == boost::asio::error::network_down) { + return false; + } + + if (not err && available == 0) { + return false; + } + + if (not err && available > 0) { + return true; + } + + return false; +} + +template +void run_with_deadline(boost::asio::io_context &io_ctx, + boost::asio::ip::tcp::socket &sock, op_t &&operation, + std::chrono::milliseconds deadline, + std::string_view event_name, + std::string_view function_name) { + deadline = std::max(deadline, std::chrono::milliseconds{250}); + + bool done = false; + bool timed_out = false; + + boost::asio::steady_timer timer{io_ctx}; + timer.expires_after(deadline); + timer.async_wait([&done, &sock, &timed_out](auto &&err_) { + if (not err_ && not done) { + timed_out = true; + sock.cancel(); + } + }); + + boost::system::error_code err{}; + operation([&done, &err](auto &&err_) { + err = err_; + done = true; + }); + + io_ctx.restart(); + while (not done) { + io_ctx.run_one(); + } + timer.cancel(); + + if (timed_out) { + repertory::event_system::instance().raise( + std::string(event_name), std::string(function_name)); + throw std::runtime_error(std::string(event_name) + " timed-out"); + } + + if (err) { + throw std::runtime_error(std::string(event_name) + " failed|err|" + + err.message()); + } +} + +void connect_with_deadline( + boost::asio::io_context &io_ctx, boost::asio::ip::tcp::socket &sock, + boost::asio::ip::basic_resolver::results_type + &endpoints, + std::chrono::milliseconds deadline) { + REPERTORY_USES_FUNCTION_NAME(); + + run_with_deadline( + io_ctx, sock, + [&sock, &endpoints](auto &&handler) { + boost::asio::async_connect( + sock, endpoints, [handler](auto &&err, auto &&) { handler(err); }); + }, + deadline, "connect", function_name); +} + +void read_exact_with_deadline(boost::asio::io_context &io_ctx, + boost::asio::ip::tcp::socket &sock, + boost::asio::mutable_buffer buf, + std::chrono::milliseconds deadline) { + REPERTORY_USES_FUNCTION_NAME(); + + auto *base = static_cast(buf.data()); + + std::size_t total = buf.size(); + std::size_t offset = 0U; + + while (offset < total) { + std::size_t bytes_read = 0U; + + run_with_deadline( + io_ctx, sock, + [&](auto &&handler) { + sock.async_read_some( + boost::asio::buffer(base + offset, total - offset), + [&bytes_read, handler](auto &&err, auto &&count) { + bytes_read = count; + handler(err); + }); + }, + deadline, "read", function_name); + + if (bytes_read == 0U) { + throw std::runtime_error("0 bytes read"); + } + + offset += bytes_read; + } +} + +void write_all_with_deadline(boost::asio::io_context &io_ctx, + boost::asio::ip::tcp::socket &sock, + boost::asio::mutable_buffer buf, + std::chrono::milliseconds deadline) { + REPERTORY_USES_FUNCTION_NAME(); + + auto *base = static_cast(buf.data()); + std::size_t total = buf.size(); + std::size_t offset = 0U; + + while (offset < total) { + std::size_t bytes_written = 0U; + + run_with_deadline( + io_ctx, sock, + [&](auto &&handler) { + sock.async_write_some( + boost::asio::buffer(base + offset, total - offset), + [&bytes_written, handler](auto &&err, auto &&count) { + bytes_written = count; + handler(err); + }); + }, + deadline, "write", function_name); + + if (bytes_written == 0U) { + throw std::runtime_error("0 bytes written"); + } + + offset += bytes_written; + } +} +} // namespace repertory::comm diff --git a/repertory/librepertory/src/comm/packet/packet.cpp b/repertory/librepertory/src/comm/packet/packet.cpp index 4ff12365..ad458184 100644 --- a/repertory/librepertory/src/comm/packet/packet.cpp +++ b/repertory/librepertory/src/comm/packet/packet.cpp @@ -518,14 +518,16 @@ void packet::encode_top(remote::file_info val) { encode_top(&val, sizeof(val), true); } -void packet::encrypt(std::string_view token) { +void packet::encrypt(std::string_view token, bool include_size) { REPERTORY_USES_FUNCTION_NAME(); try { data_buffer result; utils::encryption::encrypt_data(token, buffer_, result); buffer_ = std::move(result); - encode_top(static_cast(buffer_.size())); + if (include_size) { + encode_top(static_cast(buffer_.size())); + } } catch (const std::exception &e) { utils::error::raise_error(function_name, e, "exception occurred"); } diff --git a/repertory/librepertory/src/comm/packet/packet_client.cpp b/repertory/librepertory/src/comm/packet/packet_client.cpp index 24ee277a..412cc4e0 100644 --- a/repertory/librepertory/src/comm/packet/packet_client.cpp +++ b/repertory/librepertory/src/comm/packet/packet_client.cpp @@ -21,8 +21,7 @@ */ #include "comm/packet/packet_client.hpp" -#include "events/event_system.hpp" -#include "events/types/packet_client_timeout.hpp" +#include "comm/packet/common.hpp" #include "platform/platform.hpp" #include "types/repertory.hpp" #include "utils/collection.hpp" @@ -30,199 +29,7 @@ #include "utils/error_utils.hpp" #include "version.hpp" -namespace { -constexpr std::uint8_t max_attempts{5U}; - -struct non_blocking_guard final { - boost::asio::ip::tcp::socket &sock; - bool non_blocking{}; - - non_blocking_guard(const non_blocking_guard &) = delete; - non_blocking_guard(non_blocking_guard &&) = delete; - - auto operator=(const non_blocking_guard &) -> non_blocking_guard & = delete; - auto operator=(non_blocking_guard &&) -> non_blocking_guard & = delete; - - explicit non_blocking_guard(boost::asio::ip::tcp::socket &sock_) - : sock(sock_), non_blocking(sock_.non_blocking()) { - boost::system::error_code err; - [[maybe_unused]] auto ret = sock_.non_blocking(true, err); - } - - ~non_blocking_guard() { - if (not sock.is_open()) { - return; - } - - boost::system::error_code err; - [[maybe_unused]] auto ret = sock.non_blocking(non_blocking, err); - } -}; - -[[nodiscard]] auto is_socket_still_alive(boost::asio::ip::tcp::socket &sock) - -> bool { - if (not sock.is_open()) { - return false; - } - - non_blocking_guard guard{sock}; - - boost::system::error_code err{}; - std::array tmp{}; - auto available = sock.receive(boost::asio::buffer(tmp), - boost::asio::socket_base::message_peek, err); - - if (err == boost::asio::error::would_block || - err == boost::asio::error::try_again) { - return true; - } - - if (err == boost::asio::error::eof || - err == boost::asio::error::connection_reset || - err == boost::asio::error::operation_aborted || - err == boost::asio::error::not_connected || - err == boost::asio::error::bad_descriptor || - err == boost::asio::error::network_down) { - return false; - } - - if (not err && available == 0) { - return false; - } - - if (not err && available > 0) { - return true; - } - - return false; -} - -template -void run_with_deadline(boost::asio::io_context &io_ctx, - boost::asio::ip::tcp::socket &sock, op_t &&operation, - std::chrono::milliseconds deadline, - std::string_view event_name, - std::string_view function_name) { - deadline = std::max(deadline, std::chrono::milliseconds{250}); - - bool done = false; - bool timed_out = false; - - boost::asio::steady_timer timer{io_ctx}; - timer.expires_after(deadline); - timer.async_wait([&done, &sock, &timed_out](auto &&err_) { - if (not err_ && not done) { - timed_out = true; - sock.cancel(); - } - }); - - boost::system::error_code err{}; - operation([&done, &err](auto &&err_) { - err = err_; - done = true; - }); - - io_ctx.restart(); - while (not done) { - io_ctx.run_one(); - } - timer.cancel(); - - if (timed_out) { - repertory::event_system::instance().raise( - std::string(event_name), std::string(function_name)); - throw std::runtime_error(std::string(event_name) + " timed-out"); - } - - if (err) { - throw std::runtime_error(std::string(event_name) + " failed|err|" + - err.message()); - } -} - -void connect_with_deadline(boost::asio::io_context &io_ctx, - boost::asio::ip::tcp::socket &sock, - const auto &endpoints, - std::chrono::milliseconds deadline) { - REPERTORY_USES_FUNCTION_NAME(); - - run_with_deadline( - io_ctx, sock, - [&sock, &endpoints](auto &&handler) { - boost::asio::async_connect( - sock, endpoints, [handler](auto &&err, auto &&) { handler(err); }); - }, - deadline, "connect", function_name); -} - -void read_exact_with_deadline(boost::asio::io_context &io_ctx, - boost::asio::ip::tcp::socket &sock, - boost::asio::mutable_buffer buf, - std::chrono::milliseconds deadline) { - REPERTORY_USES_FUNCTION_NAME(); - - auto *base = static_cast(buf.data()); - - std::size_t total = buf.size(); - std::size_t offset = 0U; - - while (offset < total) { - std::size_t bytes_read = 0U; - - run_with_deadline( - io_ctx, sock, - [&](auto &&handler) { - sock.async_read_some( - boost::asio::buffer(base + offset, total - offset), - [&bytes_read, handler](auto &&err, auto &&count) { - bytes_read = count; - handler(err); - }); - }, - deadline, "read", function_name); - - if (bytes_read == 0U) { - throw std::runtime_error("0 bytes read"); - } - - offset += bytes_read; - } -} - -void write_all_with_deadline(boost::asio::io_context &io_ctx, - boost::asio::ip::tcp::socket &sock, - boost::asio::mutable_buffer buf, - std::chrono::milliseconds deadline) { - REPERTORY_USES_FUNCTION_NAME(); - - auto *base = static_cast(buf.data()); - std::size_t total = buf.size(); - std::size_t offset = 0U; - - while (offset < total) { - std::size_t bytes_written = 0U; - - run_with_deadline( - io_ctx, sock, - [&](auto &&handler) { - sock.async_write_some( - boost::asio::buffer(base + offset, total - offset), - [&bytes_written, handler](auto &&err, auto &&count) { - bytes_written = count; - handler(err); - }); - }, - deadline, "write", function_name); - - if (bytes_written == 0U) { - throw std::runtime_error("0 bytes written"); - } - - offset += bytes_written; - } -} -} // namespace +using namespace repertory::comm; namespace repertory { packet_client::packet_client(remote::remote_config cfg) @@ -318,38 +125,21 @@ auto packet_client::handshake(client &cli) const -> bool { data_buffer buffer; { packet tmp; - tmp.encode_top(cli.nonce); + tmp.encode_top(utils::generate_random_string(packet_nonce_size)); tmp.to_buffer(buffer); } - auto to_read{buffer.size()}; - std::uint32_t total_read{}; - while ((total_read < to_read) && cli.socket.is_open()) { - auto bytes_read = boost::asio::read( - cli.socket, - boost::asio::buffer(&buffer[total_read], buffer.size() - total_read)); - if (bytes_read <= 0) { - throw std::runtime_error("0 bytes read"); - } + read_exact_with_deadline(io_context_, cli.socket, + boost::asio::buffer(buffer), + std::chrono::milliseconds(cfg_.recv_timeout_ms)); + packet response(buffer); + response.encrypt(cfg_.encryption_token, false); + response.to_buffer(buffer); - total_read += static_cast(bytes_read); - } - - if (total_read == to_read) { - packet response(buffer); - response.encrypt(cfg_.encryption_token); - response.to_buffer(buffer); - - auto written = boost::asio::write( - cli.socket, boost::asio::buffer(boost::asio::buffer(buffer))); - if (written == to_read) { - return true; - } - - throw std::runtime_error("failed to send handshake"); - } - - throw std::runtime_error("failed to read handshake"); + write_all_with_deadline(io_context_, cli.socket, + boost::asio::buffer(buffer), + std::chrono::milliseconds(cfg_.send_timeout_ms)); + return true; } catch (const std::exception &e) { repertory::utils::error::raise_error(function_name, e, "handlshake failed"); } @@ -427,7 +217,8 @@ auto packet_client::send(std::string_view method, packet &request, request.encode_top(std::string{project_get_version()}); for (std::uint8_t retry = 1U; - allow_connections_ && not success && (retry <= max_attempts); ++retry) { + allow_connections_ && not success && (retry <= max_read_attempts); + ++retry) { auto current_client = get_client(); if (current_client) { try { @@ -456,7 +247,7 @@ auto packet_client::send(std::string_view method, packet &request, utils::error::raise_error(function_name, e, "send failed"); close(*current_client); - if (allow_connections_ && (retry < max_attempts)) { + if (allow_connections_ && (retry < max_read_attempts)) { std::this_thread::sleep_for(1s); } } diff --git a/repertory/librepertory/src/comm/packet/packet_server.cpp b/repertory/librepertory/src/comm/packet/packet_server.cpp index 5badcab0..8ecb0a53 100644 --- a/repertory/librepertory/src/comm/packet/packet_server.cpp +++ b/repertory/librepertory/src/comm/packet/packet_server.cpp @@ -21,6 +21,7 @@ */ #include "comm/packet/packet_server.hpp" +#include "comm/packet/common.hpp" #include "comm/packet/packet.hpp" #include "events/event_system.hpp" #include "events/types/service_start_begin.hpp" @@ -31,9 +32,10 @@ #include "types/repertory.hpp" #include "utils/error_utils.hpp" -namespace repertory { +using namespace repertory::comm; using std::thread; +namespace repertory { packet_server::packet_server(std::uint16_t port, std::string token, std::uint8_t pool_size, closed_callback closed, message_handler_callback message_handler) @@ -89,49 +91,30 @@ auto packet_server::handshake(std::shared_ptr conn) const -> bool { packet request; request.encode_top(conn->nonce); request.to_buffer(buffer); - auto to_read{buffer.size()}; + auto to_read{buffer.size() + utils::encryption::encryption_header_size}; - auto written = boost::asio::write( - conn->socket, boost::asio::buffer(boost::asio::buffer(buffer))); - if (written == to_read) { - conn->buffer.resize(to_read); + write_all_with_deadline(io_context_, conn->socket, + boost::asio::buffer(buffer), + std::chrono::milliseconds(3000U)); - std::uint32_t total_read{}; - while ((total_read < to_read) && conn->socket.is_open()) { - auto bytes_read = boost::asio::read( - conn->socket, - boost::asio::buffer(&conn->buffer[total_read], - conn->buffer.size() - total_read)); - if (bytes_read <= 0) { - throw std::runtime_error("0 bytes read"); + conn->buffer.resize(to_read); + read_exact_with_deadline(io_context_, conn->socket, + boost::asio::buffer(conn->buffer), + std::chrono::milliseconds(3000U)); + packet response(conn->buffer); + if (response.decrypt(encryption_token_) == 0) { + std::string nonce; + if (response.decode(nonce) == 0) { + if (nonce == conn->nonce) { + conn->generate_nonce(); + return true; } - - total_read += static_cast(bytes_read); } - if (total_read == to_read) { - packet response(conn->buffer); - if (response.decrypt(encryption_token_) == 0) { - std::string nonce; - if (response.decode(nonce) == 0) { - if (nonce == conn->nonce) { - conn->generate_nonce(); - return true; - } - - throw std::runtime_error("invalid nonce"); - } - - throw std::runtime_error("invalid nonce"); - } - - throw std::runtime_error("decryption failed"); - } - - throw std::runtime_error("invalid handshake"); + throw std::runtime_error("invalid nonce"); } - throw std::runtime_error("failed to send handshake"); + throw std::runtime_error("decryption failed"); } catch (const std::exception &e) { repertory::utils::error::raise_error(function_name, e, "handlshake failed"); } @@ -169,10 +152,16 @@ void packet_server::initialize(const uint16_t &port, uint8_t pool_size) { } void packet_server::listen_for_connection(tcp::acceptor &acceptor) { + REPERTORY_USES_FUNCTION_NAME(); + auto conn = std::make_shared(io_context_, acceptor); acceptor.async_accept(conn->socket, [this, conn](auto &&err) { - on_accept(conn, std::forward(err)); + try { + on_accept(conn, std::forward(err)); + } catch (const std::exception &e) { + utils::error::raise_error(function_name, e, "exception occurred"); + } }); } @@ -331,9 +320,10 @@ void packet_server::send_response(std::shared_ptr conn, if (err) { remove_client(*conn); utils::error::raise_error(function_name, err.message()); - } else { - read_header(conn); + return; } + + read_header(conn); }); } } // namespace repertory