]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4282] Added Radius*Status classes
authorFrancis Dupont <fdupont@isc.org>
Mon, 29 Dec 2025 00:15:36 +0000 (01:15 +0100)
committerFrancis Dupont <fdupont@isc.org>
Tue, 20 Jan 2026 09:31:09 +0000 (10:31 +0100)
src/hooks/dhcp/radius/meson.build
src/hooks/dhcp/radius/radius_accounting.cc
src/hooks/dhcp/radius/radius_messages.cc
src/hooks/dhcp/radius/radius_messages.h
src/hooks/dhcp/radius/radius_messages.mes
src/hooks/dhcp/radius/radius_request.cc
src/hooks/dhcp/radius/radius_status.cc [new file with mode: 0644]
src/hooks/dhcp/radius/radius_status.h [new file with mode: 0644]
src/hooks/dhcp/radius/tests/meson.build
src/hooks/dhcp/radius/tests/status_unittests.cc [new file with mode: 0644]

index 4440f6394e9c25d9b334b124cbde415a938ade7c..86fb34a1481bec78b29e14c18401f81590c57d66 100644 (file)
@@ -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"'],
index 30eb07e167c3aa5deb5009935b46c524f4875094..71b9dcff3c71131406d2fe0706d2f36d2a5a27cc 100644 (file)
@@ -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_;
 }
 
index a04a6343582de46e23ab5091c3852f97a67bacbe..e89e9d9dc0bb9598f394698ddaae49fb2e4ed9c5 100644 (file)
@@ -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)",
index 9a65758bf0992d52a7be7c9cacd71e0679bfaf90..c520fc2b61ef11606c8e0d894e35aecf3f4335e1 100644 (file)
@@ -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;
index 042981a95d23f90148388beea87096bb04647a59..bc24a7eb7ef91815cd3768d1f3cdce85470860b8 100644 (file)
@@ -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
index 692c16dad7c5bec71a6927580502e545a6568a89..8eec4bf6350afae0ff8349cb5782d678a77d90cf 100644 (file)
@@ -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 (file)
index 0000000..36a7922
--- /dev/null
@@ -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 <config.h>
+
+#include <radius_access.h>
+#include <radius_accounting.h>
+#include <radius_status.h>
+#include <radius_log.h>
+#include <sstream>
+
+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<uint8_t>(),
+                                   "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<uint8_t>(),
+                                   "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 (file)
index 0000000..6367402
--- /dev/null
@@ -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 <client_exchange.h>
+#include <radius.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/timer_mgr.h>
+
+#include <functional>
+#include <sstream>
+
+namespace isc {
+namespace radius {
+
+/// @brief Type of callback for status termination.
+/// The argument is the result code.
+typedef std::function<void(int)> 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<RadiusAuthStatus> 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<RadiusAcctStatus> RadiusAcctStatusPtr;
+
+} // end of namespace radius
+} // end of namespace isc
+#endif // RADIUS_STATUS_H
index e121c854206f8c16135421642b36910b4dbf2cb5..a9fc9a88233ab2f47817802d452d5c12025994c2 100644 (file)
@@ -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 (file)
index 0000000..2bd1f1e
--- /dev/null
@@ -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 <config.h>
+#include <cryptolink/crypto_hash.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/udp_socket.h>
+#include <database/database_connection.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/host_mgr.h>
+#include <radius_status.h>
+#include <attribute_test.h>
+#include <gtest/gtest.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <atomic>
+
+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<bool>& 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<bool>& 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<thread> 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<uint8_t> receive_buffer_;
+
+    /// @brief Receive error code.
+    boost::system::error_code receive_error_code_;
+
+    /// @brief Received flag.
+    atomic<bool> received_;
+
+    /// @brief Send buffer.
+    vector<uint8_t> send_buffer_;
+
+    /// @brief Send error code.
+    boost::system::error_code send_error_code_;
+
+    /// @brief Sent flag.
+    atomic<bool> sent_;
+
+    /// @brief Timer for timeout.
+    IntervalTimer timer_;
+
+    /// @brief Timeout flag.
+    atomic<bool> 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<bool> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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