Browse Source

[master] Merge branch 'trac1539-2' with fixing minor conflicts.

JINMEI Tatuya 13 years ago
parent
commit
d10df14d44

+ 14 - 0
src/bin/auth/auth_messages.mes

@@ -255,6 +255,20 @@ processed by the authoritative server has been found to contain an
 unsupported opcode. (The opcode is included in the message.) The server
 will return an error code of NOTIMPL to the sender.
 
+% AUTH_MESSAGE_FORWARD_ERROR failed to forward %1 request from %2: %3
+The authoritative server tried to forward some type DNS request
+message to a separate process (e.g., forwarding dynamic update
+requests to b10-ddns) to handle it, but it failed.  The authoritative
+server returns SERVFAIL to the client on behalf of the separate
+process.  The error could be configuration mismatch between b10-auth
+and the recipient component, or it may be because the requests are
+coming too fast and the receipient process cannot keep up with the
+rate, or some system level failure.  In either case this means the
+BIND 10 system is not working as expected, so the administrator should
+look into the cause and address the issue.  The log message includes
+the client's address (and port), and the error message sent from the
+lower layer that detects the failure.
+
 % AUTH_XFRIN_CHANNEL_CREATED XFRIN session channel created
 This is a debug message indicating that the authoritative server has
 created a channel to the XFRIN (Transfer-in) process.  It is issued

+ 144 - 18
src/bin/auth/auth_srv.cc

@@ -14,18 +14,10 @@
 
 #include <config.h>
 
-#include <sys/types.h>
-#include <netinet/in.h>
-
-#include <algorithm>
-#include <cassert>
-#include <iostream>
-#include <vector>
-#include <memory>
-
-#include <boost/bind.hpp>
+#include <util/io/socketsession.h>
 
 #include <asiolink/asiolink.h>
+#include <asiolink/io_endpoint.h>
 
 #include <config/ccsession.h>
 
@@ -64,6 +56,18 @@
 #include <auth/statistics.h>
 #include <auth/auth_log.h>
 
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <vector>
+#include <memory>
+
+#include <sys/types.h>
+#include <netinet/in.h>
+
 using namespace std;
 
 using namespace isc;
@@ -71,6 +75,7 @@ using namespace isc::cc;
 using namespace isc::datasrc;
 using namespace isc::dns;
 using namespace isc::util;
+using namespace isc::util::io;
 using namespace isc::auth;
 using namespace isc::dns::rdata;
 using namespace isc::data;
@@ -107,6 +112,107 @@ public:
 private:
     MessageRenderer& renderer_;
 };
+
+// A helper container of socket session forwarder.
+//
+// This class provides a simple wrapper interface to SocketSessionForwarder
+// so that the caller doesn't have to worry about connection management,
+// exception handling or parameter building.
+//
+// It internally maintains whether the underlying forwarder establishes a
+// connection to the receiver.  On a forwarding request, if the connection
+// hasn't been established yet, it automatically opens a new one, then
+// pushes the session over it.  It also closes the connection on destruction,
+// or a non-recoverable error happens, automatically.  So the only thing
+// the application has to do is to create this object and push any session
+// to be forwarded.
+class SocketSessionForwarderHolder {
+public:
+    /// \brief The constructor.
+    ///
+    /// \param message_name Any string that can identify the type of messages
+    /// to be forwarded via this session.  It will be only used as part of
+    /// log message, so it can be anything, but in practice something like
+    /// "update" or "xfr" is expected.
+    /// \param forwarder The underlying socket session forwarder.
+    SocketSessionForwarderHolder(const string& message_name,
+                                 BaseSocketSessionForwarder& forwarder) :
+        message_name_(message_name), forwarder_(forwarder), connected_(false)
+    {}
+
+    ~SocketSessionForwarderHolder() {
+        if (connected_) {
+            forwarder_.close();
+        }
+    }
+
+    /// \brief Push a socket session corresponding to given IOMessage.
+    ///
+    /// If the connection with the receiver process hasn't been established,
+    /// it automatically establishes one, then push the session over it.
+    ///
+    /// If either connect or push fails, the underlying forwarder object should
+    /// throw an exception.  This method logs the event, and propagates the
+    /// exception to the caller, which will eventually result in SERVFAIL.
+    /// The connection, if established, is automatically closed, so the next
+    /// forward request will trigger reopening a new connection.
+    ///
+    /// \note: Right now, there's no API to retrieve the local address from
+    /// the IOMessage.  Until it's added, we pass the remote address as
+    /// local.
+    ///
+    /// \param io_message The request message to be forwarded as a socket
+    /// session.  It will be converted to the parameters that the underlying
+    /// SocketSessionForwarder expects.
+    void push(const IOMessage& io_message) {
+        const IOEndpoint& remote_ep = io_message.getRemoteEndpoint();
+        const int protocol = remote_ep.getProtocol();
+        const int sock_type = getSocketType(protocol);
+        try {
+            connect();
+            forwarder_.push(io_message.getSocket().getNative(),
+                            remote_ep.getFamily(), sock_type, protocol,
+                            remote_ep.getSockAddr(), remote_ep.getSockAddr(),
+                            io_message.getData(), io_message.getDataSize());
+        } catch (const SocketSessionError& ex) {
+            LOG_ERROR(auth_logger, AUTH_MESSAGE_FORWARD_ERROR).
+                arg(message_name_).arg(remote_ep).arg(ex.what());
+            close();
+            throw;
+        }
+    }
+
+private:
+    const string message_name_;
+    BaseSocketSessionForwarder& forwarder_;
+    bool connected_;
+
+    void connect() {
+        if (!connected_) {
+            forwarder_.connectToReceiver();
+            connected_ = true;
+        }
+    }
+
+    void close() {
+        if (connected_) {
+            forwarder_.close();
+            connected_ = false;
+        }
+    }
+
+    static int getSocketType(int protocol) {
+        switch (protocol) {
+        case IPPROTO_UDP:
+            return (SOCK_DGRAM);
+        case IPPROTO_TCP:
+            return (SOCK_STREAM);
+        default:
+            isc_throw(isc::InvalidParameter,
+                      "Unexpected socket address family: " << protocol);
+        }
+    }
+};
 }
 
 class AuthSrvImpl {
@@ -115,7 +221,8 @@ private:
     AuthSrvImpl(const AuthSrvImpl& source);
     AuthSrvImpl& operator=(const AuthSrvImpl& source);
 public:
-    AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client);
+    AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client,
+                BaseSocketSessionForwarder& ddns_forwarder);
     ~AuthSrvImpl();
     isc::data::ConstElementPtr setDbFile(isc::data::ConstElementPtr config);
 
