Browse Source

Merge branch 'work/sock/cppclient' into scfinal

Michal 'vorner' Vaner 13 years ago
parent
commit
43fda10b4f

+ 3 - 0
src/bin/auth/main.cc

@@ -49,6 +49,7 @@
 #include <asiolink/asiolink.h>
 #include <log/logger_support.h>
 #include <server_common/keyring.h>
+#include <server_common/socket_request.h>
 
 using namespace std;
 using namespace isc::asiodns;
@@ -168,6 +169,8 @@ main(int argc, char* argv[]) {
                                              my_config_handler,
                                              my_command_handler, false);
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_CONFIG_CHANNEL_ESTABLISHED);
+        // Initialize the Socket Requestor
+        isc::server_common::SocketRequestor::init(*config_session);
 
         xfrin_session = new Session(io_service.get_io_service());
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_XFRIN_CHANNEL_CREATED);

+ 19 - 1
src/bin/auth/tests/auth_srv_unittest.cc

@@ -41,6 +41,7 @@
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 #include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
 
 using namespace std;
 using namespace isc::cc;
@@ -68,7 +69,8 @@ protected:
     AuthSrvTest() :
         dnss_(ios_, NULL, NULL, NULL),
         server(true, xfrout),
-        rrclass(RRClass::IN())
+        rrclass(RRClass::IN()),
+        sock_requestor_(dnss_, address_store_, 53210)
     {
         server.setDNSService(dnss_);
         server.setXfrinSession(&notify_session);
@@ -85,6 +87,8 @@ protected:
     AuthSrv server;
     const RRClass rrclass;
     vector<uint8_t> response_data;
+    AddressList address_store_;
+    TestSocketRequestor sock_requestor_;
 };
 
 // A helper function that builds a response to version.bind/TXT/CH that
@@ -887,6 +891,20 @@ TEST_F(AuthSrvTest, stop) {
 
 TEST_F(AuthSrvTest, listenAddresses) {
     isc::testutils::portconfig::listenAddresses(server);
+    // Check it requests the correct addresses
+    const char* tokens[] = {
+        "TCP:127.0.0.1:53210:1",
+        "UDP:127.0.0.1:53210:2",
+        "TCP:::1:53210:3",
+        "UDP:::1:53210:4",
+        NULL
+    };
+    sock_requestor_.checkTokens(tokens, sock_requestor_.given_tokens_,
+                                "Given tokens");
+    // It returns back to empty set of addresses afterwards, so
+    // they should be released
+    sock_requestor_.checkTokens(tokens, sock_requestor_.released_tokens_,
+                                "Released tokens");
 }
 
 }

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

@@ -31,6 +31,7 @@
 
 #include <testutils/mockups.h>
 #include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
 
 using namespace isc::dns;
 using namespace isc::data;
@@ -44,7 +45,8 @@ protected:
     AuthConfigTest() :
         dnss_(ios_, NULL, NULL, NULL),
         rrclass(RRClass::IN()),
-        server(true, xfrout)
+        server(true, xfrout),
+        sock_requestor_(dnss_, address_store_, 53210)
     {
         server.setDNSService(dnss_);
     }
@@ -53,6 +55,9 @@ protected:
     const RRClass rrclass;
     MockXfroutClient xfrout;
     AuthSrv server;
