From: Francis Dupont Date: Sat, 24 Jan 2026 00:09:49 +0000 (+0100) Subject: [#4274] Checkpoint: finished RcpExchange X-Git-Tag: Kea-3.1.6~47 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d85a63c3074090c19d6020ace8c29a97d53e52a8;p=thirdparty%2Fkea.git [#4274] Checkpoint: finished RcpExchange --- diff --git a/src/hooks/dhcp/radius/client_exchange.cc b/src/hooks/dhcp/radius/client_exchange.cc index 4fbd8076b2..0f6a4c3c53 100644 --- a/src/hooks/dhcp/radius/client_exchange.cc +++ b/src/hooks/dhcp/radius/client_exchange.cc @@ -25,6 +25,7 @@ using namespace isc; using namespace isc::asiolink; using namespace isc::data; +using namespace isc::tcp; using namespace isc::util; using namespace std; using namespace std::chrono; @@ -55,17 +56,13 @@ exchangeRCtoText(const int rc) { } } -Exchange::Exchange(const asiolink::IOServicePtr io_service, - const MessagePtr& request, +Exchange::Exchange(const MessagePtr& request, unsigned maxretries, const Servers& servers, Handler handler) - : identifier_(""), io_service_(io_service), sync_(false), - rc_(ERROR_RC), request_(request), sent_(), received_(), + : identifier_(""), sync_(false), rc_(ERROR_RC), + request_(request), sent_(), received_(), maxretries_(maxretries), servers_(servers), handler_(handler) { - if (!io_service) { - isc_throw(BadValue, "null IO service"); - } if (!request) { isc_throw(BadValue, "null request"); } @@ -81,8 +78,8 @@ Exchange::Exchange(const asiolink::IOServicePtr io_service, Exchange::Exchange(const MessagePtr& request, unsigned maxretries, const Servers& servers) - : identifier_(""), io_service_(new IOService()), sync_(true), - rc_(ERROR_RC), request_(request), sent_(), received_(), + : identifier_(""), sync_(true), rc_(ERROR_RC), + request_(request), sent_(), received_(), maxretries_(maxretries), servers_(servers), handler_() { if (!request) { isc_throw(BadValue, "null request"); @@ -98,9 +95,15 @@ Exchange::create(const asiolink::IOServicePtr io_service, const MessagePtr& request, unsigned maxretries, const Servers& servers, - Handler handler) { - return (UdpExchangePtr(new UdpExchange(io_service, request, maxretries, - servers, handler))); + Handler handler, + RadiusProtocol protocol) { + if (protocol == PW_PROTO_UDP) { + return (UdpExchangePtr(new UdpExchange(io_service, request, maxretries, + servers, handler))); + } else { + return (TcpExchangePtr(new TcpExchange(request, maxretries, + servers, handler))); + } } ExchangePtr @@ -115,18 +118,22 @@ UdpExchange::UdpExchange(const asiolink::IOServicePtr io_service, unsigned maxretries, const Servers& servers, Handler handler) - : Exchange(io_service, request, maxretries, servers, handler), - started_(false), terminated_(false), + : Exchange(request, maxretries, servers, handler), + io_service_(io_service), started_(false), terminated_(false), start_time_(std::chrono::steady_clock().now()), socket_(), ep_(), timer_(), server_(), idx_(0), buffer_(), size_(0), retries_(0), postponed_(), mutex_(new std::mutex()) { + if (!io_service) { + isc_throw(BadValue, "null IO service"); + } } UdpExchange::UdpExchange(const MessagePtr& request, unsigned maxretries, const Servers& servers) : Exchange(request, maxretries, servers), + io_service_(new IOService()), started_(false), terminated_(false), start_time_(std::chrono::steady_clock().now()), socket_(), ep_(), timer_(), server_(), idx_(0), @@ -158,7 +165,6 @@ Exchange::createIdentifier() { rs << hex << setfill('0') << setw(8) << ri; identifier_ = rs.str(); } - void Exchange::logReplyMessages() const { if (!received_) { @@ -308,6 +314,11 @@ UdpExchange::buildRequest() { Exchange::buildRequest(server_, start_time_); } +void +TcpExchange::buildRequest() { + Exchange::buildRequest(server_, start_time_); +} + void UdpExchange::open() { if (RadiusImpl::shutdown_) { @@ -567,6 +578,87 @@ UdpExchange::sentHandler(UdpExchangePtr ex, ph::_2)); // size. } +void +Exchange::processResponse() { + // Decode message. + rc_ = OK_RC; + try { + // In order: + // - decode message. + // - verify that identifiers match. + // - verify that message codes match. + received_->decode(); + unsigned got = received_->getIdentifier(); + unsigned expected = sent_->getIdentifier(); + if (got != expected) { + LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_MISMATCH) + .arg(identifier_) + .arg(got) + .arg(expected); + rc_ = BADRESP_RC; + } else if (request_->getCode() == PW_ACCESS_REQUEST) { + if (received_->getCode() == PW_ACCESS_REJECT) { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_EXCHANGE_RECEIVED_ACCESS_REJECT) + .arg(identifier_); + rc_ = REJECT_RC; + } else if (received_->getCode() != PW_ACCESS_ACCEPT) { + LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_UNEXPECTED) + .arg(identifier_) + .arg(msgCodeToText(request_->getCode())) + .arg(msgCodeToText(received_->getCode())); + rc_ = BADRESP_RC; + } else { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_EXCHANGE_RECEIVED_ACCESS_ACCEPT) + .arg(identifier_); + } + } else if (request_->getCode() == PW_ACCOUNTING_REQUEST) { + if (received_->getCode() != PW_ACCOUNTING_RESPONSE) { + LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_UNEXPECTED) + .arg(identifier_) + .arg(msgCodeToText(request_->getCode())) + .arg(msgCodeToText(received_->getCode())); + rc_ = BADRESP_RC; + } else { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_EXCHANGE_RECEIVED_ACCOUNTING_RESPONSE) + .arg(identifier_); + } + } else if (request_->getCode() == PW_STATUS_SERVER) { + if (received_->getCode() == PW_ACCESS_ACCEPT) { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_EXCHANGE_RECEIVED_ACCESS_ACCEPT) + .arg(identifier_); + } else if (received_->getCode() == PW_ACCESS_REJECT) { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_EXCHANGE_RECEIVED_ACCESS_REJECT) + .arg(identifier_); + } else if (received_->getCode() == PW_ACCOUNTING_RESPONSE) { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_EXCHANGE_RECEIVED_ACCOUNTING_RESPONSE) + .arg(identifier_); + } else { + LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_UNEXPECTED) + .arg(identifier_) + .arg(msgCodeToText(request_->getCode())) + .arg(msgCodeToText(received_->getCode())); + rc_ = BADRESP_RC; + } + } + } catch (const Exception& exc) { + LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_BAD_RESPONSE) + .arg(identifier_) + .arg(exc.what()); + rc_ = BADRESP_RC; + } + + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_EXCHANGE_RECEIVED_RESPONSE) + .arg(identifier_) + .arg(exchangeRCtoText(rc_)); +} + void UdpExchange::receivedHandler(UdpExchangePtr ex, const boost::system::error_code ec, @@ -617,83 +709,7 @@ UdpExchange::receivedHandler(UdpExchangePtr ex, ex->received_.reset(new Message(ex->buffer_, ex->sent_->getAuth(), ex->server_->getSecret())); - // Decode message. - ex->rc_ = OK_RC; - try { - // In order: - // - decode message. - // - verify that identifiers match. - // - verify that message codes match. - ex->received_->decode(); - unsigned got = ex->received_->getIdentifier(); - unsigned expected = ex->sent_->getIdentifier(); - if (got != expected) { - LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_MISMATCH) - .arg(ex->identifier_) - .arg(got) - .arg(expected); - ex->rc_ = BADRESP_RC; - } else if (ex->request_->getCode() == PW_ACCESS_REQUEST) { - if (ex->received_->getCode() == PW_ACCESS_REJECT) { - LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, - RADIUS_EXCHANGE_RECEIVED_ACCESS_REJECT) - .arg(ex->identifier_); - ex->rc_ = REJECT_RC; - } else if (ex->received_->getCode() != PW_ACCESS_ACCEPT) { - LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_UNEXPECTED) - .arg(ex->identifier_) - .arg(msgCodeToText(ex->request_->getCode())) - .arg(msgCodeToText(ex->received_->getCode())); - ex->rc_ = BADRESP_RC; - } else { - LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, - RADIUS_EXCHANGE_RECEIVED_ACCESS_ACCEPT) - .arg(ex->identifier_); - } - } else if (ex->request_->getCode() == PW_ACCOUNTING_REQUEST) { - if (ex->received_->getCode() != PW_ACCOUNTING_RESPONSE) { - LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_UNEXPECTED) - .arg(ex->identifier_) - .arg(msgCodeToText(ex->request_->getCode())) - .arg(msgCodeToText(ex->received_->getCode())); - ex->rc_ = BADRESP_RC; - } else { - LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, - RADIUS_EXCHANGE_RECEIVED_ACCOUNTING_RESPONSE) - .arg(ex->identifier_); - } - } else if (ex->request_->getCode() == PW_STATUS_SERVER) { - if (ex->received_->getCode() == PW_ACCESS_ACCEPT) { - LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, - RADIUS_EXCHANGE_RECEIVED_ACCESS_ACCEPT) - .arg(ex->identifier_); - } else if (ex->received_->getCode() == PW_ACCESS_REJECT) { - LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, - RADIUS_EXCHANGE_RECEIVED_ACCESS_REJECT) - .arg(ex->identifier_); - } else if (ex->received_->getCode() == PW_ACCOUNTING_RESPONSE) { - LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, - RADIUS_EXCHANGE_RECEIVED_ACCOUNTING_RESPONSE) - .arg(ex->identifier_); - } else { - LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_UNEXPECTED) - .arg(ex->identifier_) - .arg(msgCodeToText(ex->request_->getCode())) - .arg(msgCodeToText(ex->received_->getCode())); - ex->rc_ = BADRESP_RC; - } - } - } catch (const Exception& exc) { - LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_BAD_RESPONSE) - .arg(ex->identifier_) - .arg(exc.what()); - ex->rc_ = BADRESP_RC; - } - - LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, - RADIUS_EXCHANGE_RECEIVED_RESPONSE) - .arg(ex->identifier_) - .arg(exchangeRCtoText(ex->rc_)); + ex->processResponse(); // If bad then retry, if not including reject it is done. if ((ex->rc_ != OK_RC) && (ex->rc_ != REJECT_RC)) { @@ -782,5 +798,141 @@ UdpExchange::timeoutHandler(UdpExchangePtr ex) { } } +TcpExchange::TcpExchange(const MessagePtr& request, + unsigned maxretries, + const Servers& servers, + Handler handler) + : Exchange(request, maxretries, servers, handler), + start_time_(std::chrono::steady_clock().now()), + server_(), resp_() { + server_ = servers_[0]; +} + +void +TcpExchange::start() { + if (RadiusImpl::shutdown_) { + shutdown(); + } + + if (!server_) { + isc_throw(Unexpected, "no server"); + } + + try { + // Reset error code. + rc_ = ERROR_RC; + + // Build to be send request message. + buildRequest(); + + // Build write data request. + WireDataPtr request(new WireData(sent_->getBuffer())); + + ///// Log. + + RadiusImpl::instance().tcp_client_->asyncSendRequest( + server_->getPeerAddress(), + server_->getPeerPort(), + server_->getTlsContext(), + request, + resp_, + true, + TcpExchange::CompleteCheck, + std::bind(&TcpExchange::RequestHandler, + shared_from_this(), + ph::_1, // error_code + ph::_2, // response + ph::_3), // error_msg + TcpClient::RequestTimeout(server_->getTimeout() * 1000)); + } catch (const Exception& exc) { + ///// Log. + rc_ = ERROR_RC; + // Call handler. + if (handler_) { + auto handler = handler_; + // Avoid to keep a circular reference. + handler_ = Handler(); + handler(shared_from_this()); + } + } +} + +void +TcpExchange::shutdown() { + handler_ = Handler(); +} + +void +TcpExchange::RequestHandler(TcpExchangePtr ex, + const boost::system::error_code& ec, + const WireDataPtr& response, + const string& error_msg) { + if (!ex) { + isc_throw(Unexpected, "null exchange in receivedHandler"); + } + + if (RadiusImpl::shutdown_) { + return; + } + + // Check error code. + if (ec) { + ///// log + if (ec == boost::asio::error::timed_out) { + ex->rc_ = TIMEOUT_RC; + } else { + ex->rc_ = ERROR_RC; + } + // Call handler. + if (ex->handler_) { + auto handler = ex->handler_; + // Avoid to keep a circular reference. + ex->handler_ = Handler(); + handler(ex); + } + return; + } + + //// log + const WireData& buffer = *response; + ex->received_.reset(new Message(buffer, ex->sent_->getAuth(), + ex->server_->getSecret())); + + ex->processResponse(); + + if ((ex->rc_ == OK_RC) || (ex->rc_ == REJECT_RC)) { + ex->logReplyMessages(); + } + // Call handler. + if (ex->handler_) { + auto handler = ex->handler_; + // Avoid to keep a circular reference. + ex->handler_ = Handler(); + handler(ex); + } +} + +int +TcpExchange::CompleteCheck(const WireDataPtr& response, string& error_msg) { + if (!response) { + error_msg = "null response"; + return (-1); + } + const WireData& buffer = *response; + if (buffer.size() < AUTH_HDR_LEN) { + return (0); + } + uint16_t length = static_cast(buffer[2]) << 8; + length |= static_cast(buffer[3]); + if (length > buffer.size()) { + return (0); + } else if (length == buffer.size()) { + return (1); + } else { + error_msg = "overflow"; + return (-2); + } +} + } // end of namespace isc::radius } // end of namespace isc diff --git a/src/hooks/dhcp/radius/client_exchange.h b/src/hooks/dhcp/radius/client_exchange.h index 4b7b2ab2b0..c8a656d7ad 100644 --- a/src/hooks/dhcp/radius/client_exchange.h +++ b/src/hooks/dhcp/radius/client_exchange.h @@ -79,15 +79,17 @@ public: /// @param maxretries maximum number of retries for a server. /// @param servers Servers. /// @param handler Termination handler. + /// @param protocol Protocol (default to UDP). static ExchangePtr create(const asiolink::IOServicePtr io_service, const MessagePtr& request, unsigned maxretries, const Servers& servers, - Handler handler); + Handler handler, + RadiusProtocol protocol = PW_PROTO_UDP); /// @brief Factory. /// - /// Sync version. + /// Sync version (UDP only). /// /// @param request request message to send. /// @param maxretries maximum number of retries for a server. @@ -136,18 +138,19 @@ public: /// @brief Shutdown. virtual void shutdown() = 0; + /// @brief Process response. + void processResponse(); + protected: /// @brief Constructor. /// /// Async version. /// - /// @param io_service Reference to the IO service. /// @param request request message to send. /// @param maxretries maximum number of retries for a server. /// @param servers Servers. /// @param handler Termination handler. - Exchange(const asiolink::IOServicePtr io_service, - const MessagePtr& request, + Exchange(const MessagePtr& request, unsigned maxretries, const Servers& servers, Handler handler); @@ -166,9 +169,6 @@ protected: /// @brief The identifier (random value in hexadecimal). std::string identifier_; - /// @brief IO service (argument for async or internal for sync). - asiolink::IOServicePtr io_service_; - /// @brief Sync / async flag. bool sync_; @@ -251,6 +251,9 @@ public: virtual void shutdown(); protected: + /// @brief IO service (argument for async or internal for sync). + asiolink::IOServicePtr io_service_; + /// @brief Started flag. bool started_; @@ -342,6 +345,73 @@ protected: void terminate(); }; +/// @brief RADIUS/TCP exchange (forward declaration). +class TcpExchange; + +/// @brief Type of shared pointers to RADIUS/TCP exchange object. +typedef boost::shared_ptr TcpExchangePtr; + +/// @brief RADIUS/TCP (or RADIUS/TLS) Exchange. +class TcpExchange : public Exchange, + public boost::enable_shared_from_this { +public: + /// @brief Constructor. + /// + /// Async version. + /// + /// @param request request message to send. + /// @param maxretries maximum number of retries for a server. + /// @param servers Servers. + /// @param handler Termination handler. + TcpExchange(const MessagePtr& request, + unsigned maxretries, + const Servers& servers, + Handler handler); + + /// @note: no sync version. + + /// @brief Destructor. + virtual ~TcpExchange() = default; + + /// @brief Start. + virtual void start(); + + /// @brief Shutdown. + virtual void shutdown(); + +protected: + /// @brief Start time. + std::chrono::steady_clock::time_point start_time_; + + /// @brief Current server. + ServerPtr server_; + + /// @brief Response wire data. + isc::tcp::WireDataPtr resp_; + + /// @brief Build request. + void buildRequest(); + + /// @brief Request handler. + /// + /// @param ex Point to the Exchange. + /// @param ec Boost error code. + /// @param response Pointer to response wire data. + /// @param error_msg Error message. + static void RequestHandler(TcpExchangePtr ex, + const boost::system::error_code& ec, + const isc::tcp::WireDataPtr& response, + const std::string& error_msg); + + /// @brief Complete check. + /// + /// @param response Pointer to response wire data. + /// @param error_msg Reference to the error message. + /// @return status (>0 complete, 0 incomplete, <0 error). + static int CompleteCheck(const isc::tcp::WireDataPtr& response, + std::string& error_msg); +}; + } // end of namespace isc::radius } // end of namespace isc diff --git a/src/hooks/dhcp/radius/client_message.h b/src/hooks/dhcp/radius/client_message.h index cd12e5af0c..9b36dcc248 100644 --- a/src/hooks/dhcp/radius/client_message.h +++ b/src/hooks/dhcp/radius/client_message.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include diff --git a/src/hooks/dhcp/radius/radius.cc b/src/hooks/dhcp/radius/radius.cc index 772c2e7f50..4b7ab7114f 100644 --- a/src/hooks/dhcp/radius/radius.cc +++ b/src/hooks/dhcp/radius/radius.cc @@ -27,6 +27,7 @@ using namespace isc::asiolink; using namespace isc::data; using namespace isc::db; using namespace isc::dhcp; +using namespace isc::tcp; using namespace isc::util; namespace isc { @@ -170,7 +171,8 @@ RadiusImpl::instancePtr() { } RadiusImpl::RadiusImpl() - : proto_(PW_PROTO_UDP), udp_client_(), common_(new RadiusTls()), + : proto_(PW_PROTO_UDP), udp_client_(), tcp_client_(), + common_(new RadiusTls()), auth_(new RadiusAccess()), acct_(new RadiusAccounting()), bindaddr_("*"), canonical_mac_address_(false), clientid_pop0_(false), clientid_printable_(false), @@ -231,6 +233,9 @@ void RadiusImpl::cleanup() { if (udp_client_) { udp_client_.reset(); } + if (tcp_client_) { + tcp_client_.reset(); + } io_context_.reset(new IOService()); if (getIOService()) { @@ -307,15 +312,33 @@ RadiusImpl::startServices() { if (multi_threaded) { // Schedule a start of the services. This ensures we begin after // the dust has settled and Kea MT mode has been firmly established. - io_service_->post([this, thread_pool_size]() { - udp_client_.reset(new UdpClient(io_service_, thread_pool_size)); + if (proto_ == PW_PROTO_UDP) { + io_service_->post([this, thread_pool_size]() { + udp_client_.reset(new UdpClient(io_service_, + thread_pool_size)); + + io_context_ = udp_client_->getThreadIOService(); + + udp_client_->start(); + }); + } else { + io_service_->post([this, multi_threaded, thread_pool_size]() { + tcp_client_.reset(new TcpClient(io_service_, + multi_threaded, + thread_pool_size, + true)); - io_context_ = udp_client_->getThreadIOService(); + io_context_ = tcp_client_->getThreadIOService(); - udp_client_->start(); - }); + tcp_client_->start(); + }); + } } else { - udp_client_.reset(new UdpClient(io_service_, 0)); + if (proto_ == PW_PROTO_UDP) { + udp_client_.reset(new UdpClient(io_service_, 0)); + } else { + tcp_client_.reset(new TcpClient(io_service_, false, 0)); + } } } diff --git a/src/hooks/dhcp/radius/radius.h b/src/hooks/dhcp/radius/radius.h index f455fd2b8a..2e3fd55fca 100644 --- a/src/hooks/dhcp/radius/radius.h +++ b/src/hooks/dhcp/radius/radius.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -270,6 +271,9 @@ public: /// @brief UDP client. UdpClientPtr udp_client_; + /// @brief TCP client. + isc::tcp::TcpClientPtr tcp_client_; + /// @brief Subnet ID to NAS port map. std::map remap_; diff --git a/src/hooks/dhcp/radius/tests/exchange_unittests.cc b/src/hooks/dhcp/radius/tests/exchange_unittests.cc index 8ea6eb1fcb..475adf79bd 100644 --- a/src/hooks/dhcp/radius/tests/exchange_unittests.cc +++ b/src/hooks/dhcp/radius/tests/exchange_unittests.cc @@ -34,13 +34,7 @@ TEST(TestExchange, async) { MessagePtr msg; Servers servers; - // No IO service. - EXPECT_THROW_MSG(Exchange::create(io_service, msg, 0, servers, - Exchange::Handler()), - BadValue, "null IO service"); - // No message. - io_service.reset(new IOService()); EXPECT_THROW_MSG(Exchange::create(io_service, msg, 0, servers, Exchange::Handler()), BadValue, "null request"); @@ -67,8 +61,13 @@ TEST(TestExchange, async) { Exchange::Handler()), BadValue, "null handler"); - // No error. + // No IO service. auto handler = [] (const ExchangePtr) { }; + EXPECT_THROW_MSG(Exchange::create(io_service, msg, 0, servers, handler), + BadValue, "null IO service"); + + // No error. + io_service.reset(new IOService()); ASSERT_NO_THROW_LOG(exchange = Exchange::create(io_service, msg, 0, servers, handler));