@@ -128,6 +235,7 @@ public:
     bool processNotify(const IOMessage& io_message, Message& message,
                        OutputBuffer& buffer,
                        auto_ptr<TSIGContext> tsig_context);
+    bool processUpdate(const IOMessage& io_message);
 
     IOService io_service_;
 
@@ -189,6 +297,9 @@ private:
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
 
+    // Socket session forwarder for dynamic update requests
+    SocketSessionForwarderHolder ddns_forwarder_;
+
     /// Increment query counter
     void incCounter(const int protocol);
 
@@ -199,7 +310,8 @@ private:
 };
 
 AuthSrvImpl::AuthSrvImpl(const bool use_cache,
-                         AbstractXfroutClient& xfrout_client) :
+                         AbstractXfroutClient& xfrout_client,
+                         BaseSocketSessionForwarder& ddns_forwarder) :
     config_session_(NULL),
     xfrin_session_(NULL),
     memory_client_class_(RRClass::IN()),
@@ -207,7 +319,8 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
     counters_(),
     keyring_(NULL),
     xfrout_connected_(false),
-    xfrout_client_(xfrout_client)
+    xfrout_client_(xfrout_client),
+    ddns_forwarder_("update", ddns_forwarder)
 {
     // cur_datasrc_ is automatically initialized by the default constructor,
     // effectively being an empty (sqlite) data source.  once ccsession is up
@@ -277,9 +390,10 @@ private:
     AuthSrv* server_;
 };
 
-AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client)
+AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client,
+                 BaseSocketSessionForwarder& ddns_forwarder)
 {
-    impl_ = new AuthSrvImpl(use_cache, xfrout_client);
+    impl_ = new AuthSrvImpl(use_cache, xfrout_client, ddns_forwarder);
     checkin_ = new ConfigChecker(this);
     dns_lookup_ = new MessageLookup(this);
     dns_answer_ = new MessageAnswer(this);
@@ -527,16 +641,19 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         return;
     }
 