+    isc::server_common::portconfig::AddressList address_store_;
+private:
+    isc::testutils::TestSocketRequestor sock_requestor_;
 };
 
 TEST_F(AuthConfigTest, datasourceConfig) {

+ 3 - 0
src/bin/resolver/main.cc

@@ -41,6 +41,8 @@
 #include <cc/data.h>
 #include <config/ccsession.h>
 
+#include <server_common/socket_request.h>
+
 #include <xfr/xfrout_client.h>
 
 #include <auth/change_user.h>
@@ -209,6 +211,7 @@ main(int argc, char* argv[]) {
         config_session = new ModuleCCSession(specfile, *cc_session,
                                              my_config_handler,
                                              my_command_handler);
+        isc::server_common::SocketRequestor::init(*config_session);
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_CONFIG_CHANNEL);
 
         // FIXME: This does not belong here, but inside Boss

+ 21 - 1
src/bin/resolver/tests/resolver_config_unittest.cc

@@ -49,6 +49,7 @@
 #include <dns/tests/unittest_util.h>
 #include <testutils/srv_test.h>
 #include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
 
 using namespace std;
 using boost::scoped_ptr;
@@ -81,7 +82,10 @@ protected:
     scoped_ptr<const IOMessage> query_message;
     scoped_ptr<const Client> client;
     scoped_ptr<const RequestContext> request;
-    ResolverConfig() : dnss(ios, NULL, NULL, NULL) {
+    ResolverConfig() :
+        dnss(ios, NULL, NULL, NULL),
+        sock_requestor_(dnss, address_store_, 53210)
+    {
         server.setDNSService(dnss);
     }
     const RequestContext& createRequest(const string& source_addr) {
@@ -96,6 +100,8 @@ protected:
         return (*request);
     }
     void invalidTest(const string &JSON, const string& name);
+    isc::server_common::portconfig::AddressList address_store_;
+    isc::testutils::TestSocketRequestor sock_requestor_;
 };
 
 TEST_F(ResolverConfig, forwardAddresses) {
@@ -310,6 +316,20 @@ TEST_F(ResolverConfig, invalidForwardAddresses) {
 // Try setting the addresses directly
 TEST_F(ResolverConfig, listenAddresses) {
     isc::testutils::portconfig::listenAddresses(server);
+    // Check it requests the correct addresses
+    const char* tokens[] = {
+        "TCP:127.0.0.1:53210:1",
+        "UDP:127.0.0.1:53210:2",
+        "TCP:::1:53210:3",
+        "UDP:::1:53210:4",
+        NULL
+    };
+    sock_requestor_.checkTokens(tokens, sock_requestor_.given_tokens_,
+                                "Given tokens");
+    // It returns back to empty set of addresses afterwards, so
+    // they should be released
+    sock_requestor_.checkTokens(tokens, sock_requestor_.released_tokens_,
+                                "Released tokens");
 }
 
 // Try setting some addresses and a rollback

+ 1 - 0
src/lib/asiodns/Makefile.am

@@ -25,6 +25,7 @@ libasiodns_la_SOURCES += dns_service.cc dns_service.h
 libasiodns_la_SOURCES += tcp_server.cc tcp_server.h
 libasiodns_la_SOURCES += udp_server.cc udp_server.h
 libasiodns_la_SOURCES += io_fetch.cc io_fetch.h
+libasiodns_la_SOURCES += logger.h logger.cc
 
 nodist_libasiodns_la_SOURCES = asiodns_messages.cc asiodns_messages.h
 

+ 8 - 0
src/lib/asiodns/asiodns_messages.mes

@@ -14,6 +14,14 @@
 
 $NAMESPACE isc::asiodns
 
+% ASIODNS_FD_ADD_TCP adding a new TCP server by opened fd %1
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+
+% ASIODNS_FD_ADD_UDP adding a new UDP server by opened fd %1
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+
 % ASIODNS_FETCH_COMPLETED upstream fetch to %1(%2) has now completed
 A debug message, this records that the upstream fetch (a query made by the
 resolver on behalf of its client) to the specified address has completed.

+ 15 - 0
src/lib/asiodns/dns_service.cc

@@ -78,6 +78,13 @@ public:
     DNSLookup *lookup_;
     DNSAnswer *answer_;
 
+    template<class Ptr, class Server> void addServerFromFD(int fd, int af) {
+        Ptr server(new Server(io_service_.get_io_service(), fd, af, checkin_,
+                              lookup_, answer_));
+        (*server)();
+        servers_.push_back(server);
+    }
+
     void addServer(uint16_t port, const asio::ip::address& address) {
         try {
             dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
@@ -189,6 +196,14 @@ DNSService::addServer(uint16_t port, const std::string& address) {
     impl_->addServer(port, convertAddr(address));
 }
 
+void DNSService::addServerTCPFromFD(int fd, int af) {
+    impl_->addServerFromFD<DNSServiceImpl::TCPServerPtr, TCPServer>(fd, af);
+}
+
+void DNSService::addServerUDPFromFD(int fd, int af) {
+    impl_->addServerFromFD<DNSServiceImpl::UDPServerPtr, UDPServer>(fd, af);
+}
+
 void
 DNSService::clearServers() {
     BOOST_FOREACH(const DNSServiceImpl::DNSServerPtr& s, impl_->servers_) {

+ 36 - 0
src/lib/asiodns/dns_service.h

@@ -88,6 +88,42 @@ public:
     /// \brief Add another server to the service
     void addServer(uint16_t port, const std::string &address);
     void addServer(const char &port, const std::string &address);
+
+    /// \brief Add another TCP server/listener to the service from already
+    /// opened file descriptor
+    ///
+    /// Adds a new TCP server using an already opened file descriptor (eg. it
+    /// only wraps it so the file descriptor is usable within the event loop).
+    /// The file descriptor must be associated with a TCP socket of the given
+    /// address family that is bound to an appropriate port (and possibly a
+    /// specific address) and is ready for listening to new connection
+    /// requests but has not actually started listening.
+    ///
+    /// \param fd the file descriptor to be used.
+    /// \param af the address family of the file descriptor. Must be either
+    ///     AF_INET or AF_INET6.
+    /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6.
+    /// \throw isc::asiolink::IOError when a low-level error happens, like the
+    ///     fd is not a valid descriptor or it can't be listened on.
+    void addServerTCPFromFD(int fd, int af);
+
+    /// \brief Add another UDP server to the service from already opened
+    ///    file descriptor
+    ///
+    /// Adds a new UDP server using an already opened file descriptor (eg. it
+    /// only wraps it so the file descriptor is usable within the event loop).
+    /// The file descriptor must be associated with a UDP socket of the given
+    /// address family that is bound to an appropriate port (and possibly a
+    /// specific address).
+    ///
+    /// \param fd the file descriptor to be used.
+    /// \param af the address family of the file descriptor. Must be either
+    ///     AF_INET or AF_INET6.
+    /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6.
+    /// \throw isc::asiolink::IOError when a low-level error happens, like the
+    ///     fd is not a valid descriptor or it can't be listened on.
+    void addServerUDPFromFD(int fd, int af);
+
     /// \brief Remove all servers from the service
     void clearServers();
 

+ 1 - 7
src/lib/asiodns/io_fetch.cc

@@ -38,15 +38,13 @@
 #include <dns/messagerenderer.h>
 #include <dns/opcode.h>
 #include <dns/rcode.h>
-#include <log/logger.h>
-#include <log/macros.h>
 
-#include <asiodns/asiodns_messages.h>
 #include <asiodns/io_fetch.h>
 
 #include <util/buffer.h>
 #include <util/random/qid_gen.h>
 
+#include <asiodns/logger.h>
 
 using namespace asio;
 using namespace isc::asiolink;
@@ -59,10 +57,6 @@ using namespace std;
 namespace isc {
 namespace asiodns {
 
-/// Use the ASIO logger
-
-isc::log::Logger logger("asiolink");
-
 // Log debug verbosity
 
 const int DBG_IMPORTANT = DBGLVL_TRACE_BASIC;

+ 25 - 0
src/lib/asiodns/logger.cc

@@ -0,0 +1,25 @@
+// 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.
+
+#include <asiodns/logger.h>
+
+namespace isc {
+namespace asiodns {
+
+/// Use the ASIO logger
+
+isc::log::Logger logger("asiodns");
+
+}
+}

+ 26 - 0
src/lib/asiodns/logger.h

@@ -0,0 +1,26 @@
+// 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.
+
+#include <log/logger.h>
+#include <log/macros.h>
+#include <log/log_dbglevels.h>
+#include <asiodns/asiodns_messages.h>
+
+namespace isc {
+namespace asiodns {
+
+extern isc::log::Logger logger;
+
+}
+}

+ 27 - 2
src/lib/asiodns/tcp_server.cc

@@ -29,8 +29,8 @@
 #include <asiolink/dummy_io_cb.h>
 #include <asiolink/tcp_endpoint.h>
 #include <asiolink/tcp_socket.h>
-#include <tcp_server.h>
-
+#include <asiodns/tcp_server.h>
+#include <asiodns/logger.h>
 
 using namespace asio;
 using asio::ip::udp;
@@ -69,6 +69,31 @@ TCPServer::TCPServer(io_service& io_service,
     acceptor_->listen();
 }
 
+TCPServer::TCPServer(io_service& io_service, int fd, int af,
+                     const SimpleCallback* checkin,
+                     const DNSLookup* lookup,
+                     const DNSAnswer* answer) :
+    io_(io_service), done_(false),
+    checkin_callback_(checkin), lookup_callback_(lookup),
+    answer_callback_(answer)
+{
+    if (af != AF_INET && af != AF_INET6) {
+        isc_throw(InvalidParameter, "Address family must be either AF_INET "
+                  "or AF_INET6, not " << af);
+    }
+    LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_TCP).arg(fd);
+
+    try {
+        acceptor_.reset(new tcp::acceptor(io_service));
+        acceptor_->assign(af == AF_INET6 ? tcp::v6() : tcp::v4(), fd);
+        acceptor_->listen();
+    } catch (const std::exception& exception) {
+        // Whatever the thing throws, it is something from ASIO and we convert
+        // it
+        isc_throw(IOError, exception.what());
+    }
+}
+
 void
 TCPServer::operator()(asio::error_code ec, size_t length) {
     /// Because the coroutine reentry block is implemented as

+ 14 - 0
src/lib/asiodns/tcp_server.h

@@ -43,6 +43,20 @@ public:
                        const DNSLookup* lookup = NULL,
                        const DNSAnswer* answer = NULL);
 
+    /// \brief Constructor
+    /// \param io_service the asio::io_service to work with
+    /// \param fd the file descriptor of opened TCP socket
+    /// \param af address family of the socket, either AF_INET or AF_INET6
+    /// \param checkin the callbackprovider for non-DNS events
+    /// \param lookup the callbackprovider for DNS lookup events
+    /// \param answer the callbackprovider for DNS answer events
+    /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
+    /// \throw isc::asiolink::IOError when a low-level error happens, like the
+    ///     fd is not a valid descriptor or it can't be listened on.
+    TCPServer(asio::io_service& io_service, int fd, int af,
+              const isc::asiolink::SimpleCallback* checkin = NULL,
+              const DNSLookup* lookup = NULL, const DNSAnswer* answer = NULL);
+
     void operator()(asio::error_code ec = asio::error_code(),
                     size_t length = 0);
     void asyncLookup();

+ 250 - 104
src/lib/asiodns/tests/dns_server_unittest.cc

@@ -23,6 +23,8 @@
 #include <asiodns/dns_answer.h>
 #include <asiodns/dns_lookup.h>
 #include <string>
+#include <cstring>
+#include <cerrno>
 #include <csignal>
 #include <unistd.h> //for alarm
 
@@ -30,6 +32,8 @@
 #include <boost/bind.hpp>
 #include <boost/function.hpp>
 
+#include <sys/types.h>
+#include <sys/socket.h>
 
 /// The following tests focus on stop interface for udp and
 /// tcp server, there are lots of things can be shared to test
@@ -70,11 +74,12 @@ using namespace isc::asiodns;
 using namespace asio;
 
 namespace {
-static const std::string server_ip = "127.0.0.1";
+const char* const server_ip = "::1";
 const int server_port = 5553;
+const char* const server_port_str = "5553";
 //message client send to udp server, which isn't dns package
 //just for simple testing
-static const std::string query_message("BIND10 is awesome");
+const char* const query_message = "BIND10 is awesome";
 
 // \brief provide capacity to derived class the ability
 // to stop DNSServer at certern point
@@ -200,15 +205,15 @@ class SimpleClient : public ServerStopper {
 
 class UDPClient : public SimpleClient {
     public:
-    //After 1 seconds without feedback client will stop wait
-    static const unsigned int server_time_out = 1;
+    //After 1 second without feedback client will stop wait
+    static const unsigned int SERVER_TIME_OUT = 1;
 
     UDPClient(asio::io_service& service, const ip::udp::endpoint& server) :
-        SimpleClient(service, server_time_out)
+        SimpleClient(service, SERVER_TIME_OUT)
     {
         server_ = server;
         socket_.reset(new ip::udp::socket(service));
-        socket_->open(ip::udp::v4());
+        socket_->open(ip::udp::v6());
     }
 
 
@@ -243,13 +248,13 @@ class TCPClient : public SimpleClient {
     public:
     // after 2 seconds without feedback client will stop wait,
     // this includes connect, send message and recevice message
-    static const unsigned int server_time_out = 2;
+    static const unsigned int SERVER_TIME_OUT = 2;
     TCPClient(asio::io_service& service, const ip::tcp::endpoint& server)
-        : SimpleClient(service, server_time_out)
+        : SimpleClient(service, SERVER_TIME_OUT)
     {
         server_ = server;
         socket_.reset(new ip::tcp::socket(service));
-        socket_->open(ip::tcp::v4());
+        socket_->open(ip::tcp::v6());
     }
 
 
@@ -305,33 +310,40 @@ class TCPClient : public SimpleClient {
     uint16_t data_to_send_len_;
 };
 
-
-
-// \brief provide the context which including two client and
-// two server, udp client will only communicate with udp server, same for tcp client
-class DNSServerTest : public::testing::Test {
+// \brief provide the context which including two clients and
+// two servers, UDP client will only communicate with UDP server, same for TCP
+// client
+//
+// This is only the active part of the test. We run the test case twice, once
+// for each type of initialization (once when giving it the address and port,
+// once when giving the file descriptor), to ensure it works both ways exactly
+// the same.
+class DNSServerTestBase : public::testing::Test {
     protected:
-        void SetUp() {
-            ip::address server_address = ip::address::from_string(server_ip);
-            checker_ = new DummyChecker();
-            lookup_ = new DummyLookup();
-            answer_ = new SimpleAnswer();
-            udp_server_ = new UDPServer(service, server_address, server_port,
-                    checker_, lookup_, answer_);
-            udp_client_ = new UDPClient(service,
-                    ip::udp::endpoint(server_address,
-                        server_port));
-            tcp_server_ = new TCPServer(service, server_address, server_port,
-                    checker_, lookup_, answer_);
-            tcp_client_ = new TCPClient(service,
-                    ip::tcp::endpoint(server_address,
-                        server_port));
+        DNSServerTestBase() :
+            server_address_(ip::address::from_string(server_ip)),
+            checker_(new DummyChecker()),
+            lookup_(new DummyLookup()),
+            answer_(new SimpleAnswer()),
+            udp_client_(new UDPClient(service,
+                                      ip::udp::endpoint(server_address_,
+                                                         server_port))),
+            tcp_client_(new TCPClient(service,
+                                      ip::tcp::endpoint(server_address_,
+                                                        server_port))),
+            udp_server_(NULL),
+            tcp_server_(NULL)
+        {
+            current_service = &service;
         }
 
-
-        void TearDown() {
-            udp_server_->stop();
-            tcp_server_->stop();
+        ~ DNSServerTestBase() {
+            if (udp_server_ != NULL) {
+                udp_server_->stop();
+            }
+            if (tcp_server_ != NULL) {
+                tcp_server_->stop();
+            }
             delete checker_;
             delete lookup_;
             delete answer_;
@@ -339,22 +351,26 @@ class DNSServerTest : public::testing::Test {
             delete udp_client_;
             delete tcp_server_;
             delete tcp_client_;
+            // No delete here. The service is not allocated by new, but as our
+            // member. This only references it, so just cleaning the pointer.
+            current_service = NULL;
         }
 
-
         void testStopServerByStopper(DNSServer* server, SimpleClient* client,
                 ServerStopper* stopper)
         {
-            static const unsigned int io_service_time_out = 5;
+            static const unsigned int IO_SERVICE_TIME_OUT = 5;
             io_service_is_time_out = false;
             stopper->setServerToStop(server);
             (*server)();
             client->sendDataThenWaitForFeedback(query_message);
-            // Since thread hasn't been introduced into the tool box, using signal
-            // to make sure run function will eventually return even server stop
-            // failed
-            void (*prev_handler)(int) = std::signal(SIGALRM, DNSServerTest::stopIOService);
-            alarm(io_service_time_out);
+            // Since thread hasn't been introduced into the tool box, using
+            // signal to make sure run function will eventually return even
+            // server stop failed
+            void (*prev_handler)(int) =
+                std::signal(SIGALRM, DNSServerTestBase::stopIOService);
+            current_service = &service;
+            alarm(IO_SERVICE_TIME_OUT);
             service.run();
             service.reset();
             //cancel scheduled alarm
@@ -362,71 +378,155 @@ class DNSServerTest : public::testing::Test {
             std::signal(SIGALRM, prev_handler);
         }
 
-
         static void stopIOService(int _no_use_parameter) {
             io_service_is_time_out = true;
-            service.stop();
+            if (current_service != NULL) {
+                current_service->stop();
+            }
         }
 
         bool serverStopSucceed() const {
             return (!io_service_is_time_out);
         }
 
-        DummyChecker* checker_;
-        DummyLookup*  lookup_;
-        SimpleAnswer* answer_;
+        asio::io_service service;
+        const ip::address server_address_;
+        DummyChecker* const checker_;
+        DummyLookup*  const lookup_;
+        SimpleAnswer* const answer_;
+        UDPClient*    const udp_client_;
+        TCPClient*    const tcp_client_;
         UDPServer*    udp_server_;
-        UDPClient*    udp_client_;
-        TCPClient*    tcp_client_;
         TCPServer*    tcp_server_;
 
         // To access them in signal handle function, the following
         // variables have to be static.
-        static asio::io_service service;
+        static asio::io_service* current_service;
         static bool io_service_is_time_out;
 };
 
-bool DNSServerTest::io_service_is_time_out = false;
-asio::io_service DNSServerTest::service;
+// Initialization with name and port
+class AddrPortInit : public DNSServerTestBase {
+protected:
+    AddrPortInit() {
+        udp_server_ = new UDPServer(service, server_address_, server_port,
+                                    checker_, lookup_, answer_);
+        tcp_server_ = new TCPServer(service, server_address_, server_port,
+                                    checker_, lookup_, answer_);
+    }
+};
+
+// Initialization by the file descriptor
+class FdInit : public DNSServerTestBase {
+private:
+    // Opens the file descriptor for us
+    // It uses the low-level C api, as it seems to be the easiest way to get
+    // a raw file descriptor. It also is what the socket creator does and this
+    // API is aimed to it.
+    int getFd(int type) {
+        struct addrinfo hints;
+        memset(&hints, 0, sizeof(hints));
+        hints.ai_family = AF_UNSPEC;
+        hints.ai_socktype = type;
+        hints.ai_protocol = (type == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP;
+        hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST;
+
+        struct addrinfo* res;
+        const int error = getaddrinfo(server_ip, server_port_str,
+                                      &hints, &res);
+        if (error != 0) {
+            isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
+        }
+
+        int sock;
+        const int on(1);
+        // Go as far as you can and stop on failure
+        // Create the socket
+        // set the options
+        // and bind it
+        const bool failed((sock = socket(res->ai_family, res->ai_socktype,
+                                         res->ai_protocol)) == -1 ||
+                          setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on,
+                                     sizeof(on)) == -1 ||
+                          bind(sock, res->ai_addr, res->ai_addrlen) == -1);
+        // No matter if it succeeded or not, free the address info
+        freeaddrinfo(res);
+        if (failed) {
+            if (sock != -1) {
+                close(sock);
+            }
+            return (-1);
+        } else {
+            return (sock);
+        }
+    }
+protected:
+    // Using SetUp here so we can ASSERT_*
+    void SetUp() {
+        const int fdUDP(getFd(SOCK_DGRAM));
+        ASSERT_NE(-1, fdUDP) << strerror(errno);
+        udp_server_ = new UDPServer(service, fdUDP, AF_INET6, checker_,
+                                    lookup_, answer_);
+        const int fdTCP(getFd(SOCK_STREAM));
+        ASSERT_NE(-1, fdTCP) << strerror(errno);
+        tcp_server_ = new TCPServer(service, fdTCP, AF_INET6, checker_,
+                                    lookup_, answer_);
+    }
+};
+
+// This makes it the template as gtest wants it.
+template<class Parent>
+class DNSServerTest : public Parent { };
+
+typedef ::testing::Types<AddrPortInit, FdInit> ServerTypes;
+TYPED_TEST_CASE(DNSServerTest, ServerTypes);
+
+bool DNSServerTestBase::io_service_is_time_out = false;
+asio::io_service* DNSServerTestBase::current_service(NULL);
 
 // Test whether server stopped successfully after client get response
 // client will send query and start to wait for response, once client
 // get response, udp server will be stopped, the io service won't quit
 // if udp server doesn't stop successfully.
-TEST_F(DNSServerTest, stopUDPServerAfterOneQuery) {
-    testStopServerByStopper(udp_server_, udp_client_, udp_client_);
-    EXPECT_EQ(query_message, udp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopUDPServerAfterOneQuery) {
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+                                  this->udp_client_);
+    EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 // Test whether udp server stopped successfully before server start to serve
-TEST_F(DNSServerTest, stopUDPServerBeforeItStartServing) {
-    udp_server_->stop();
-    testStopServerByStopper(udp_server_, udp_client_, udp_client_);
-    EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopUDPServerBeforeItStartServing) {
+    this->udp_server_->stop();
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+                                  this->udp_client_);
+    EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 
 // Test whether udp server stopped successfully during message check
-TEST_F(DNSServerTest, stopUDPServerDuringMessageCheck) {
-    testStopServerByStopper(udp_server_, udp_client_, checker_);
-    EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopUDPServerDuringMessageCheck) {
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+                                  this->checker_);
+    EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 // Test whether udp server stopped successfully during query lookup
-TEST_F(DNSServerTest, stopUDPServerDuringQueryLookup) {
-    testStopServerByStopper(udp_server_, udp_client_, lookup_);
-    EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+                                  this->lookup_);
+    EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 // Test whether udp server stopped successfully during composing answer
-TEST_F(DNSServerTest, stopUDPServerDuringPrepareAnswer) {
-    testStopServerByStopper(udp_server_, udp_client_, answer_);
-    EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopUDPServerDuringPrepareAnswer) {
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+                                  this->answer_);
+    EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 static void stopServerManyTimes(DNSServer *server, unsigned int times) {
@@ -437,67 +537,113 @@ static void stopServerManyTimes(DNSServer *server, unsigned int times) {
 
 // Test whether udp server stop interface can be invoked several times without
 // throw any exception
-TEST_F(DNSServerTest, stopUDPServeMoreThanOnce) {
+TYPED_TEST(DNSServerTest, stopUDPServeMoreThanOnce) {
     ASSERT_NO_THROW({
         boost::function<void()> stop_server_3_times
-            = boost::bind(stopServerManyTimes, udp_server_, 3);
-        udp_client_->setGetFeedbackCallback(stop_server_3_times);
-        testStopServerByStopper(udp_server_, udp_client_, udp_client_);
-        EXPECT_EQ(query_message, udp_client_->getReceivedData());
+            = boost::bind(stopServerManyTimes, this->udp_server_, 3);
+        this->udp_client_->setGetFeedbackCallback(stop_server_3_times);
+        this->testStopServerByStopper(this->udp_server_,
+                                      this->udp_client_, this->udp_client_);
+        EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
     });
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 
-TEST_F(DNSServerTest, stopTCPServerAfterOneQuery) {
-    testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
-    EXPECT_EQ(query_message, tcp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopTCPServerAfterOneQuery) {
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+                                  this->tcp_client_);
+    EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 
 // Test whether tcp server stopped successfully before server start to serve
-TEST_F(DNSServerTest, stopTCPServerBeforeItStartServing) {
-    tcp_server_->stop();
-    testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
-    EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopTCPServerBeforeItStartServing) {
+    this->tcp_server_->stop();
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+                                  this->tcp_client_);
+    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 
 // Test whether tcp server stopped successfully during message check
-TEST_F(DNSServerTest, stopTCPServerDuringMessageCheck) {
-    testStopServerByStopper(tcp_server_, tcp_client_, checker_);
-    EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopTCPServerDuringMessageCheck) {
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+                                  this->checker_);
+    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 // Test whether tcp server stopped successfully during query lookup
-TEST_F(DNSServerTest, stopTCPServerDuringQueryLookup) {
-    testStopServerByStopper(tcp_server_, tcp_client_, lookup_);
-    EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopTCPServerDuringQueryLookup) {
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+                                  this->lookup_);
+    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 // Test whether tcp server stopped successfully during composing answer
-TEST_F(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
-    testStopServerByStopper(tcp_server_, tcp_client_, answer_);
-    EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
-    EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+                                  this->answer_);
+    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 
 
 // Test whether tcp server stop interface can be invoked several times without
 // throw any exception
-TEST_F(DNSServerTest, stopTCPServeMoreThanOnce) {
+TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
     ASSERT_NO_THROW({
         boost::function<void()> stop_server_3_times
-            = boost::bind(stopServerManyTimes, tcp_server_, 3);
-        tcp_client_->setGetFeedbackCallback(stop_server_3_times);
-        testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
-        EXPECT_EQ(query_message, tcp_client_->getReceivedData());
+            = boost::bind(stopServerManyTimes, this->tcp_server_, 3);
+        this->tcp_client_->setGetFeedbackCallback(stop_server_3_times);
+        this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+                                      this->tcp_client_);
+        EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
     });
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_TRUE(this->serverStopSucceed());
+}
+
+// It raises an exception when invalid address family is passed
+TEST_F(DNSServerTestBase, invalidFamily) {
+    // We abuse DNSServerTestBase for this test, as we don't need the
+    // initialization.
+    EXPECT_THROW(UDPServer(service, 0, AF_UNIX, checker_, lookup_,
+                           answer_), isc::InvalidParameter);
+    EXPECT_THROW(TCPServer(service, 0, AF_UNIX, checker_, lookup_,
+                           answer_), isc::InvalidParameter);
+}
+
+// It raises an exception when invalid address family is passed
+TEST_F(DNSServerTestBase, invalidTCPFD) {
+    // We abuse DNSServerTestBase for this test, as we don't need the
+    // initialization.
+    /*
+     FIXME: The UDP server doesn't fail reliably with an invalid FD.
+     We need to find a way to trigger it reliably (it seems epoll
+     asio backend does fail as it tries to insert it right away, but
+     not the others, maybe we could make it run this at last on epoll-based
+     systems).
+    EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
+                           answer_), isc::asiolink::IOError);
+    */
+    EXPECT_THROW(TCPServer(service, -1, AF_INET, checker_, lookup_,
+                           answer_), isc::asiolink::IOError);
+}
+
+TEST_F(DNSServerTestBase, DISABLED_invalidUDPFD) {
+    /*
+     FIXME: The UDP server doesn't fail reliably with an invalid FD.
+     We need to find a way to trigger it reliably (it seems epoll
+     asio backend does fail as it tries to insert it right away, but
+     not the others, maybe we could make it run this at least on epoll-based
+     systems).
+    */
+    EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
+                           answer_), isc::asiolink::IOError);
 }
 
 }

+ 30 - 3
src/lib/asiodns/udp_server.cc

@@ -29,6 +29,7 @@
 #include <asiolink/udp_endpoint.h>
 #include <asiolink/udp_socket.h>
 #include "udp_server.h"
+#include "logger.h"
 
 #include <dns/opcode.h>
 
@@ -53,7 +54,7 @@ namespace asiodns {
  */
 struct UDPServer::Data {
     /*
-     * Constructor from parameters passed to UDPServer constructor.
+     * Constructors from parameters passed to UDPServer constructor.
      * This instance will not be used to retrieve and answer the actual
      * query, it will only hold parameters until we wait for the
      * first packet. But we do initialize the socket in here.
@@ -74,6 +75,26 @@ struct UDPServer::Data {
         }
         socket_->bind(udp::endpoint(addr, port));
     }
+    Data(io_service& io_service, int fd, int af, SimpleCallback* checkin,
+         DNSLookup* lookup, DNSAnswer* answer) :
+         io_(io_service), done_(false),
+         checkin_callback_(checkin),lookup_callback_(lookup),
+         answer_callback_(answer)
+    {
+        if (af != AF_INET && af != AF_INET6) {
+            isc_throw(InvalidParameter, "Address family must be either AF_INET "
+                      "or AF_INET6, not " << af);
+        }
+        LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
+        try {
+            socket_.reset(new udp::socket(io_service));
+            socket_->assign(af == AF_INET6 ? udp::v6() : udp::v4(), fd);
+        } catch (const std::exception& exception) {
+            // Whatever the thing throws, it is something from ASIO and we
+            // convert it
+            isc_throw(IOError, exception.what());
+        }
+    }
 
     /*
      * Copy constructor. Default one would probably do, but it is unnecessary
@@ -162,11 +183,17 @@ struct UDPServer::Data {
 /// The constructor. It just creates new internal state object
 /// and lets it handle the initialization.
 UDPServer::UDPServer(io_service& io_service, const ip::address& addr,
-    const uint16_t port, SimpleCallback* checkin, DNSLookup* lookup,
-    DNSAnswer* answer) :
+                     const uint16_t port, SimpleCallback* checkin,
+                     DNSLookup* lookup, DNSAnswer* answer) :
     data_(new Data(io_service, addr, port, checkin, lookup, answer))
 { }
 
+UDPServer::UDPServer(io_service& io_service, int fd, int af,
+                     SimpleCallback* checkin, DNSLookup* lookup,
+                     DNSAnswer* answer) :
+    data_(new Data(io_service, fd, af, checkin, lookup, answer))
+{ }
+
 /// The function operator is implemented with the "stackless coroutine"
 /// pattern; see internal/coroutine.h for details.
 void

+ 14 - 0
src/lib/asiodns/udp_server.h

@@ -52,6 +52,20 @@ public:
                        DNSLookup* lookup = NULL,
                        DNSAnswer* answer = NULL);
 
+    /// \brief Constructor
+    /// \param io_service the asio::io_service to work with
+    /// \param fd the file descriptor of opened UDP socket
+    /// \param af address family, either AF_INET or AF_INET6
+    /// \param checkin the callbackprovider for non-DNS events
+    /// \param lookup the callbackprovider for DNS lookup events
+    /// \param answer the callbackprovider for DNS answer events
+    /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
+    /// \throw isc::asiolink::IOError when a low-level error happens, like the
+    ///     fd is not a valid descriptor.
+    UDPServer(asio::io_service& io_service, int fd, int af,
+              isc::asiolink::SimpleCallback* checkin = NULL,
+              DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
+
     /// \brief The function operator
     void operator()(asio::error_code ec = asio::error_code(),
                     size_t length = 0);

+ 1 - 0
src/lib/server_common/Makefile.am

@@ -21,6 +21,7 @@ libserver_common_la_SOURCES = client.h client.cc
 libserver_common_la_SOURCES += keyring.h keyring.cc
 libserver_common_la_SOURCES += portconfig.h portconfig.cc
 libserver_common_la_SOURCES += logger.h logger.cc
+libserver_common_la_SOURCES += socket_request.h socket_request.cc
 nodist_libserver_common_la_SOURCES = server_common_messages.h
 nodist_libserver_common_la_SOURCES += server_common_messages.cc
 libserver_common_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la

+ 43 - 3
src/lib/server_common/portconfig.cc

@@ -14,6 +14,7 @@
 
 #include <server_common/portconfig.h>
 #include <server_common/logger.h>
+#include <server_common/socket_request.h>
 
 #include <asiolink/io_address.h>
 #include <asiodns/dns_service.h>
@@ -30,6 +31,11 @@ namespace isc {
 namespace server_common {
 namespace portconfig {
 
+// This flags disables pushing the sockets to the DNSService. It prevents
+// the clearServers() method to close the file descriptors we made up.
+// It is not presented in any header, but we use it from the tests anyway.
+bool test_mode(false);
+
 AddressList
 parseAddresses(isc::data::ConstElementPtr addresses,
                const std::string& elemName)
@@ -76,11 +82,41 @@ parseAddresses(isc::data::ConstElementPtr addresses,
 
 namespace {
 
+vector<string> current_sockets;
+
 void
 setAddresses(DNSService& service, const AddressList& addresses) {
     service.clearServers();
+    BOOST_FOREACH(const string& token, current_sockets) {
+        socketRequestor().releaseSocket(token);
+    }
+    current_sockets.clear();
     BOOST_FOREACH(const AddressPair &address, addresses) {
-        service.addServer(address.second, address.first);
+        const int af(IOAddress(address.first).getFamily());
+        // TODO: Support sharing somehow in future.
+
+        // As for now, we hardcode the application name as dummy_app, because:
+        // * we don't have a name available in our interface, which will change
+        //   soon anyway
+        // * we use the DONT_SHARE mode, so the name is irrelevant anyway
+        const SocketRequestor::SocketID
+            tcp(socketRequestor().requestSocket(SocketRequestor::TCP,
+                                                address.first, address.second,
+                                                SocketRequestor::DONT_SHARE,
+                                                "dummy_app"));
+        current_sockets.push_back(tcp.second);
+        if (!test_mode) {
+            service.addServerTCPFromFD(tcp.first, af);
+        }
+        const SocketRequestor::SocketID
+            udp(socketRequestor().requestSocket(SocketRequestor::UDP,
+                                                address.first, address.second,
+                                                SocketRequestor::DONT_SHARE,
+                                                "dummy_app"));
+        current_sockets.push_back(udp.second);
+        if (!test_mode) {
+            service.addServerUDPFromFD(udp.first, af);
+        }
     }
 }
 
@@ -117,9 +153,13 @@ installListenAddresses(const AddressList& newAddresses,
         LOG_ERROR(logger, SRVCOMM_ADDRESS_FAIL).arg(e.what());
         try {
             setAddresses(service, addressStore);
-        }
-        catch (const exception& e2) {
+        } catch (const exception& e2) {
             LOG_FATAL(logger, SRVCOMM_ADDRESS_UNRECOVERABLE).arg(e2.what());
+            // If we can't set the new ones, nor the old ones, at least
+            // releasing everything should work. If it doesn't, there isn't
+            // anything else we could do.
+            setAddresses(service, AddressList());
+            addressStore.clear();
         }
         //Anyway the new configure has problem, we need to notify configure
         //manager the new configure doesn't work

+ 10 - 4
src/lib/server_common/portconfig.h

@@ -96,10 +96,12 @@ parseAddresses(isc::data::ConstElementPtr addresses,
  *
  * If it fails to set up the new addresses, it attempts to roll back to the
  * previous addresses (but it still propagates the exception). If the rollback
- * fails as well, it aborts the application (it assumes if it can't listen
- * on the new addresses nor on the old ones, the application is useless anyway
- * and should be restarted by Boss, not to mention that the internal state is
- * probably broken).
+ * fails as well, it doesn't abort the application (to allow reconfiguration),
+ * but removes all the sockets it listened on. One of the exceptions is
+ * propagated.
+ *
+ * The ports are requested from the socket creator through boss. Therefore
+ * you need to initialize the SocketRequestor before using this function.
  *
  * \param newAddresses are the addresses you want to listen on.
  * \param addressStore is the place you store your current addresses. It is
@@ -109,7 +111,11 @@ parseAddresses(isc::data::ConstElementPtr addresses,
  *     the new sockets are handled using this dnsService (and all current
  *     sockets on the service are closed first).
  * \throw asiolink::IOError when initialization or closing of socket fails.
+ * \throw isc::server_common::SocketRequestor::Socket error when the
+ *     boss/socket creator doesn't want to give us the socket.
  * \throw std::bad_alloc when allocation fails.
+ * \throw isc::InvalidOperation when the function is called and the
+ *     SocketRequestor isn't initialized yet.
  */
 void
 installListenAddresses(const AddressList& newAddresses,

+ 45 - 0
src/lib/server_common/socket_request.cc

@@ -0,0 +1,45 @@
+// Copyright (C) 2011  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.
+
+#include "socket_request.h"
+
+namespace isc {
+namespace server_common {
+
+namespace {
+SocketRequestor* requestor(NULL);
+}
+
+SocketRequestor&
+socketRequestor() {
+    if (requestor != NULL) {
+        return (*requestor);
+    } else {
+        isc_throw(InvalidOperation, "The socket requestor is not initialized");
+    }
+}
+
+void
+SocketRequestor::initTest(SocketRequestor* new_requestor) {
+    requestor = new_requestor;
+}
+
+void
+SocketRequestor::init(config::ModuleCCSession&) {
+    isc_throw(NotImplemented,
+              "The socket requestor will be implemented in #1522");
+}
+
+}
+}

+ 189 - 0
src/lib/server_common/socket_request.h

@@ -0,0 +1,189 @@
+// Copyright (C) 2011  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 __SOCKET_REQUEST_H
+#define __SOCKET_REQUEST_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+#include <utility>
+#include <string>
+#include <stdint.h>
+
+namespace isc {
+
+namespace config {
+class ModuleCCSession;
+};
+
+namespace server_common {
+
+/// \brief A singleton class for requesting sockets
+///
+/// This class allows requesting sockets from the socket creator.
+///
+/// It is considered to be a singleton - a class which is instantiated
+/// at most once in the whole application. This is because it makes no
+/// sense to have two of them.
+///
+/// This is actually an abstract base class. There'll be one with
+/// hidden implementation and we expect the tests to create it's own
+/// subclass when needed.
+///
+/// \see socketRequestor function to access the object of this class.
+class SocketRequestor : boost::noncopyable {
+protected:
+    /// \brief Protected constructor
+    ///
+    /// The constructor is protected so this class is not created by accident
+    /// (which it can't anyway, as it has pure virtual methods, but just to
+    /// be sure).
+    SocketRequestor() {}
+public:
+    /// \brief virtual destructor
+    ///
+    /// A virtual destructor, as we have virtual methods, to make sure it is
+    /// destroyed by the destructor of the subclass. This shouldn't matter, as
+    /// a singleton class wouldn't get destroyed, but just to be sure.
+
+    virtual ~ SocketRequestor() {}
+    /// \brief A representation of received socket
+    ///
+    /// The pair holds two parts. The OS-level file descriptor acting as the
+    /// socket (you might want to use it directly with functions like recv,
+    /// or fill it into an asio socket). The other part is the token representing
+    /// the socket, which allows it to be given up again.
+    typedef std::pair<int, std::string> SocketID;
+
+    /// \brief The protocol of requested socket
+    ///
+    /// This describes which protocol the socket should have when created.
+    enum Protocol {
+        UDP,
+        TCP
+    };
+
+    /// \brief The share mode of the requested socket
+    ///
+    /// The socket creator is able to "borrow" the same socket to multiple
+    /// applications at once. However, it isn't always what is required. This
+    /// describes the restrains we want to have on our socket regarding the
+    /// sharing. Union of restriction of all requests on the given socket
+    /// is taken (so you still don't have to get your socket even if you
+    /// say SHARE_ANY, because someone else might already asked for the socket
+    /// with DONT_SHARE).
+    enum ShareMode {
+        DONT_SHARE, //< Request an exclusive ownership of the socket.
+        SHARE_SAME, //< It is possible to share the socket with anybody who
+                    //< provided the same share_name.
+        SHARE_ANY   //< Any sharing is allowed.
+    };
+
+    /// \brief Exception when we can't manipulate a socket
+    ///
+    /// This is thrown if the other side doesn't want to comply to our
+    /// requests, like when we ask for a socket already held by someone
+    /// else or ask for nonsense (releasing a socket we don't own).
+    class SocketError : public Exception {
+    public:
+        SocketError(const char* file, size_t line, const char *what) :
+            Exception(file, line, what)
+        { }
+    };
+
+    /// \brief Ask for a socket
+    ///
+    /// Asks the socket creator to give us a socket. The socket will be bound
+    /// to the given address and port.
+    ///
+    /// \param protocol specifies the protocol of the socket.
+    /// \param address to which the socket should be bound.
+    /// \param port the port to which the socket should be bound (native endian,
+    ///     not network byte order).
+    /// \param share_mode how the socket can be shared with other requests.
+    /// \param share_name the name of sharing group, relevant for SHARE_SAME
+    ///     (specified by us or someone else).
+    /// \return the socket, as a file descriptor and token representing it on
+    ///     the socket creator side.
+    /// \throw CCSessionError when we have a problem talking over the CC
+    ///     session.
+    /// \throw SocketError in case the other side doesn't want to give us
+    ///     the socket for some reason (common cases are when the socket
+    ///     can't be allocated or bound, or when the socket is claimed by
+    ///     some other application and the sharing parameters don't allow
+    ///     sharing it).
+    virtual SocketID requestSocket(Protocol protocol,
+                                   const std::string& address,
+                                   uint16_t port, ShareMode share_mode,
+                                   const std::string& share_name) = 0;
+
+    /// \brief Tell the socket creator we no longer need the socket
+    ///
+    /// Releases the identified socket. This must be called *after*
+    /// the file descriptor was closed on our side. This will allow
+    /// the remote side to either give it to some other application
+    /// or close it, depending on the situation.
+    ///
+    /// \param token the token representing the socket, as received
+    ///     in the second part of the requestSocket result.
+    /// \throw CCSessionError when we have a problem talking over the CC
+    ///     session.
+    /// \throw SocketError in case the other side doesn't like the
+    ///     release (like we're trying to release a socket that doesn't
+    ///     belong to us or exist at all).
+    virtual void releaseSocket(const std::string& token) = 0;
+
+    /// \brief Initialize the singleton object
+    ///
+    /// This creates the object that will be used to request sockets.
+    /// It can be called only once per the life of application.
+    ///
+    /// \param session the CC session that'll be used to talk to the
+    ///     socket creator.
+    /// \throw InvalidOperation when it is called more than once.
+    static void init(config::ModuleCCSession& session);
+
+    /// \brief Initialization for tests
+    ///
+    /// This is to support different subclasses in tests. It replaces
+    /// the object used by socketRequestor() function by this one provided
+    /// as parameter. The ownership is not taken, eg. it's up to the caller
+    /// to delete it when necessary.
+    ///
+    /// This is not to be used in production applications. It is meant as
+    /// an replacement of init.
+    ///
+    /// This never throws.
+    ///
+    /// \param requestor the object to be used. It can be NULL to reset to
+    ///     an "virgin" state (which acts as if initTest or init was never
+    ///     called before).
+    static void initTest(SocketRequestor* requestor);
+};
+
+/// \brief Access the requestor object.
+///
+/// This returns the singleton object for the Requestor.
+///
+/// \return the active socket requestor object.
+/// \throw InvalidOperation if the object was not yet initialized.
+/// \see SocketRequestor::init to initialize the object.
+SocketRequestor&
+socketRequestor();
+
+}
+}
+
+#endif

+ 1 - 0
src/lib/server_common/tests/Makefile.am

@@ -29,6 +29,7 @@ run_unittests_SOURCES  = run_unittests.cc
 run_unittests_SOURCES += client_unittest.cc
 run_unittests_SOURCES += portconfig_unittest.cc
 run_unittests_SOURCES += keyring_test.cc
+run_unittests_SOURCES += socket_requestor_test.cc
 nodist_run_unittests_SOURCES = data_path.h
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 118 - 4
src/lib/server_common/tests/portconfig_unittest.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <server_common/portconfig.h>
+#include <testutils/socket_request.h>
 
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
@@ -23,11 +24,13 @@
 #include <string>
 
 using namespace isc::server_common::portconfig;
+using namespace isc::server_common;
 using namespace isc::data;
 using namespace isc;
 using namespace std;
 using namespace isc::asiolink;
 using namespace isc::asiodns;
+using boost::lexical_cast;
 
 namespace {
 
@@ -129,26 +132,30 @@ TEST_F(ParseAddresses, invalid) {
 // Test fixture for installListenAddresses
 struct InstallListenAddresses : public ::testing::Test {
     InstallListenAddresses() :
-        dnss_(ios_, NULL, NULL, NULL)
+        dnss_(ios_, NULL, NULL, NULL),
+        sock_requestor_(dnss_, store_, 5288)
     {
         valid_.push_back(AddressPair("127.0.0.1", 5288));
         valid_.push_back(AddressPair("::1", 5288));
+        invalid_.push_back(AddressPair("127.0.0.1", 5288));
         invalid_.push_back(AddressPair("192.0.2.2", 1));
     }
     IOService ios_;
     DNSService dnss_;
     AddressList store_;
+    isc::testutils::TestSocketRequestor sock_requestor_;
     // We should be able to bind to these addresses
     AddressList valid_;
     // But this shouldn't work
     AddressList invalid_;
     // Check that the store_ addresses are the same as expected
-    void checkAddresses(const AddressList& expected, const string& name) {
+    void checkAddresses(const AddressList& expected, const string& name) const
+    {
         SCOPED_TRACE(name);
 
         ASSERT_EQ(expected.size(), store_.size()) <<
             "Different amount of elements, not checking content";
-        // Run in parallel trough the vectors
+        // Run in parallel through the vectors
         for (AddressList::const_iterator ei(expected.begin()),
              si(store_.begin()); ei != expected.end(); ++ei, ++si) {
             EXPECT_EQ(ei->first, si->first);
@@ -158,17 +165,46 @@ struct InstallListenAddresses : public ::testing::Test {
 };
 
 // Try switching valid addresses
+// Check the sockets are correctly requested and returned
 TEST_F(InstallListenAddresses, valid) {
     // First, bind to the valid addresses
     EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
     checkAddresses(valid_, "Valid addresses");
+    const char* tokens1[] = {
+        "TCP:127.0.0.1:5288:1",
+        "UDP:127.0.0.1:5288:2",
+        "TCP:::1:5288:3",
+        "UDP:::1:5288:4",
+        NULL
+    };
+    const char* no_tokens[] = { NULL };
+    sock_requestor_.checkTokens(tokens1, sock_requestor_.given_tokens_,
+                                "Valid given tokens 1");
+    sock_requestor_.checkTokens(no_tokens, sock_requestor_.released_tokens_,
+                                "Valid no released tokens 1");
     // TODO Maybe some test to actually connect to them
     // Try setting it back to nothing
+    sock_requestor_.given_tokens_.clear();
     EXPECT_NO_THROW(installListenAddresses(AddressList(), store_, dnss_));
     checkAddresses(AddressList(), "No addresses");
+    sock_requestor_.checkTokens(no_tokens, sock_requestor_.given_tokens_,
+                                "Valid no given tokens");
+    sock_requestor_.checkTokens(tokens1, sock_requestor_.released_tokens_,
+                                "Valid released tokens");
     // Try switching back again
     EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
     checkAddresses(valid_, "Valid addresses");
+    const char* tokens2[] = {
+        "TCP:127.0.0.1:5288:5",
+        "UDP:127.0.0.1:5288:6",
+        "TCP:::1:5288:7",
+        "UDP:::1:5288:8",
+        NULL
+    };
+    sock_requestor_.checkTokens(tokens2, sock_requestor_.given_tokens_,
+                                "Valid given tokens 2");
+    sock_requestor_.checkTokens(tokens1, sock_requestor_.released_tokens_,
+                                "Valid released tokens");
 }
 
 // Try if rollback works
@@ -176,9 +212,87 @@ TEST_F(InstallListenAddresses, rollback) {
     // Set some addresses
     EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
     checkAddresses(valid_, "Before rollback");
+    const char* tokens1[] = {
+        "TCP:127.0.0.1:5288:1",
+        "UDP:127.0.0.1:5288:2",
+        "TCP:::1:5288:3",
+        "UDP:::1:5288:4",
+        NULL
+    };
+    const char* no_tokens[] = { NULL };
+    sock_requestor_.checkTokens(tokens1, sock_requestor_.given_tokens_,
+                                "Given before rollback");
+    sock_requestor_.checkTokens(no_tokens, sock_requestor_.released_tokens_,
+                                "Released before rollback");
+    sock_requestor_.given_tokens_.clear();
     // This should not bind them, but should leave the original addresses
-    EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_), exception);
+    EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_),
+                 SocketRequestor::SocketError);
     checkAddresses(valid_, "After rollback");
+    // Now, it should have requested first pair of sockets from the invalids
+    // and, as the second failed, it should have returned them right away.
+    const char* released1[] = {
+        "TCP:127.0.0.1:5288:1",
+        "UDP:127.0.0.1:5288:2",
+        "TCP:::1:5288:3",
+        "UDP:::1:5288:4",
+        "TCP:127.0.0.1:5288:5",
+        "UDP:127.0.0.1:5288:6",
+        NULL
+    };
+    // It should request the first pair of sockets, and then request the
+    // complete set of valid addresses to rollback
+    const char* tokens2[] = {
+        "TCP:127.0.0.1:5288:5",
+        "UDP:127.0.0.1:5288:6",
+        "TCP:127.0.0.1:5288:7",
+        "UDP:127.0.0.1:5288:8",
+        "TCP:::1:5288:9",
+        "UDP:::1:5288:10",
+        NULL
+    };
+    sock_requestor_.checkTokens(tokens2, sock_requestor_.given_tokens_,
+                                "Given after rollback");
+    sock_requestor_.checkTokens(released1, sock_requestor_.released_tokens_,
+                                "Released after rollback");
+}
+
+// Try it at least releases everything when even the rollback fails.
+TEST_F(InstallListenAddresses, brokenRollback) {
+    EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
+    checkAddresses(valid_, "Before rollback");
+    // Don't check the tokens now, we already do it in rollback and valid tests
+    sock_requestor_.given_tokens_.clear();
+    sock_requestor_.break_rollback_ = true;
+    EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_),
+                 SocketRequestor::SocketError);
+    // No addresses here
+    EXPECT_TRUE(store_.empty());
+    // The first pair should be requested in the first part of the failure to
+    // bind and the second pair in the first part of rollback
+    const char* tokens[] = {
+        "TCP:127.0.0.1:5288:5",
+        "UDP:127.0.0.1:5288:6",
+        "TCP:127.0.0.1:5288:7",
+        "UDP:127.0.0.1:5288:8",
+        NULL
+    };
+    // The first set should be released, as well as all the ones we request now
+    const char* released[] = {
+        "TCP:127.0.0.1:5288:1",
+        "UDP:127.0.0.1:5288:2",
+        "TCP:::1:5288:3",
+        "UDP:::1:5288:4",
+        "TCP:127.0.0.1:5288:5",
+        "UDP:127.0.0.1:5288:6",
+        "TCP:127.0.0.1:5288:7",
+        "UDP:127.0.0.1:5288:8",
+        NULL
+    };
+    sock_requestor_.checkTokens(tokens, sock_requestor_.given_tokens_,
+                                "given");
+    sock_requestor_.checkTokens(released, sock_requestor_.released_tokens_,
+                                "released");
 }
 
 }

+ 57 - 0
src/lib/server_common/tests/socket_requestor_test.cc

@@ -0,0 +1,57 @@
+// Copyright (C) 2011  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.
+
+#include <server_common/socket_request.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::server_common;
+using namespace isc;
+
+namespace {
+
+// Check it throws an exception when it is not initialized
+TEST(SocketRequestorAccess, unitialized) {
+    // Make sure it is not initialized
+    SocketRequestor::initTest(NULL);
+    EXPECT_THROW(socketRequestor(), InvalidOperation);
+}
+
+// It returns whatever it is initialized to
+TEST(SocketRequestorAccess, initialized) {
+    // A concrete implementation that does nothing, just can exist
+    class DummyRequestor : public SocketRequestor {
+    public:
+        DummyRequestor() : SocketRequestor() {}
+        virtual void releaseSocket(const std::string&) {}
+        virtual SocketID requestSocket(Protocol, const std::string&, uint16_t,
+                                       ShareMode, const std::string&)
+        {
+            return (SocketID(0, "")); // Just to silence warnings
+        }
+    };
+    DummyRequestor requestor;
+    // Make sure it is initialized (the test way, of course)
+    SocketRequestor::initTest(&requestor);
+    // It returs the same "pointer" as inserted
+    // The casts are there as the template system seemed to get confused
+    // without them, the types should be correct even without them, but
+    // the EXPECT_EQ wanted to use long long int instead of pointers.
+    EXPECT_EQ(static_cast<const SocketRequestor*>(&requestor),
+              static_cast<const SocketRequestor*>(&socketRequestor()));
+    // Just that we don't have an invalid pointer anyway
+    SocketRequestor::initTest(NULL);
+}
+
+}

+ 1 - 1
src/lib/testutils/Makefile.am

@@ -14,4 +14,4 @@ libtestutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 libtestutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
 endif
 
-EXTRA_DIST = portconfig.h
+EXTRA_DIST = portconfig.h socket_request.h

+ 5 - 5
src/lib/testutils/portconfig.h

@@ -46,7 +46,7 @@ template<class Server>
 void
 listenAddresses(Server& server) {
     using namespace isc::server_common::portconfig;
-    // Default value should be fully recursive
+    // In this test we assume the address list is originally empty.
     EXPECT_TRUE(server.getListenAddresses().empty());
 
     // Try putting there some addresses
@@ -61,7 +61,8 @@ listenAddresses(Server& server) {
     addresses.clear();
     EXPECT_EQ(2, server.getListenAddresses().size());
 
-    // Did it return to fully recursive?
+    // If we set to an empty list next, the server configuration should
+    // become empty, too.
     server.setListenAddresses(addresses);
     EXPECT_TRUE(server.getListenAddresses().empty());
 }
@@ -95,12 +96,11 @@ listenAddressConfig(Server& server) {
     EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
     EXPECT_EQ(53210, server.getListenAddresses()[0].second);
 
-    // As this is example address, the machine should not have it on
-    // any interface
+    // This address is rejected by the test socket requestor
     config = Element::fromJSON("{"
                                "\"listen_on\": ["
                                "   {"
-                               "       \"address\": \"192.0.2.0\","
+                               "       \"address\": \"192.0.2.2\","
                                "       \"port\": 53210"
                                "   }"
                                "]"

+ 195 - 0
src/lib/testutils/socket_request.h

@@ -0,0 +1,195 @@
+// Copyright (C) 2011  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.
+
+#include <server_common/socket_request.h>
+#include <server_common/portconfig.h>
+
+#include <asiodns/asiodns.h>
+
+#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+#include <vector>
+#include <string>
+
+namespace isc {
+namespace server_common {
+namespace portconfig {
+// Access the private hidden flag
+extern bool test_mode;
+}
+}
+
+namespace testutils {
+
+/// \brief A testcase part for faking the SocketRequestor in tests
+///
+/// It's awkward to request real sockets from the real socket creator
+/// during tests (for one, because it would have to be running, for
+/// another, we need to block real ports).  If you instantiate this class in
+/// a test case, the socket requestor will be initialized to a test one which
+/// handles fake socket FDs and stores what was requested, etc.
+///
+/// Furthermore, you can check if the code requested or released the correct
+/// list of sockets using the checkTokens() method.
+///
+/// Some member variables are intentionally made public so that test cases
+/// can easily check the value of them.  We prefer convenience for tests over
+/// class integrity here.
+class TestSocketRequestor : public isc::server_common::SocketRequestor {
+public:
+    /// \brief Constructor
+    ///
+    /// \param dnss The DNS service. It is expected this gets initialized
+    ///     after the TestSocketRequestor constructor is called, as the
+    ///     TestSocketRequestor should be a base class and the service only
+    ///     a member.
+    /// \param store Address store used when cleaning up.
+    /// \param expect_port The port which is expected to be requested. If
+    ///     the application requests a different port, it is considered
+    ///     a failure.
+    TestSocketRequestor(asiodns::DNSService& dnss,
+                        server_common::portconfig::AddressList& store,
+                        uint16_t expect_port) :
+        last_token_(0), break_rollback_(false), dnss_(dnss), store_(store),
+        expect_port_(expect_port)
+    {
+        // Prepare the requestor (us) for the test
+        SocketRequestor::initTest(this);
+        // Don't manipulate the real sockets
+        server_common::portconfig::test_mode = true;
+    }
+
+    /// \brief Destructor
+    ///
+    /// Removes the addresses (if any) installed by installListenAddresses,
+    /// resets the socket requestor to uninitialized state and turns off
+    /// the portconfig test mode.
+    virtual ~TestSocketRequestor() {
+        // Make sure no sockets are left inside (if installListenAddresses
+        // wasn't used, this is NOP, so it won't hurt).
+        server_common::portconfig::AddressList list;
+        server_common::portconfig::installListenAddresses(list, store_, dnss_);
+        // Don't leave invalid pointers here
+        SocketRequestor::initTest(NULL);
+        // And return the mode
+        server_common::portconfig::test_mode = false;
+    }
+
+    /// \brief Tokens released by releaseSocket
+    ///
+    /// They are stored here by this class and you can examine them.
+    std::vector<std::string> released_tokens_;
+
+    /// \brief Tokens returned from requestSocket
+    ///
+    /// They are stored here by this class and you can examine them.
+    std::vector<std::string> given_tokens_;
+private:
+    // Last token number and fd given out
+    size_t last_token_;
+public:
+    /// \brief Support a broken rollback case
+    ///
+    /// If this is set to true, the requestSocket will throw when the
+    /// ::1 address is requested.
+    bool break_rollback_;
+
+    /// \brief Release a socket
+    ///
+    /// This only stores the token passed.
+    /// \param token The socket to release
+    void releaseSocket(const std::string& token) {
+        released_tokens_.push_back(token);
+    }
+
+    /// \brief Request a socket
+    ///
+    /// This creates a new token and fakes a new socket and returns it.
+    /// The token is stored.
+    ///
+    /// In case the address is 192.0.2.2 or if the break_rollback_ is true
+    /// and address is ::1, it throws.
+    ///
+    /// The tokens produced are in form of protocol:address:port:fd. The fds
+    /// start at 1 and increase by each successfull call.
+    ///
+    /// \param protocol The protocol to request
+    /// \param address to bind to
+    /// \param port to bind to
+    /// \param mode checked to be DONT_SHARE for now
+    /// \param name checked to be dummy_app for now
+    /// \return The token and FD
+    /// \throw SocketError as described above, to test error handling
+    SocketID requestSocket(Protocol protocol, const std::string& address,
+                           uint16_t port, ShareMode mode,
+                           const std::string& name)
+    {
+        if (address == "192.0.2.2") {
+            isc_throw(SocketError, "This address is not allowed");
+        }
+        if (address == "::1" && break_rollback_) {
+            // This is valid address, but in case we need to break the
+            // rollback, it needs to be busy or whatever
+            //
+            // We break the second address to see the first one was
+            // allocated and then returned
+            isc_throw(SocketError,
+                      "This address is available, but not for you");
+        }
+        const std::string proto(protocol == TCP ? "TCP" : "UDP");
+        const size_t number = ++ last_token_;
+        EXPECT_EQ(expect_port_, port);
+        EXPECT_EQ(DONT_SHARE, mode);
+        EXPECT_EQ("dummy_app", name);
+        const std::string token(proto + ":" + address + ":" +
+                                boost::lexical_cast<std::string>(port) + ":" +
+                                boost::lexical_cast<std::string>(number));
+        given_tokens_.push_back(token);
+        return (SocketID(number, token));
+    }
+
+    /// \brief Check the list of tokens is as expected
+    ///
+    /// Compares the expected and real tokens.
+    ///
+    /// \param expected List of the expected tokens, as NULL-terminated array
+    ///     of C strings (it is more convenient to type as a constant than to
+    ///     manually push_back all the strings to a vector).
+    /// \param real The token list that was produced by this class (usually
+    ///     either given_tokens_ or released_tokens_).
+    /// \param scope Human readable identifier of which checkTokens call it is.
+    ///     It is printed as a part of failure message.
+    void checkTokens(const char** expected,
+                     const std::vector<std::string>& real,
+                     const char* scope) const
+    {
+        SCOPED_TRACE(scope);
+        size_t position(0);
+        while (expected[position] != NULL) {
+            ASSERT_LT(position, real.size());
+            EXPECT_EQ(expected[position], real[position]) << position;
+            position ++;
+        }
+        EXPECT_EQ(position, real.size());
+    }
+
+private:
+    asiodns::DNSService& dnss_;
+    server_common::portconfig::AddressList& store_;
+    const uint16_t expect_port_;
+};
+
+}
+}

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

@@ -44,7 +44,7 @@ extern const unsigned int RA_FLAG;
 extern const unsigned int AD_FLAG;
 extern const unsigned int CD_FLAG;
 
-// The base class for Auth and Recurse test case
+/// \brief The base class for Auth and Recurse test case
 class SrvTestBase : public ::testing::Test {
 protected:
     SrvTestBase();