From: Francis Dupont Date: Mon, 29 Dec 2025 00:15:36 +0000 (+0100) Subject: [#4282] Added Radius*Status classes X-Git-Tag: Kea-3.1.5~46 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b08ecf37cdd53b0ca13f93162f298cca0a9f918e;p=thirdparty%2Fkea.git [#4282] Added Radius*Status classes --- diff --git a/src/hooks/dhcp/radius/meson.build b/src/hooks/dhcp/radius/meson.build index 4440f6394e..86fb34a148 100644 --- a/src/hooks/dhcp/radius/meson.build +++ b/src/hooks/dhcp/radius/meson.build @@ -23,6 +23,7 @@ dhcp_radius_lib = shared_library( 'radius_parsers.cc', 'radius_request.cc', 'radius_service.cc', + 'radius_status.cc', 'radius_utils.cc', 'version.cc', cpp_args: [f'-DDICTIONARY="@SYSCONFDIR_INSTALLED@/kea/radius/dictionary"'], diff --git a/src/hooks/dhcp/radius/radius_accounting.cc b/src/hooks/dhcp/radius/radius_accounting.cc index 30eb07e167..71b9dcff3c 100644 --- a/src/hooks/dhcp/radius/radius_accounting.cc +++ b/src/hooks/dhcp/radius/radius_accounting.cc @@ -67,8 +67,8 @@ RadiusAcctHandler::RadiusAcctHandler(RadiusAcctEnv env, const CallbackAcct& callback) : env_(env), acct_() { acct_.reset(new RadiusAsyncAcct(env_.subnet_id_, env_.send_attrs_, callback)); - RadiusImpl::instance().registerExchange(acct_->getExchange()); MultiThreadingLock lock(mutex_); + RadiusImpl::instance().registerExchange(acct_->getExchange()); ++counter_; } diff --git a/src/hooks/dhcp/radius/radius_messages.cc b/src/hooks/dhcp/radius/radius_messages.cc index a04a634358..e89e9d9dc0 100644 --- a/src/hooks/dhcp/radius/radius_messages.cc +++ b/src/hooks/dhcp/radius/radius_messages.cc @@ -28,6 +28,9 @@ extern const isc::log::MessageID RADIUS_ACCOUNTING_ASYNC_SUCCEED = "RADIUS_ACCOU extern const isc::log::MessageID RADIUS_ACCOUNTING_ERROR = "RADIUS_ACCOUNTING_ERROR"; extern const isc::log::MessageID RADIUS_ACCOUNTING_HISTORY_UPDATE_FAILED = "RADIUS_ACCOUNTING_HISTORY_UPDATE_FAILED"; extern const isc::log::MessageID RADIUS_ACCOUNTING_NO_HISTORY = "RADIUS_ACCOUNTING_NO_HISTORY"; +extern const isc::log::MessageID RADIUS_ACCOUNTING_STATUS = "RADIUS_ACCOUNTING_STATUS"; +extern const isc::log::MessageID RADIUS_ACCOUNTING_STATUS_FAILED = "RADIUS_ACCOUNTING_STATUS_FAILED"; +extern const isc::log::MessageID RADIUS_ACCOUNTING_STATUS_SUCCEED = "RADIUS_ACCOUNTING_STATUS_SUCCEED"; extern const isc::log::MessageID RADIUS_ACCOUNTING_SYNC = "RADIUS_ACCOUNTING_SYNC"; extern const isc::log::MessageID RADIUS_ACCOUNTING_SYNC_FAILED = "RADIUS_ACCOUNTING_SYNC_FAILED"; extern const isc::log::MessageID RADIUS_ACCOUNTING_SYNC_SUCCEED = "RADIUS_ACCOUNTING_SYNC_SUCCEED"; @@ -35,6 +38,9 @@ extern const isc::log::MessageID RADIUS_AUTHENTICATION_ASYNC = "RADIUS_AUTHENTIC extern const isc::log::MessageID RADIUS_AUTHENTICATION_ASYNC_ACCEPTED = "RADIUS_AUTHENTICATION_ASYNC_ACCEPTED"; extern const isc::log::MessageID RADIUS_AUTHENTICATION_ASYNC_FAILED = "RADIUS_AUTHENTICATION_ASYNC_FAILED"; extern const isc::log::MessageID RADIUS_AUTHENTICATION_ASYNC_REJECTED = "RADIUS_AUTHENTICATION_ASYNC_REJECTED"; +extern const isc::log::MessageID RADIUS_AUTHENTICATION_STATUS = "RADIUS_AUTHENTICATION_STATUS"; +extern const isc::log::MessageID RADIUS_AUTHENTICATION_STATUS_FAILED = "RADIUS_AUTHENTICATION_STATUS_FAILED"; +extern const isc::log::MessageID RADIUS_AUTHENTICATION_STATUS_SUCCEED = "RADIUS_AUTHENTICATION_STATUS_SUCCEED"; extern const isc::log::MessageID RADIUS_AUTHENTICATION_SYNC = "RADIUS_AUTHENTICATION_SYNC"; extern const isc::log::MessageID RADIUS_AUTHENTICATION_SYNC_ACCEPTED = "RADIUS_AUTHENTICATION_SYNC_ACCEPTED"; extern const isc::log::MessageID RADIUS_AUTHENTICATION_SYNC_FAILED = "RADIUS_AUTHENTICATION_SYNC_FAILED"; @@ -117,6 +123,9 @@ const char* values[] = { "RADIUS_ACCOUNTING_ERROR", "Accounting-Request failed for %1 on event %2 (%3) failed with %4 (%5)", "RADIUS_ACCOUNTING_HISTORY_UPDATE_FAILED", "failed to insert a record for %1 in the history container", "RADIUS_ACCOUNTING_NO_HISTORY", "failed to find the date the lease for %1 was created", + "RADIUS_ACCOUNTING_STATUS", "send Status-Server with %1", + "RADIUS_ACCOUNTING_STATUS_FAILED", "Status-Server failed: return code %1 (%2)", + "RADIUS_ACCOUNTING_STATUS_SUCCEED", "received valid response to Status-Server", "RADIUS_ACCOUNTING_SYNC", "Synchronous send Accounting-Request for NAS port %1 with %2", "RADIUS_ACCOUNTING_SYNC_FAILED", "Synchronous Accounting-Request failed: return code %1 (%2)", "RADIUS_ACCOUNTING_SYNC_SUCCEED", "received valid Accounting-Response (synchronously)", @@ -124,6 +133,9 @@ const char* values[] = { "RADIUS_AUTHENTICATION_ASYNC_ACCEPTED", "received valid Access-Accept with %1", "RADIUS_AUTHENTICATION_ASYNC_FAILED", "Access-Request failed: return code %1 (%2)", "RADIUS_AUTHENTICATION_ASYNC_REJECTED", "received valid Access-Reject with %1", + "RADIUS_AUTHENTICATION_STATUS", "send Status-Server with %1", + "RADIUS_AUTHENTICATION_STATUS_FAILED", "Status-Server failed: return code %1 (%2)", + "RADIUS_AUTHENTICATION_STATUS_SUCCEED", "received valid response to Status-Server", "RADIUS_AUTHENTICATION_SYNC", "send Access-Request for NAS port %1 with %2", "RADIUS_AUTHENTICATION_SYNC_ACCEPTED", "received valid Access-Accept with %1", "RADIUS_AUTHENTICATION_SYNC_FAILED", "Access-Request failed: return code %1 (%2)", diff --git a/src/hooks/dhcp/radius/radius_messages.h b/src/hooks/dhcp/radius/radius_messages.h index 9a65758bf0..c520fc2b61 100644 --- a/src/hooks/dhcp/radius/radius_messages.h +++ b/src/hooks/dhcp/radius/radius_messages.h @@ -29,6 +29,9 @@ extern const isc::log::MessageID RADIUS_ACCOUNTING_ASYNC_SUCCEED; extern const isc::log::MessageID RADIUS_ACCOUNTING_ERROR; extern const isc::log::MessageID RADIUS_ACCOUNTING_HISTORY_UPDATE_FAILED; extern const isc::log::MessageID RADIUS_ACCOUNTING_NO_HISTORY; +extern const isc::log::MessageID RADIUS_ACCOUNTING_STATUS; +extern const isc::log::MessageID RADIUS_ACCOUNTING_STATUS_FAILED; +extern const isc::log::MessageID RADIUS_ACCOUNTING_STATUS_SUCCEED; extern const isc::log::MessageID RADIUS_ACCOUNTING_SYNC; extern const isc::log::MessageID RADIUS_ACCOUNTING_SYNC_FAILED; extern const isc::log::MessageID RADIUS_ACCOUNTING_SYNC_SUCCEED; @@ -36,6 +39,9 @@ extern const isc::log::MessageID RADIUS_AUTHENTICATION_ASYNC; extern const isc::log::MessageID RADIUS_AUTHENTICATION_ASYNC_ACCEPTED; extern const isc::log::MessageID RADIUS_AUTHENTICATION_ASYNC_FAILED; extern const isc::log::MessageID RADIUS_AUTHENTICATION_ASYNC_REJECTED; +extern const isc::log::MessageID RADIUS_AUTHENTICATION_STATUS; +extern const isc::log::MessageID RADIUS_AUTHENTICATION_STATUS_FAILED; +extern const isc::log::MessageID RADIUS_AUTHENTICATION_STATUS_SUCCEED; extern const isc::log::MessageID RADIUS_AUTHENTICATION_SYNC; extern const isc::log::MessageID RADIUS_AUTHENTICATION_SYNC_ACCEPTED; extern const isc::log::MessageID RADIUS_AUTHENTICATION_SYNC_FAILED; diff --git a/src/hooks/dhcp/radius/radius_messages.mes b/src/hooks/dhcp/radius/radius_messages.mes index 042981a95d..bc24a7eb7e 100644 --- a/src/hooks/dhcp/radius/radius_messages.mes +++ b/src/hooks/dhcp/radius/radius_messages.mes @@ -105,6 +105,21 @@ This debug message is issued when an address was not found in create timestamp aka history container. This should lead to a accounting session without a start status message. +% RADIUS_ACCOUNTING_STATUS send Status-Server with %1 +Logged at debug log level 40. +This debug message is issued when starting to send a Status-Server message +to accounting servers. The message attributes are logged. + +% RADIUS_ACCOUNTING_STATUS_FAILED Status-Server failed: return code %1 (%2) +Logged at debug log level 40 +This debug message is issued when no valid response to Status-Server message +was received from accounting servers. + +% RADIUS_ACCOUNTING_STATUS_SUCCEED received valid response to Status-Server +Logged at debug log level 40. +This debug message indicates that a valid response to Status-Server message +was received from accounting servers. + % RADIUS_ACCOUNTING_SYNC Synchronous send Accounting-Request for NAS port %1 with %2 Logged at debug log level 40. This debug message is issued when starting to send an @@ -141,6 +156,21 @@ Logged at debug log level 40. This debug message indicates that a valid Access-Reject message was received. Attributes from the message are logged. +% RADIUS_AUTHENTICATION_STATUS send Status-Server with %1 +Logged at debug log level 40. +This debug message is issued when starting to send a Status-Server message +to access servers. The message attributes are logged. + +% RADIUS_AUTHENTICATION_STATUS_FAILED Status-Server failed: return code %1 (%2) +Logged at debug log level 40 +This debug message is issued when no valid response to Status-Server message +was received from access servers. + +% RADIUS_AUTHENTICATION_STATUS_SUCCEED received valid response to Status-Server +Logged at debug log level 40. +This debug message indicates that a valid response to Status-Server message +was received from access servers. + % RADIUS_AUTHENTICATION_SYNC send Access-Request for NAS port %1 with %2 Logged at debug log level 40. This debug message is issued when starting to send an Access-Request diff --git a/src/hooks/dhcp/radius/radius_request.cc b/src/hooks/dhcp/radius/radius_request.cc index 692c16dad7..8eec4bf635 100644 --- a/src/hooks/dhcp/radius/radius_request.cc +++ b/src/hooks/dhcp/radius/radius_request.cc @@ -131,14 +131,15 @@ RadiusAsyncAuth::start() { void RadiusAsyncAuth::invokeCallback(const CallbackAuth& callback, const ExchangePtr exchange) { - int result = ERROR_RC; + // Should no happen... + if (!exchange) { + return; + } + int result = exchange->getRC(); AttributesPtr recv_attrs; - if (exchange) { - result = exchange->getRC(); - MessagePtr response = exchange->getResponse(); - if (response) { - recv_attrs = response->getAttributes(); - } + MessagePtr response = exchange->getResponse(); + if (response) { + recv_attrs = response->getAttributes(); } if (result == OK_RC) { LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, @@ -223,10 +224,11 @@ RadiusAsyncAcct::start() { void RadiusAsyncAcct::invokeCallback(const CallbackAcct& callback, const ExchangePtr exchange) { - int result = ERROR_RC; - if (exchange) { - result = exchange->getRC(); + // Should not happen... + if (!exchange) { + return; } + int result = exchange->getRC(); if (result == OK_RC) { LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, RADIUS_ACCOUNTING_ASYNC_SUCCEED); diff --git a/src/hooks/dhcp/radius/radius_status.cc b/src/hooks/dhcp/radius/radius_status.cc new file mode 100644 index 0000000000..36a7922e7f --- /dev/null +++ b/src/hooks/dhcp/radius/radius_status.cc @@ -0,0 +1,145 @@ +// Copyright (C) 2020-2025 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include +#include + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::radius; +using namespace std; +namespace ph = std::placeholders; + +namespace isc { +namespace radius { + +RadiusAuthStatus::RadiusAuthStatus(const AttributesPtr& send_attrs, + const CallbackStatus& handler) + : RadiusStatus() { + AttributesPtr attrs; + if (send_attrs) { + attrs.reset(new Attributes(*send_attrs)); + } else { + attrs.reset(new Attributes()); + } + MessagePtr request(new Message(PW_STATUS_SERVER, 0, vector(), + "to-be-set", attrs)); + unsigned maxretries = RadiusImpl::instance().retries_; + Servers servers = RadiusImpl::instance().auth_->servers_; + exchange_.reset(new Exchange(RadiusImpl::instance().getIOContext(), + request, maxretries, servers, + std::bind(&RadiusAuthStatus::invokeCallback, + handler, ph::_1))); +} + +void +RadiusAuthStatus::start() { + AttributesPtr send_attrs; + MessagePtr request = exchange_->getRequest(); + if (request) { + send_attrs = request->getAttributes(); + } + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, RADIUS_AUTHENTICATION_STATUS) + .arg(send_attrs ? send_attrs->toText() : "no attributes"); + + RadiusStatus::start(); +} + +void +RadiusAuthStatus::invokeCallback(const CallbackAcct& callback, + const ExchangePtr exchange) { + // Should not happen... + if (!exchange) { + return; + } + int result = exchange->getRC(); + if (result == OK_RC) { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_AUTHENTICATION_STATUS_SUCCEED); + } else { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_AUTHENTICATION_STATUS_FAILED) + .arg(result) + .arg(exchangeRCtoText(result)); + } + + if (callback) { + try { + callback(result); + } catch (...) { + } + } + exchange->shutdown(); + RadiusImpl::instance().unregisterExchange(exchange); +} + +RadiusAcctStatus::RadiusAcctStatus(const AttributesPtr& send_attrs, + const CallbackStatus& handler) + : RadiusStatus() { + AttributesPtr attrs; + if (send_attrs) { + attrs.reset(new Attributes(*send_attrs)); + } else { + attrs.reset(new Attributes()); + } + MessagePtr request(new Message(PW_STATUS_SERVER, 0, vector(), + "to-be-set", attrs)); + unsigned maxretries = RadiusImpl::instance().retries_; + Servers servers = RadiusImpl::instance().acct_->servers_; + exchange_.reset(new Exchange(RadiusImpl::instance().getIOContext(), + request, maxretries, servers, + std::bind(&RadiusAcctStatus::invokeCallback, + handler, ph::_1))); +} + +void +RadiusAcctStatus::start() { + AttributesPtr send_attrs; + MessagePtr request = exchange_->getRequest(); + if (request) { + send_attrs = request->getAttributes(); + } + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, RADIUS_ACCOUNTING_STATUS) + .arg(send_attrs ? send_attrs->toText() : "no attributes"); + + RadiusStatus::start(); +} + +void +RadiusAcctStatus::invokeCallback(const CallbackAcct& callback, + const ExchangePtr exchange) { + // Should not happen... + if (!exchange) { + return; + } + int result = exchange->getRC(); + if (result == OK_RC) { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_ACCOUNTING_STATUS_SUCCEED); + } else { + LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, + RADIUS_ACCOUNTING_STATUS_FAILED) + .arg(result) + .arg(exchangeRCtoText(result)); + } + + if (callback) { + try { + callback(result); + } catch (...) { + } + } + exchange->shutdown(); + RadiusImpl::instance().unregisterExchange(exchange); +} + +} // end of namespace radius +} // end of namespace isc diff --git a/src/hooks/dhcp/radius/radius_status.h b/src/hooks/dhcp/radius/radius_status.h new file mode 100644 index 0000000000..6367402b7c --- /dev/null +++ b/src/hooks/dhcp/radius/radius_status.h @@ -0,0 +1,142 @@ +// Copyright (C) 2020-2025 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef RADIUS_STATUS_H +#define RADIUS_STATUS_H + +#include +#include +#include +#include +#include + +#include +#include + +namespace isc { +namespace radius { + +/// @brief Type of callback for status termination. +/// The argument is the result code. +typedef std::function CallbackStatus; + +/// @brief Base class for communication with servers. +class RadiusStatus { +public: + + /// @brief Constructor. + RadiusStatus() : exchange_() { + } + + /// @brief Destructor. + virtual ~RadiusStatus() = default; + + /// @brief Start communication. + /// Initiates the (first) exchange. + virtual void start() { + exchange_->start(); + } + + /// @brief Get the error code. + int getRC() const { + return (exchange_->getRC()); + } + + /// @brief Get request attributes. + /// + /// @return attributes from the to be sent request message. + AttributesPtr getReqAttrs() const { + MessagePtr request = exchange_->getRequest(); + if (request) { + return (request->getAttributes()); + } + return (AttributesPtr()); + } + + /// @brief Get response attributes. + /// + /// @return attributes from the received response message. + AttributesPtr getRespAttrs() const { + MessagePtr response = exchange_->getResponse(); + if (response) { + return (response->getAttributes()); + } + return (AttributesPtr()); + } + + /// @brief Get the exchange. + /// + /// @return The exchange. + ExchangePtr getExchange() { + return (exchange_); + } + +protected: + /// @brief Exchange. + ExchangePtr exchange_; +}; + +/// @brief class for communication with accounting servers. +/// Only the asynchronous variant is defined. +class RadiusAuthStatus : public RadiusStatus { +public: + + /// @brief Constructor. + /// + /// @param send_attrs Attributes to send. + /// @param handler Termination handler. + RadiusAuthStatus(const AttributesPtr& send_attrs, + const CallbackStatus& handler); + + /// @brief Destructor. + virtual ~RadiusAuthStatus() = default; + + /// @brief Start communication. + virtual void start() override; + + /// @brief Invoke accounting status callback + /// + /// @param callback Termination callback + /// @param exchange the exchange. + static void invokeCallback(const CallbackStatus& callback, + const ExchangePtr exchange); +}; + +/// @brief Pointer to accounting status. +typedef boost::shared_ptr RadiusAuthStatusPtr; + +/// @brief class for communication with accounting servers. +/// Only the asynchronous variant is defined. +class RadiusAcctStatus : public RadiusStatus { +public: + + /// @brief Constructor. + /// + /// @param send_attrs Attributes to send. + /// @param handler Termination handler. + RadiusAcctStatus(const AttributesPtr& send_attrs, + const CallbackStatus& handler); + + /// @brief Destructor. + virtual ~RadiusAcctStatus() = default; + + /// @brief Start communication. + virtual void start() override; + + /// @brief Invoke accounting status callback + /// + /// @param callback Termination callback + /// @param exchange the exchange. + static void invokeCallback(const CallbackStatus& callback, + const ExchangePtr exchange); +}; + +/// @brief Pointer to accounting status. +typedef boost::shared_ptr RadiusAcctStatusPtr; + +} // end of namespace radius +} // end of namespace isc +#endif // RADIUS_STATUS_H diff --git a/src/hooks/dhcp/radius/tests/meson.build b/src/hooks/dhcp/radius/tests/meson.build index e121c85420..a9fc9a8823 100644 --- a/src/hooks/dhcp/radius/tests/meson.build +++ b/src/hooks/dhcp/radius/tests/meson.build @@ -23,6 +23,7 @@ dhcp_radius_tests = executable( 'message_unittests.cc', 'run_unittests.cc', 'server_unittests.cc', + 'status_unittests.cc', 'sync_request_unittests.cc', 'utils_unittests.cc', cpp_args: [ diff --git a/src/hooks/dhcp/radius/tests/status_unittests.cc b/src/hooks/dhcp/radius/tests/status_unittests.cc new file mode 100644 index 0000000000..2bd1f1e126 --- /dev/null +++ b/src/hooks/dhcp/radius/tests/status_unittests.cc @@ -0,0 +1,1262 @@ +// Copyright (C) 2020-2025 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file This file contains tests which exercise the load and unload +/// functions in the Radius hooks library. In order to test the load +/// function, one must be able to pass it hook library parameters. The +/// the only way to populate these parameters is by actually loading the +/// library via HooksManager::loadLibraries(). + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::radius; +using namespace isc::util; +namespace ph = std::placeholders; + +namespace { + +/// Fake server. +const char SERVER_ADDRESS[] = "127.0.0.1"; +const unsigned short SERVER_AUTH_PORT = 11812; +const unsigned short SERVER_ACCT_PORT = 11813; + +/// Configs. +const char* CONFIGS[] = { + // CONFIGURATION 0 +"{\n" +" \"access\": {\n" +" \"servers\": [ {\n" +" \"name\": \"127.0.0.1\",\n" +" \"port\": 11812,\n" +" \"secret\": \"foo\" } ] },\n" +" \"accounting\": {\n" +" \"servers\": [ {\n" +" \"name\": \"127.0.0.1\",\n" +" \"port\": 11813,\n" +" \"secret\": \"bar\" } ] },\n" +" \"retries\": 0,\n" +" \"timeout\": 1 }\n", + // CONFIGURATION 1 +"{\n" +" \"access\": {\n" +" \"servers\": [ {\n" +" \"name\": \"127.0.0.1\",\n" +" \"port\": 21812,\n" +" \"secret\": \"foo\"\n" +" },{\n" +" \"name\": \"127.0.0.1\",\n" +" \"port\": 11812,\n" +" \"secret\": \"foo\" } ] },\n" +" \"accounting\": {\n" +" \"servers\": [ {\n" +" \"name\": \"127.0.0.1\",\n" +" \"port\": 21813,\n" +" \"secret\": \"bar\"\n" +" },{\n" +" \"name\": \"127.0.0.1\",\n" +" \"port\": 11813,\n" +" \"secret\": \"bar\" } ] },\n" +" \"retries\": 0,\n" +" \"timeout\": 1 }\n" +}; + +/// @brief Class for callbacks. +class Callback { +public: + + /// @brief Constructor. + /// + /// @param error_code Reference to the error code. + /// @param size Reference to the returned size. + /// @param flag Reference to called flag. + Callback(boost::system::error_code& error_code, + size_t& size, atomic& flag) + : error_code_(error_code), size_(size), called_(flag) { } + + /// @brief Callback. + /// + /// @param ec Error code. + void operator()(boost::system::error_code ec) { + error_code_ = ec; + size_ = 0; + called_ = true; + } + + /// @brief Callback. + /// + /// @param ec Error code. + /// @param size Size. + void operator()(boost::system::error_code ec, size_t size) { + error_code_ = ec; + size_ = size; + called_ = true; + } + + /// @brief Error code. + boost::system::error_code& error_code_; + + /// @brief Size. + size_t& size_; + + /// @brief Called flag. + atomic& called_; +}; + +/// @brief Test fixture for testing synchronous communication for +/// the radius library. +class StatusTest : public radius::test::AttributeTest { +public: + + /// @brief Thread pointer type. + typedef boost::shared_ptr ThreadPtr; + + /// @brief Constructor. + StatusTest() : + radius::test::AttributeTest(), + impl_(RadiusImpl::instance()), service_(new IOService()), + server_socket_(service_->getInternalIOService()), + receive_buffer_(4096), receive_error_code_(), received_(false), + send_buffer_(), send_error_code_(), sent_(false), + timer_(service_), timeout_(false), which_(""), + send_attributes_(), result_(OK_RC), thread_(), + handler_auth_(), handler_acct_(), finished_(false) { + + HostMgr::create(); + auto factory = [] (const isc::db::DatabaseConnection::ParameterMap&) { + return (HostDataSourcePtr()); + }; + HostDataSourceFactory::registerFactory("cache", factory); + + impl_.reset(); + impl_.setIOService(service_); + impl_.setIOContext(service_); + } + + /// @brief Destructor + virtual ~StatusTest() { + stop(); + close(); + if (thread_) { + thread_->join(); + thread_.reset(); + } + + // As a best practice, call any remaining handlers. + service_->stopAndPoll(); + HostDataSourceFactory::deregisterFactory("cache"); + } + + /// @brief Poll the I/O service. + /// From boost asio documentation: check if a handler is ready + /// and when there is one execute it. + void poll(); + + /// @brief Open fake server socket. + /// + /// @param server_port UDP server port. + void open(unsigned short server_port); + + /// @brief Close fake server socket. + void close(); + + /// @brief Timeout callback. + void timeout(); + + /// @brief Start timer. + /// + /// @param deadline Maximum time the test can run in milliseconds. + void start(long deadline); + + /// @brief Stop timer. + void stop(); + + /// @brief Done handler. + void done(int result); + + /// @brief Run a request handler i.e. the client part. + /// The server side is simulated by the individual test code. + void run(); + + /// @brief Radius implementation. + RadiusImpl& impl_; + + /// @brief IO service. + IOServicePtr service_; + + /// @brief Fake server socket (ASIO because asiolink has no bind()). + boost::asio::ip::udp::socket server_socket_; + + /// @brief Receive buffer. + vector receive_buffer_; + + /// @brief Receive error code. + boost::system::error_code receive_error_code_; + + /// @brief Received flag. + atomic received_; + + /// @brief Send buffer. + vector send_buffer_; + + /// @brief Send error code. + boost::system::error_code send_error_code_; + + /// @brief Sent flag. + atomic sent_; + + /// @brief Timer for timeout. + IntervalTimer timer_; + + /// @brief Timeout flag. + atomic timeout_; + + /// @brief Fake server type: Authentication ("access") or + /// Accounting ("accounting"). + string which_; + + /// @brief Attributes to send. + AttributesPtr send_attributes_; + + /// @brief Request result. + int result_; + + /// @brief Thread. + ThreadPtr thread_; + + /// @brief Handler for authentication. + RadiusAuthStatusPtr handler_auth_; + + /// @brief Handler for accounting.. + RadiusAcctStatusPtr handler_acct_; + + /// @brief Finished flag. + atomic finished_; +}; + +void +StatusTest::poll() { + service_->poll(); + EXPECT_NO_THROW(IfaceMgr::instance().receive4(0, 1000)); +} + +void +StatusTest::open(unsigned short server_port) { + // Get the socket. + boost::system::error_code ec; + server_socket_.open(boost::asio::ip::udp::v4(), ec); + ASSERT_FALSE(ec); + + // Get the address. + boost::asio::ip::address server_address = + boost::asio::ip::make_address(SERVER_ADDRESS, ec); + ASSERT_FALSE(ec); + + // Get the endpoint. + boost::asio::ip::udp::endpoint server_endpoint = + boost::asio::ip::udp::endpoint(server_address, server_port); + + // Bind the socket. + server_socket_.bind(server_endpoint, ec); + if (ec) { + FAIL() << "bind failed: " << ec.message(); + } +} + +void +StatusTest::close() { + if (server_socket_.is_open()) { + EXPECT_NO_THROW(server_socket_.close()); + } +} + +void +StatusTest::timeout() { + timeout_ = true; + FAIL() << "timeout"; +} + +void +StatusTest::start(long deadline) { + timer_.setup(std::bind(&StatusTest::timeout, this), + deadline, IntervalTimer::ONE_SHOT); +} + +void +StatusTest::stop() { + timer_.cancel(); +} + +void +StatusTest::done(int result) { + result_ = result; + finished_ = true; +} + +void +StatusTest::run() { + if (which_ == "access") { + handler_auth_.reset(new RadiusAuthStatus(send_attributes_, + std::bind(&StatusTest::done, + this, ph::_1))); + handler_auth_->start(); + } else if (which_ == "accounting") { + handler_acct_.reset(new RadiusAcctStatus(send_attributes_, + std::bind(&StatusTest::done, + this, ph::_1))); + handler_acct_->start(); + } else { + FAIL() << "which is not 'access' nor 'accounting'"; + } +} + +/// Verify what happens when there is no listening authentication server at all. +TEST_F(StatusTest, noAuthServer) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "access"; + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!finished_ && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_FALSE(timeout_); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(TIMEOUT_RC, result_); +} + +/// Verify what happens when there is no listening accounting server at all. +TEST_F(StatusTest, noAcctServer) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "accounting"; + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!finished_ && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_FALSE(timeout_); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(TIMEOUT_RC, result_); +} + +/// Verify what happens when no response is sent. +TEST_F(StatusTest, noAuthResponse) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "access"; + + // Open server socket. + open(SERVER_AUTH_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while ((!received_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(received_); + EXPECT_FALSE(timeout_); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(TIMEOUT_RC, result_); + + // Check received request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + EXPECT_EQ(PW_STATUS_SERVER, receive_buffer_[0]); + uint16_t length = (receive_buffer_[2] << 8) | receive_buffer_[3]; + ASSERT_LE(length, size); + EXPECT_GE(4096, length); +} + +/// Verify what happens when no response is sent. +TEST_F(StatusTest, noAcctResponse) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "accounting"; + + // Open server socket. + open(SERVER_ACCT_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while ((!received_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(received_); + EXPECT_FALSE(timeout_); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(TIMEOUT_RC, result_); + + // Check received request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + EXPECT_EQ(PW_STATUS_SERVER, receive_buffer_[0]); + uint16_t length = (receive_buffer_[2] << 8) | receive_buffer_[3]; + ASSERT_LE(length, size); + EXPECT_GE(4096, length); +} + +/// Verify what happens with Access-Accept response. +TEST_F(StatusTest, accept) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "access"; + + // Open server socket. + open(SERVER_AUTH_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header. + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCESS_ACCEPT; // Access-Accept. + send_buffer_[1] = receive_buffer_[1]; // Copy id. + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'f'; + auth_input[size + 1] = 'o'; + auth_input[size + 2] = 'o'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + Callback send_callback(send_error_code_, sent_size, sent_); + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], size), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(size, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(OK_RC, result_); +} + +/// Verify what happens with Access-Reject response. +TEST_F(StatusTest, reject) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "access"; + + // Open server socket. + open(SERVER_AUTH_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCESS_REJECT; // Access-Reject. + send_buffer_[1] = receive_buffer_[1]; // Copy id. + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'f'; + auth_input[size + 1] = 'o'; + auth_input[size + 2] = 'o'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + Callback send_callback(send_error_code_, sent_size, sent_); + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], size), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(size, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(OK_RC, result_); +} + +/// Verify what happens with Accounting-Response response. +TEST_F(StatusTest, response) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "accounting"; + + // Open server socket. + open(SERVER_ACCT_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header (no attributes). + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCOUNTING_RESPONSE; // Access-Accept. + send_buffer_[1] = receive_buffer_[1]; // Copy id. + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'b'; + auth_input[size + 1] = 'a'; + auth_input[size + 2] = 'r'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + Callback send_callback(send_error_code_, sent_size, sent_); + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], size), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(size, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(OK_RC, result_); +} + +/// Verify what happens with bad Access-Accept response. +TEST_F(StatusTest, badAccept) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "access"; + + // Open server socket. + open(SERVER_AUTH_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header. + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCESS_ACCEPT; // Access-Accept. + // There are a lot of ways to get an error including this one. + send_buffer_[1] = receive_buffer_[1] ^ 0x12; + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'f'; + auth_input[size + 1] = 'o'; + auth_input[size + 2] = 'o'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + Callback send_callback(send_error_code_, sent_size, sent_); + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], size), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(size, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(BADRESP_RC, result_); +} + +/// Verify what happens with bad Accounting-Response response. +TEST_F(StatusTest, badResponse) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "accounting"; + + // Open server socket. + open(SERVER_ACCT_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header (no attributes). + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCOUNTING_RESPONSE; // Access-Accept. + // There are a lot of ways to get an error including this one. + send_buffer_[1] = receive_buffer_[1] ^ 21; + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'b'; + auth_input[size + 1] = 'a'; + auth_input[size + 2] = 'r'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + Callback send_callback(send_error_code_, sent_size, sent_); + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], size), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(size, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(BADRESP_RC, result_); +} + +/// Verify what happens with bad (too short) response. +TEST_F(StatusTest, shortAuth) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "access"; + + // Open server socket. + open(SERVER_AUTH_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header. + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCESS_ACCEPT; // Access-Accept. + send_buffer_[1] = receive_buffer_[1]; // Copy id. + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'f'; + auth_input[size + 1] = 'o'; + auth_input[size + 2] = 'o'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + // Change the size. + Callback send_callback(send_error_code_, sent_size, sent_); + // Truncate the buffer. + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], 10), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(10, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(BADRESP_RC, result_); +} + +/// Verify what happens with bad (too short) response. +TEST_F(StatusTest, shortAcct) { + // Use CONFIGS[0]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "accounting"; + + // Open server socket. + open(SERVER_ACCT_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header (no attributes). + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCOUNTING_RESPONSE; // Access-Accept. + // There are a lot of ways to get an error including this one. + send_buffer_[1] = receive_buffer_[1]; // Copy id. + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'b'; + auth_input[size + 1] = 'a'; + auth_input[size + 2] = 'r'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + Callback send_callback(send_error_code_, sent_size, sent_); + // Truncate the buffer. + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], 10), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(10, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(BADRESP_RC, result_); +} + +/// Verify what happens with a backup authentication server. +TEST_F(StatusTest, accept2) { + // Use CONFIGS[1]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[1])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "access"; + + // Open server socket. + open(SERVER_AUTH_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header. + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCESS_ACCEPT; // Access-Accept. + send_buffer_[1] = receive_buffer_[1]; // Copy id. + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'f'; + auth_input[size + 1] = 'o'; + auth_input[size + 2] = 'o'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + Callback send_callback(send_error_code_, sent_size, sent_); + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], size), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(size, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(OK_RC, result_); +} + +/// Verify what happens with a backup accounting server. +TEST_F(StatusTest, response2) { + // Use CONFIGS[1]. + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[1])); + ASSERT_NO_THROW(impl_.init(json)); + + // Build request parameters. + which_ = "accounting"; + + // Open server socket. + open(SERVER_ACCT_PORT); + + // Push a receiver on it. + boost::asio::ip::udp::endpoint client; + size_t size = 0; + Callback receive_callback(receive_error_code_, size, received_); + server_socket_.async_receive_from( + boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()), + client, receive_callback); + + // Launch request handler i.e. the client code to test. + run(); + + // Start timer for 1.5s timeout. + start(1500); + + // Busy loop. + while (!received_ && !timeout_) { + poll(); + } + + ASSERT_TRUE(received_); + ASSERT_FALSE(timeout_); + + // Sanity checks on the request. + receive_buffer_.resize(size); + ASSERT_LE(20, size); + + // Build the response. + size = AUTH_HDR_LEN; // header (no attributes). + send_buffer_.resize(size); + send_buffer_[0] = PW_ACCOUNTING_RESPONSE; // Access-Accept. + send_buffer_[1] = receive_buffer_[1]; // Copy id. + send_buffer_[2] = size >> 8; // Length + send_buffer_[3] = size & 0xff; + // Copy the authenticator. + memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN); + + // Compute the authenticator. + vector auth_input(size + 3); + memmove(&auth_input[0], &send_buffer_[0], size); + auth_input[size] = 'b'; + auth_input[size + 1] = 'a'; + auth_input[size + 2] = 'r'; + OutputBuffer auth_output(AUTH_VECTOR_LEN); + digest(&auth_input[0], size + 3, isc::cryptolink::MD5, + auth_output, AUTH_VECTOR_LEN); + memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN); + + // Push a sender on the socket. + size_t sent_size = 0; + Callback send_callback(send_error_code_, sent_size, sent_); + server_socket_.async_send_to(boost::asio::buffer(&send_buffer_[0], size), + client, send_callback); + + // Second busy loop. + while ((!sent_ || !finished_) && !timeout_) { + poll(); + } + + EXPECT_TRUE(finished_); + EXPECT_TRUE(sent_); + EXPECT_FALSE(timeout_); + EXPECT_EQ(size, sent_size); + + // Done. + stop(); + service_->stopWork(); + + // Check result. + EXPECT_EQ(OK_RC, result_); +} + +} // end of anonymous namespace