+    const Opcode opcode = message.getOpcode();
     bool send_answer = true;
     try {
         // update per opcode statistics counter.  This can only be reliable
         // after TSIG check succeeds.
         impl_->counters_.inc(message.getOpcode());
 
-        if (message.getOpcode() == Opcode::NOTIFY()) {
+        if (opcode == Opcode::NOTIFY()) {
             send_answer = impl_->processNotify(io_message, message, buffer,
                                                tsig_context);
-        } else if (message.getOpcode() != Opcode::QUERY()) {
+        } else if (opcode == Opcode::UPDATE()) {
+            send_answer = impl_->processUpdate(io_message);
+        } else if (opcode != Opcode::QUERY()) {
             LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
                       .arg(message.getOpcode().toText());
             makeErrorMessage(impl_->renderer_, message, buffer,
@@ -546,7 +663,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                              Rcode::FORMERR(), tsig_context);
         } else {
             ConstQuestionPtr question = *message.beginQuestion();
-            const RRType &qtype = question->getType();
+            const RRType& qtype = question->getType();
             if (qtype == RRType::AXFR()) {
                 send_answer = impl_->processXfrQuery(io_message, message,
                                                      buffer, tsig_context);
@@ -754,6 +871,15 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
     return (true);
 }
 
+bool
+AuthSrvImpl::processUpdate(const IOMessage& io_message) {
+    // Push the update request to a separate process via the forwarder.
+    // On successful push, the request shouldn't be responded from b10-auth,
+    // so we return false.
+    ddns_forwarder_.push(io_message);
+    return (false);
+}
+
 void
 AuthSrvImpl::incCounter(const int protocol) {
     // Increment query counter.

+ 10 - 1
src/bin/auth/auth_srv.h

@@ -37,6 +37,14 @@
 #include <auth/statistics.h>
 
 namespace isc {
+namespace util {
+namespace io {
+class BaseSocketSessionForwarder;
+}
+}
+namespace datasrc {
+class InMemoryClient;
+}
 namespace xfr {
 class AbstractXfroutClient;
 }
@@ -90,7 +98,8 @@ public:
     /// but can refer to a local mock object for testing (or other
     /// experimental) purposes.
     AuthSrv(const bool use_cache,
-            isc::xfr::AbstractXfroutClient& xfrout_client);
+            isc::xfr::AbstractXfroutClient& xfrout_client,
+            isc::util::io::BaseSocketSessionForwarder& ddns_forwarder);
     ~AuthSrv();
     //@}
 

+ 6 - 2
src/bin/auth/benchmarks/query_bench.cc

@@ -31,9 +31,10 @@
 #include <dns/rrclass.h>
 
 #include <log/logger_support.h>
-
 #include <xfr/xfrout_client.h>
 
+#include <util/unittests/mock_socketsession.h>
+
 #include <auth/auth_srv.h>
 #include <auth/auth_config.h>
 #include <auth/query.h>
@@ -48,6 +49,7 @@ using namespace isc::auth;
 using namespace isc::dns;
 using namespace isc::log;
 using namespace isc::util;
+using namespace isc::util::unittests;
 using namespace isc::xfr;
 using namespace isc::bench;
 using namespace isc::asiodns;
@@ -78,7 +80,7 @@ protected:
     QueryBenchMark(const bool enable_cache,
                    const BenchQueries& queries, Message& query_message,
                    OutputBuffer& buffer) :
-        server_(new AuthSrv(enable_cache, xfrout_client)),
+        server_(new AuthSrv(enable_cache, xfrout_client, ddns_forwarder)),
         queries_(queries),
         query_message_(query_message),
         buffer_(buffer),
@@ -103,6 +105,8 @@ public:
 
         return (queries_.size());
     }
+private:
+    MockSocketSessionForwarder ddns_forwarder;
 protected:
     AuthSrvPtr server_;
 private:

+ 19 - 1
src/bin/auth/common.cc

@@ -33,7 +33,25 @@ getXfroutSocketPath() {
         if (getenv("BIND10_XFROUT_SOCKET_FILE") != NULL) {
             return (getenv("BIND10_XFROUT_SOCKET_FILE"));
         } else {
-            return (UNIX_SOCKET_FILE);
+            return (UNIX_XFROUT_SOCKET_FILE);
+        }
+    }
+}
+
+string
+getDDNSSocketPath() {
+    if (getenv("B10_FROM_BUILD") != NULL) {
+        if (getenv("B10_FROM_SOURCE_LOCALSTATEDIR") != NULL) {
+            return (string(getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) +
+                    "/ddns_socket");
+        } else {
+            return (string(getenv("B10_FROM_BUILD")) + "/ddns_socket");
+        }
+    } else {
+        if (getenv("BIND10_DDNS_SOCKET_FILE") != NULL) {
+            return (getenv("BIND10_DDNS_SOCKET_FILE"));
+        } else {
+            return (UNIX_DDNS_SOCKET_FILE);
         }
     }
 }

+ 14 - 0
src/bin/auth/common.h

@@ -38,6 +38,20 @@ public:
 /// The logic should be the same as in b10-xfrout, so they find each other.
 std::string getXfroutSocketPath();
 
+/// \brief Get the path of socket to talk to ddns
+///
+/// It takes some environment variables into account (B10_FROM_BUILD,
+/// B10_FROM_SOURCE_LOCALSTATEDIR and BIND10_DDNS_SOCKET_FILE). It
+/// also considers the installation prefix.
+///
+/// The logic should be the same as in b10-ddns, so they find each other.
+///
+/// Note: eventually we should find a better way so that we don't have to
+/// repeat the same magic value (and how to tweak it with some magic
+/// environment variable) twice, at which point this function may be able
+/// to be deprecated.
+std::string getDDNSSocketPath();
+
 /// \brief The name used when identifieng the process
 ///
 /// This is currently b10-auth, but it can be changed easily in one place.

+ 4 - 1
src/bin/auth/main.cc

@@ -28,6 +28,7 @@
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
+#include <util/io/socketsession.h>
 
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
@@ -60,6 +61,7 @@ using namespace isc::data;
 using namespace isc::dns;
 using namespace isc::log;
 using namespace isc::util;
+using namespace isc::util::io;
 using namespace isc::xfr;
 
 namespace {
@@ -130,6 +132,7 @@ main(int argc, char* argv[]) {
     bool statistics_session_established = false; // XXX (see Trac #287)
     ModuleCCSession* config_session = NULL;
     XfroutClient xfrout_client(getXfroutSocketPath());
+    SocketSessionForwarder ddns_forwarder(getDDNSSocketPath());
     try {
         string specfile;
         if (getenv("B10_FROM_BUILD")) {
@@ -139,7 +142,7 @@ main(int argc, char* argv[]) {
             specfile = string(AUTH_SPECFILE_LOCATION);
         }
 
-        auth_server = new AuthSrv(cache, xfrout_client);
+        auth_server = new AuthSrv(cache, xfrout_client, ddns_forwarder);
         LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
 
         SimpleCallback* checkin = auth_server->getCheckinProvider();

+ 2 - 1
src/bin/auth/spec_config.h.pre.in

@@ -13,4 +13,5 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #define AUTH_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/auth.spec"
-#define UNIX_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/auth_xfrout_conn"
+#define UNIX_XFROUT_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/auth_xfrout_conn"
+#define UNIX_DDNS_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/ddns_socket"

+ 165 - 9
src/bin/auth/tests/auth_srv_unittest.cc

@@ -14,12 +14,7 @@
 
 #include <config.h>
 
-#include <vector>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/scoped_ptr.hpp>
-
-#include <gtest/gtest.h>
+#include <util/io/sockaddr_util.h>
 
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
@@ -39,6 +34,7 @@
 #include <auth/common.h>
 #include <auth/statistics.h>
 
+#include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
@@ -46,10 +42,24 @@
 #include <testutils/portconfig.h>
 #include <testutils/socket_request.h>
 
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <vector>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
 using namespace std;
 using namespace isc::cc;
 using namespace isc::dns;
 using namespace isc::util;
+using namespace isc::util::io::internal;
+using namespace isc::util::unittests;
 using namespace isc::dns::rdata;
 using namespace isc::data;
 using namespace isc::xfr;
@@ -58,6 +68,7 @@ using namespace isc::asiolink;
 using namespace isc::testutils;
 using namespace isc::server_common::portconfig;
 using isc::UnitTestUtil;
+using boost::scoped_ptr;
 
 namespace {
 const char* const CONFIG_TESTDB =
@@ -78,7 +89,7 @@ class AuthSrvTest : public SrvTestBase {
 protected:
     AuthSrvTest() :
         dnss_(),
-        server(true, xfrout),
+        server(true, xfrout, ddns_forwarder),
         rrclass(RRClass::IN()),
         // The empty string is expected value of the parameter of
         // requestSocket, not the app_name (there's no fallback, it checks
@@ -144,9 +155,30 @@ protected:
                     opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
     }
 
+    // Convenient shortcut of creating a simple request and having the
+    // server process it.
+    void createAndSendRequest(RRType req_type, Opcode opcode = Opcode::QUERY(),
+                              const Name& req_name = Name("example.com"),
+                              RRClass req_class = RRClass::IN(),
+                              int protocol = IPPROTO_UDP,
+                              const char* const remote_address =
+                              DEFAULT_REMOTE_ADDRESS,
+                              uint16_t remote_port = DEFAULT_REMOTE_PORT)
+    {
+        UnitTestUtil::createRequestMessage(request_message, opcode,
+                                           default_qid, req_name,
+                                           req_class, req_type);
+        createRequestPacket(request_message, protocol, NULL,
+                            remote_address, remote_port);
+        parse_message->clear(Message::PARSE);
+        server.processMessage(*io_message, *parse_message, *response_obuffer,
+                              &dnsserv);
+    }
+
     MockDNSService dnss_;
     MockSession statistics_session;
     MockXfroutClient xfrout;
+    MockSocketSessionForwarder ddns_forwarder;
     AuthSrv server;
     const RRClass rrclass;
     vector<uint8_t> response_data;
@@ -254,8 +286,8 @@ TEST_F(AuthSrvTest, iqueryViaDNSServer) {
 // Unsupported requests.  Should result in NOTIMP.
 TEST_F(AuthSrvTest, unsupportedRequest) {
     unsupportedRequest();
-    // unsupportedRequest tries 14 different opcodes
-    checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 14);
+    // unsupportedRequest tries 13 different opcodes
+    checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 13);
 }
 
 // Multiple questions.  Should result in FORMERR.
@@ -1488,4 +1520,128 @@ TEST_F(AuthSrvTest,
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
+//
+// DDNS related tests
+//
+
+// Helper subroutine to check if the given socket address has the expected
+// address and port.  It depends on specific output of getnameinfo() (while
+// there can be multiple textual representation of the same address) but
+// in practice it should be reliable.
+void
+checkAddrPort(const struct sockaddr& actual_sa,
+              const string& expected_addr, uint16_t expected_port)
+{
+    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+    const int error = getnameinfo(&actual_sa, getSALength(actual_sa), hbuf,
+                                  sizeof(hbuf), sbuf, sizeof(sbuf),
+                                  NI_NUMERICHOST | NI_NUMERICSERV);
+    if (error != 0) {
+        isc_throw(isc::Unexpected, "getnameinfo failed: " <<
+                  gai_strerror(error));
+    }
+    EXPECT_EQ(expected_addr, hbuf);
+    EXPECT_EQ(boost::lexical_cast<string>(expected_port), sbuf);
+}
+
+TEST_F(AuthSrvTest, DDNSForward) {
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+
+    // Repeat sending an update request 4 times, differing some network
+    // parameters: UDP/IPv4, TCP/IPv4, UDP/IPv6, TCP/IPv6, in this order.
+    // By doing that we can also confirm the forwarder connection will be
+    // established exactly once, and kept established.
+    for (size_t i = 0; i < 4; ++i) {
+        // Use different names for some different cases
+        const Name zone_name = Name(i < 2 ? "example.com" : "example.org");
+        const socklen_t family = (i < 2) ? AF_INET : AF_INET6;
+        const char* const remote_addr =
+            (family == AF_INET) ? "192.0.2.1" : "2001:db8::1";
+        const uint16_t remote_port =
+            (family == AF_INET) ? 53214 : 53216;
+        const int protocol = ((i % 2) == 0) ? IPPROTO_UDP : IPPROTO_TCP;
+
+        createAndSendRequest(RRType::SOA(), Opcode::UPDATE(), zone_name,
+                             RRClass::IN(), protocol, remote_addr,
+                             remote_port);
+        EXPECT_FALSE(dnsserv.hasAnswer());
+        EXPECT_TRUE(ddns_forwarder.isConnected());
+
+        // Examine the pushed data (note: currently "local end" has a dummy
+        // value equal to remote)
+        EXPECT_EQ(family, ddns_forwarder.getPushedFamily());
+        const int expected_type =
+            (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
+        EXPECT_EQ(expected_type, ddns_forwarder.getPushedType());
+        EXPECT_EQ(protocol, ddns_forwarder.getPushedProtocol());
+        checkAddrPort(ddns_forwarder.getPushedRemoteend(),
+                      remote_addr, remote_port);
+        checkAddrPort(ddns_forwarder.getPushedLocalend(),
+                      remote_addr, remote_port);
+        EXPECT_EQ(io_message->getDataSize(),
+                  ddns_forwarder.getPushedData().size());
+        EXPECT_EQ(0, memcmp(io_message->getData(),
+                            &ddns_forwarder.getPushedData()[0],
+                            ddns_forwarder.getPushedData().size()));
+    }
+}
+
+TEST_F(AuthSrvTest, DDNSForwardConnectFail) {
+    // make connect attempt fail.  It should result in SERVFAIL.  Note that
+    // the question (zone) section should be cleared for opcode of update.
+    ddns_forwarder.disableConnect();
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+                Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+
+    // Now make connect okay again.  Despite the previous failure the new
+    // connection should now be established.
+    ddns_forwarder.enableConnect();
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+    EXPECT_FALSE(dnsserv.hasAnswer());
+    EXPECT_TRUE(ddns_forwarder.isConnected());
+}
+
+TEST_F(AuthSrvTest, DDNSForwardPushFail) {
+    // Make first request succeed, which will establish the connection.
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+    EXPECT_TRUE(ddns_forwarder.isConnected());
+
+    // make connect attempt fail.  It should result in SERVFAIL.  The
+    // connection should be closed.  Use IPv6 address for varying log output.
+    ddns_forwarder.disablePush();
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE(), Name("example.com"),
+                         RRClass::IN(), IPPROTO_UDP, "2001:db8::2");
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+                Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+
+    // Allow push again.  Connection will be reopened, and the request will
+    // be forwarded successfully.
+    ddns_forwarder.enablePush();
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+    EXPECT_FALSE(dnsserv.hasAnswer());
+    EXPECT_TRUE(ddns_forwarder.isConnected());
+}
+
+TEST_F(AuthSrvTest, DDNSForwardClose) {
+    scoped_ptr<AuthSrv> tmp_server(new AuthSrv(true, xfrout, ddns_forwarder));
+    UnitTestUtil::createRequestMessage(request_message, Opcode::UPDATE(),
+                                       default_qid, Name("example.com"),
+                                       RRClass::IN(), RRType::SOA());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    tmp_server->processMessage(*io_message, *parse_message, *response_obuffer,
+                               &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
+    EXPECT_TRUE(ddns_forwarder.isConnected());
+
+    // Destroy the server.  The forwarder should close the connection.
+    tmp_server.reset();
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+}
+
 }

+ 4 - 1
src/bin/auth/tests/command_unittest.cc

@@ -33,6 +33,7 @@
 
 #include <asiolink/asiolink.h>
 
+#include <util/unittests/mock_socketsession.h>
 #include <testutils/mockups.h>
 
 #include <cassert>
@@ -52,6 +53,7 @@ using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::config;
+using namespace isc::util::unittests;
 using namespace isc::testutils;
 using namespace isc::auth::unittest;
 
@@ -60,7 +62,7 @@ namespace {
 class AuthCommandTest : public ::testing::Test {
 protected:
     AuthCommandTest() :
-        server_(false, xfrout_),
+        server_(false, xfrout_, ddns_forwarder_),
         rcode_(-1),
         expect_rcode_(0),
         itimer_(server_.getIOService())
@@ -73,6 +75,7 @@ protected:
     }
     MockSession statistics_session_;
     MockXfroutClient xfrout_;
+    MockSocketSessionForwarder ddns_forwarder_;
     AuthSrv server_;
     ConstElementPtr result_;
     // The shutdown command parameter

+ 38 - 12
src/bin/auth/tests/common_unittest.cc

@@ -60,37 +60,63 @@ protected:
             EXPECT_EQ(0, setenv(name.c_str(), value.c_str(), 1));
         }
     }
-    // Test getXfroutSocketPath under given environment
-    void testXfrout(const string& fromBuild, const string& localStateDir,
-                    const string& socketFile, const string& expected)
+    // Test getter functions for a socket file path under given environment
+    void testSocketPath(const string& fromBuild, const string& localStateDir,
+                        const string& socketFile, const string& env_name,
+                        const string& expected, string (*actual_fn)())
     {
         setEnv("B10_FROM_BUILD", fromBuild);
         setEnv("B10_FROM_SOURCE_LOCALSTATEDIR", localStateDir);
-        setEnv("BIND10_XFROUT_SOCKET_FILE", socketFile);
-        EXPECT_EQ(expected, getXfroutSocketPath());
+        setEnv(env_name, socketFile);
+        EXPECT_EQ(expected, actual_fn());
     }
 };
 
 // Test that when we have no special environment, we get the default from prefix
 TEST_F(Paths, xfroutNoEnv) {
-    testXfrout("", "", "", UNIX_SOCKET_FILE);
+    testSocketPath("", "", "", "BIND10_XFROUT_SOCKET_FILE",
+                   UNIX_XFROUT_SOCKET_FILE, getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsNoEnv) {
+    testSocketPath("", "", "", "BIND10_DDNS_SOCKET_FILE",
+                   UNIX_DDNS_SOCKET_FILE, getDDNSSocketPath);
 }
 
 // Override by B10_FROM_BUILD
 TEST_F(Paths, xfroutFromBuild) {
-    testXfrout("/from/build", "", "/wrong/path",
-               "/from/build/auth_xfrout_conn");
+    testSocketPath("/from/build", "", "/wrong/path",
+                   "BIND10_XFROUT_SOCKET_FILE", "/from/build/auth_xfrout_conn",
+                   getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsFromBuild) {
+    testSocketPath("/from/build", "", "/wrong/path", "BIND10_DDNS_SOCKET_FILE",
+                   "/from/build/ddns_socket", getDDNSSocketPath);
 }
 
 // Override by B10_FROM_SOURCE_LOCALSTATEDIR
 TEST_F(Paths, xfroutLocalStatedir) {
-    testXfrout("/wrong/path", "/state/dir", "/wrong/path",
-               "/state/dir/auth_xfrout_conn");
+    testSocketPath("/wrong/path", "/state/dir", "/wrong/path",
+                   "BIND10_XFROUT_SOCKET_FILE", "/state/dir/auth_xfrout_conn",
+                   getXfroutSocketPath);
 }
 
-// Override by BIND10_XFROUT_SOCKET_FILE explicitly
+TEST_F(Paths, ddnsLocalStatedir) {
+    testSocketPath("/wrong/path", "/state/dir", "/wrong/path",
+                   "BIND10_DDNS_SOCKET_FILE", "/state/dir/ddns_socket",
+                   getDDNSSocketPath);
+}
+
+// Override by BIND10_xxx_SOCKET_FILE explicitly
 TEST_F(Paths, xfroutFromEnv) {
-    testXfrout("", "", "/the/path/to/file", "/the/path/to/file");
+    testSocketPath("", "", "/the/path/to/file", "BIND10_XFROUT_SOCKET_FILE",
+                   "/the/path/to/file", getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsFromEnv) {
+    testSocketPath("", "", "/the/path/to/file", "BIND10_DDNS_SOCKET_FILE",
+                   "/the/path/to/file", getDDNSSocketPath);
 }
 
 }

+ 4 - 1
src/bin/auth/tests/config_unittest.cc

@@ -32,6 +32,7 @@
 
 #include "datasrc_util.h"
 
+#include <util/unittests/mock_socketsession.h>
 #include <testutils/mockups.h>
 #include <testutils/portconfig.h>
 #include <testutils/socket_request.h>
@@ -44,6 +45,7 @@ using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::asiodns;
 using namespace isc::auth::unittest;
+using namespace isc::util::unittests;
 using namespace isc::testutils;
 
 namespace {
@@ -52,7 +54,7 @@ protected:
     AuthConfigTest() :
         dnss_(),
         rrclass(RRClass::IN()),
-        server(true, xfrout),
+        server(true, xfrout, ddns_forwarder),
         // The empty string is expected value of the parameter of
         // requestSocket, not the app_name (there's no fallback, it checks
         // the empty string is passed).
@@ -63,6 +65,7 @@ protected:
     MockDNSService dnss_;
     const RRClass rrclass;
     MockXfroutClient xfrout;
+    MockSocketSessionForwarder ddns_forwarder;
     AuthSrv server;
     isc::server_common::portconfig::AddressList address_store_;
 private:

+ 20 - 4
src/lib/asiolink/io_endpoint.cc

@@ -14,10 +14,6 @@
 
 #include <config.h>
 
-#include <unistd.h>             // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
 #include <asio.hpp>
 
 #include <asiolink/io_address.h>
@@ -26,6 +22,13 @@
 #include <asiolink/tcp_endpoint.h>
 #include <asiolink/udp_endpoint.h>
 
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <unistd.h>             // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
 using namespace std;
 
 namespace isc {
@@ -58,5 +61,18 @@ IOEndpoint::operator!=(const IOEndpoint& other) const {
     return (!operator==(other));
 }
 
+ostream&
+operator<<(ostream& os, const IOEndpoint& endpoint) {
+    if (endpoint.getFamily() == AF_INET6) {
+        os << "[" << endpoint.getAddress().toText() << "]";
+    } else {
+        // In practice this should be AF_INET, but it's not guaranteed by
+        // the interface.  We'll use the result of textual address
+        // representation opaquely.
+        os << endpoint.getAddress().toText();
+    }
+    os << ":" << boost::lexical_cast<string>(endpoint.getPort());
+    return (os);
+}
 } // namespace asiolink
 } // namespace isc

+ 27 - 3
src/lib/asiolink/io_endpoint.h

@@ -18,9 +18,6 @@
 // IMPORTANT NOTE: only very few ASIO headers files can be included in
 // this file.  In particular, asio.hpp should never be included here.
 // See the description of the namespace below.
-#include <unistd.h>             // for some network system calls
-
-#include <sys/socket.h>         // for sockaddr
 
 #include <functional>
 #include <string>
@@ -28,6 +25,12 @@
 #include <exceptions/exceptions.h>
 #include <asiolink/io_address.h>
 
+# include <ostream>
+
+#include <unistd.h>             // for some network system calls
+
+#include <sys/socket.h>         // for sockaddr
+
 namespace isc {
 namespace asiolink {
 
@@ -158,6 +161,27 @@ public:
                                     const unsigned short port);
 };
 
+/// \brief Insert the \c IOEndpoint as a string into stream.
+///
+/// This method converts \c endpoint into a string and inserts it into the
+/// output stream \c os.
+///
+/// This method converts the address and port of the endpoint in the textual
+/// format that other BIND 10 modules would use in logging, i.e.,
+/// - For IPv6 address: [<address>]:port (e.g., [2001:db8::5300]:53)
+/// - For IPv4 address: <address>:port (e.g., 192.0.2.53:5300)
+///
+/// If it's neither IPv6 nor IPv4, it converts the endpoint into text in the
+/// same format as that for IPv4, although in practice such a case is not
+/// really expected.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param endpoint A reference to an \c IOEndpoint object output by the
+/// operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const IOEndpoint& endpoint);
 } // namespace asiolink
 } // namespace isc
 #endif // __IO_ENDPOINT_H

+ 53 - 5
src/lib/asiolink/tests/io_endpoint_unittest.cc

@@ -13,18 +13,22 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_error.h>
+
 #include <gtest/gtest.h>
 
+#include <boost/shared_ptr.hpp>
+
+#include <sstream>
+#include <string>
+
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netdb.h>
 #include <string.h>
 
-#include <boost/shared_ptr.hpp>
-
-#include <asiolink/io_endpoint.h>
-#include <asiolink/io_error.h>
-
 using namespace isc::asiolink;
 
 namespace {
@@ -240,4 +244,48 @@ TEST(IOEndpointTest, getSockAddr) {
     sockAddrMatch(ep->getSockAddr(), "2001:db8::5300", "35");
 }
 
+// A faked IOEndpoint for an uncommon address family.  It wouldn't be possible
+// to create via the normal factory, so we define a special derived class
+// for it.
+class TestIOEndpoint : public IOEndpoint {
+    virtual IOAddress getAddress() const {
+        return IOAddress("2001:db8::bad:add");
+    }
+    virtual uint16_t getPort() const { return (42); }
+    virtual short getProtocol() const { return (IPPROTO_UDP); }
+    virtual short getFamily() const { return (AF_UNSPEC); }
+    virtual const struct sockaddr& getSockAddr() const {
+        static struct sockaddr sa_placeholder;
+        return (sa_placeholder);
+    }
+};
+
+void
+checkEndpointText(const std::string& expected, const IOEndpoint& ep) {
+    std::ostringstream oss;
+    oss << ep;
+    EXPECT_EQ(expected, oss.str());
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST(IOEndpointTest, LeftShiftOperator) {
+    // UDP/IPv4
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+                                             IOAddress("192.0.2.1"), 53210));
+    checkEndpointText("192.0.2.1:53210", *ep);
+
+    // UDP/IPv6
+    ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::53"), 53));
+    checkEndpointText("[2001:db8::53]:53", *ep);
+
+    // Same for TCP: shouldn't be different
+    ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 53210));
+    checkEndpointText("192.0.2.1:53210", *ep);
+    ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::53"), 53));
+    checkEndpointText("[2001:db8::53]:53", *ep);
+
+    // Uncommon address family.  The actual behavior doesn't matter much
+    // in practice, but we check such input doesn't make it crash.
+    checkEndpointText("2001:db8::bad:add:42", TestIOEndpoint());
+}
 }

+ 11 - 6
src/lib/testutils/srv_test.cc

@@ -34,6 +34,7 @@ using namespace isc::asiolink;
 namespace isc {
 namespace testutils {
 const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
+const uint16_t DEFAULT_REMOTE_PORT = 53210;
 
 SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
                              parse_message(new Message(Message::PARSE)),
@@ -62,7 +63,8 @@ SrvTestBase::createDataFromFile(const char* const datafile,
     delete endpoint;
 
     endpoint = IOEndpoint::create(protocol,
-                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS),
+                                  DEFAULT_REMOTE_PORT);
     UnitTestUtil::readWireData(datafile, data);
     io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
         &IOSocket::getDummyTCPSocket();
@@ -71,7 +73,9 @@ SrvTestBase::createDataFromFile(const char* const datafile,
 
 void
 SrvTestBase::createRequestPacket(Message& message,
-                                 const int protocol, TSIGContext* context)
+                                 const int protocol, TSIGContext* context,
+                                 const char* const remote_address,
+                                 uint16_t remote_port)
 {
     if (context == NULL) {
         message.toWire(request_renderer);
@@ -81,8 +85,8 @@ SrvTestBase::createRequestPacket(Message& message,
 
     delete io_message;
 
-    endpoint = IOEndpoint::create(protocol,
-                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
+    endpoint = IOEndpoint::create(protocol, IOAddress(remote_address),
+                                  remote_port);
     io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
         &IOSocket::getDummyTCPSocket();
 
@@ -96,9 +100,10 @@ void
 SrvTestBase::unsupportedRequest() {
     for (unsigned int i = 0; i < 16; ++i) {
         // set Opcode to 'i', which iterators over all possible codes except
-        // the standard query and notify 
+        // the standard opcodes we support.
         if (i == isc::dns::Opcode::QUERY().getCode() ||
-            i == isc::dns::Opcode::NOTIFY().getCode()) {
+            i == isc::dns::Opcode::NOTIFY().getCode() ||
+            i == isc::dns::Opcode::UPDATE().getCode()) {
             continue;
         }
         createDataFromFile("simplequery_fromWire.wire");

+ 4 - 1
src/lib/testutils/srv_test.h

@@ -35,6 +35,7 @@ class IOEndpoint;
 namespace isc {
 namespace testutils {
 extern const char* const DEFAULT_REMOTE_ADDRESS;
+extern const uint16_t DEFAULT_REMOTE_PORT;
 
 // These are flags to indicate whether the corresponding flag bit of the
 // DNS header is to be set in the test cases.  (The flag values
@@ -88,7 +89,9 @@ protected:
     /// The existing content of \c io_message, if any, will be deleted.
     void createRequestPacket(isc::dns::Message& message,
                              const int protocol = IPPROTO_UDP,
-                             isc::dns::TSIGContext* context = NULL);
+                             isc::dns::TSIGContext* context = NULL,
+                             const char* const address = DEFAULT_REMOTE_ADDRESS,
+                             uint16_t port = DEFAULT_REMOTE_PORT);
 
     MockSession notify_session;
     MockServer dnsserv;

+ 43 - 10
src/lib/util/io/socketsession.h

@@ -15,12 +15,14 @@
 #ifndef __SOCKETSESSION_H_
 #define __SOCKETSESSION_H_ 1
 
-#include <string>
-
 #include <boost/noncopyable.hpp>
 
 #include <exceptions/exceptions.h>
 
+#include <string>
+
+#include <sys/socket.h>
+
 namespace isc {
 namespace util {
 namespace io {
@@ -156,6 +158,35 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+/// The "base" class of \c SocketSessionForwarder
+///
+/// This class defines abstract interfaces of the \c SocketSessionForwarder
+/// class.  Although \c SocketSessionForwarder is not intended to be used in
+/// a polymorphic way, it's not easy to use in tests because it will require
+/// various low level network operations.  So it would be useful if we
+/// provide a framework for defining a fake or mock version of it.
+/// An application that needs to use \c SocketSessionForwarder would actually
+/// refer to this base class, and tests for the application would define
+/// and use a fake version of the forwarder class.
+///
+/// Normal applications are not expected to define and use their own derived
+/// version of this base class, while it's not prohibited at the API level.
+///
+/// See description of \c SocketSessionForwarder for the expected interface.
+class BaseSocketSessionForwarder  {
+protected:
+    BaseSocketSessionForwarder() {}
+
+public:
+    virtual ~BaseSocketSessionForwarder() {}
+    virtual void connectToReceiver() = 0;
+    virtual void close() = 0;
+    virtual void push(int sock, int family, int type, int protocol,
+                      const struct sockaddr& local_end,
+                      const struct sockaddr& remote_end,
+                      const void* data, size_t data_len) = 0;
+};
+
 /// The forwarder of socket sessions
 ///
 /// An object of this class maintains a UNIX domain socket (normally expected
@@ -164,7 +195,9 @@ public:
 ///
 /// See the description of \ref SocketSessionUtility for other details of how
 /// the session forwarding works.
-class SocketSessionForwarder : boost::noncopyable {
+class SocketSessionForwarder : boost::noncopyable,
+                               public BaseSocketSessionForwarder
+{
 public:
     /// The constructor.
     ///
@@ -212,7 +245,7 @@ public:
     ///
     /// If a connection has been established, it's automatically closed in
     /// the destructor.
-    ~SocketSessionForwarder();
+    virtual ~SocketSessionForwarder();
 
     /// Establish a connection to the receiver.
     ///
@@ -224,7 +257,7 @@ public:
     /// \exception BadValue The method is called while an already
     /// established connection is still active.
     /// \exception SocketSessionError A system error in socket operation.
-    void connectToReceiver();
+    virtual void connectToReceiver();
 
     /// Close the connection to the receiver.
     ///
@@ -232,7 +265,7 @@ public:
     /// As long as it's met this method is exception free.
     ///
     /// \exception BadValue The connection hasn't been established.
-    void close();
+    virtual void close();
 
     /// Forward a socket session to the receiver.
     ///
@@ -276,10 +309,10 @@ public:
     /// \param data A pointer to the beginning of the memory region for the
     ///             session data
     /// \param data_len The size of the session data in bytes.
-    void push(int sock, int family, int type, int protocol,
-              const struct sockaddr& local_end,
-              const struct sockaddr& remote_end,
-              const void* data, size_t data_len);
+    virtual void push(int sock, int family, int type, int protocol,
+                      const struct sockaddr& local_end,
+                      const struct sockaddr& remote_end,
+                      const void* data, size_t data_len);
 
 private:
     struct ForwarderImpl;

+ 3 - 0
src/lib/util/unittests/Makefile.am

@@ -11,6 +11,9 @@ libutil_unittests_la_SOURCES += run_all.h run_all.cc
 libutil_unittests_la_SOURCES += textdata.h
 endif
 
+# For now, this isn't needed for libutil_unittests
+EXTRA_DIST = mock_socketsession.h
+
 libutil_unittests_la_CPPFLAGS = $(AM_CPPFLAGS)
 if HAVE_GTEST
 libutil_unittests_la_CPPFLAGS += $(GTEST_INCLUDES)

+ 154 - 0
src/lib/util/unittests/mock_socketsession.h

@@ -0,0 +1,154 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UTIL_UNITTESTS_MOCKSOCKETSESSION_H
+#define __UTIL_UNITTESTS_MOCKSOCKETSESSION_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <util/io/socketsession.h>
+#include <util/io/sockaddr_util.h>
+
+#include <cassert>
+#include <cstring>
+#include <vector>
+
+#include <sys/socket.h>
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Mock socket session forwarder.
+///
+/// It emulates the behavior of SocketSessionForwarder without involving
+/// network communication, and allowing the tester to customize the behavior
+/// and to examine forwarded data afterwards.
+class MockSocketSessionForwarder :
+    public isc::util::io::BaseSocketSessionForwarder
+{
+public:
+    MockSocketSessionForwarder() :
+        is_connected_(false), connect_ok_(true), push_ok_(true),
+        close_ok_(true)
+    {}
+
+    virtual void connectToReceiver() {
+        if (!connect_ok_) {
+            isc_throw(isc::util::io::SocketSessionError, "socket session "
+                      "forwarding connection disabled for test");
+        }
+        if (is_connected_) {
+            isc_throw(isc::util::io::SocketSessionError, "duplicate connect");
+        }
+        is_connected_ = true;
+    }
+    virtual void close() {
+        if (!is_connected_) {
+            isc_throw(isc::util::io::SocketSessionError, "duplicate close");
+        }
+        is_connected_ = false;
+    }
+
+    // Pushing a socket session.  It copies the given session data
+    // so that the test code can check the values later via the getter
+    // methods.  Complete deep copy will be created, so the caller doesn't
+    // have to keep the parameters valid after the call to this method.
+    virtual void push(int sock, int family, int type, int protocol,
+                      const struct sockaddr& local_end,
+                      const struct sockaddr& remote_end,
+                      const void* data, size_t data_len)
+    {
+        if (!push_ok_) {
+            isc_throw(isc::util::io::SocketSessionError,
+                       "socket session forwarding is disabled for test");
+        }
+        if (!is_connected_) {
+            isc_throw(isc::util::io::SocketSessionError,
+                       "socket session is being pushed before connected");
+        }
+
+        // Copy parameters for later checks
+        pushed_sock_ = sock;
+        pushed_family_ = family;
+        pushed_type_ = type;
+        pushed_protocol_ = protocol;
+        assert(io::internal::getSALength(local_end) <=
+               sizeof(pushed_local_end_ss_));
+        std::memcpy(&pushed_local_end_ss_, &local_end,
+                    io::internal::getSALength(local_end));
+        assert(io::internal::getSALength(remote_end) <=
+               sizeof(pushed_remote_end_ss_));
+        std::memcpy(&pushed_remote_end_ss_, &remote_end,
+                    io::internal::getSALength(remote_end));
+        pushed_data_.resize(data_len);
+        std::memcpy(&pushed_data_[0], data, data_len);
+    }
+
+    // Allow the test code to check if the connection is established.
+    bool isConnected() const { return (is_connected_); }
+
+    // Allow the test code to customize the forwarder behavior wrt whether
+    // a specific operation should succeed or fail.
+    void disableConnect() { connect_ok_ = false; }
+    void enableConnect() { connect_ok_ = true; }
+    void disableClose() { close_ok_ = false; }
+    void disablePush() { push_ok_ = false; }
+    void enablePush() { push_ok_ = true; }
+
+    // Read-only accessors to recorded parameters to the previous successful
+    // call to push().  Return values are undefined if there has been no
+    // successful call to push().
+    // Note that we use convertSockAddr() to convert sockaddr_storage to
+    // sockaddr.  It should be safe since we use the storage in its literal
+    // sense; it was originally filled with the binary image of another
+    // sockaddr structure, and we are going to return the image opaquely
+    // as a sockaddr structure without touching the data.
+    int getPushedSock() const { return (pushed_sock_); }
+    int getPushedFamily() const { return (pushed_family_); }
+    int getPushedType() const { return (pushed_type_); }
+    int getPushedProtocol() const { return (pushed_protocol_); }
+    const struct sockaddr& getPushedLocalend() const {
+        return (*io::internal::convertSockAddr(&pushed_local_end_ss_));
+    }
+    const struct sockaddr& getPushedRemoteend() const {
+        return (*io::internal::convertSockAddr(&pushed_remote_end_ss_));
+    }
+    const std::vector<uint8_t>& getPushedData() const {
+        return (pushed_data_);
+    }
+
+private:
+    bool is_connected_;
+    bool connect_ok_;
+    bool push_ok_;
+    bool close_ok_;
+    int pushed_sock_;
+    int pushed_family_;
+    int pushed_type_;
+    int pushed_protocol_;
+    struct sockaddr_storage pushed_local_end_ss_;
+    struct sockaddr_storage pushed_remote_end_ss_;
+    std::vector<uint8_t> pushed_data_;
+};
+
+} // end of unittests
+} // end of util
+} // end of isc
+#endif  // __UTIL_UNITTESTS_MOCKSOCKETSESSION_H
+
+// Local Variables:
+// mode: c++
+// End: