Michal 'vorner' Vaner 13 years ago
parent
commit
1830215f88
40 changed files with 2253 additions and 195 deletions
  1. 3 0
      src/bin/auth/main.cc
  2. 19 1
      src/bin/auth/tests/auth_srv_unittest.cc
  3. 6 1
      src/bin/auth/tests/config_unittest.cc
  4. 6 7
      src/bin/bind10/bind10_src.py.in
  5. 7 7
      src/bin/bind10/tests/bind10_test.py.in
  6. 3 0
      src/bin/resolver/main.cc
  7. 25 4
      src/bin/resolver/tests/resolver_config_unittest.cc
  8. 20 3
      src/bin/sockcreator/sockcreator.cc
  9. 10 1
      src/bin/sockcreator/sockcreator.h
  10. 39 2
      src/bin/sockcreator/tests/sockcreator_tests.cc
  11. 1 0
      src/lib/asiodns/Makefile.am
  12. 8 0
      src/lib/asiodns/asiodns_messages.mes
  13. 15 0
      src/lib/asiodns/dns_service.cc
  14. 36 0
      src/lib/asiodns/dns_service.h
  15. 1 7
      src/lib/asiodns/io_fetch.cc
  16. 25 0
      src/lib/asiodns/logger.cc
  17. 26 0
      src/lib/asiodns/logger.h
  18. 27 2
      src/lib/asiodns/tcp_server.cc
  19. 14 0
      src/lib/asiodns/tcp_server.h
  20. 250 104
      src/lib/asiodns/tests/dns_server_unittest.cc
  21. 30 3
      src/lib/asiodns/udp_server.cc
  22. 14 0
      src/lib/asiodns/udp_server.h
  23. 35 1
      src/lib/config/ccsession.h
  24. 31 10
      src/lib/python/bind10_config.py.in
  25. 2 2
      src/lib/python/isc/bind10/socket_cache.py
  26. 2 2
      src/lib/python/isc/bind10/special_component.py
  27. 1 0
      src/lib/server_common/Makefile.am
  28. 43 3
      src/lib/server_common/portconfig.cc
  29. 10 4
      src/lib/server_common/portconfig.h
  30. 26 1
      src/lib/server_common/server_common_messages.mes
  31. 408 0
      src/lib/server_common/socket_request.cc
  32. 203 0
      src/lib/server_common/socket_request.h
  33. 1 0
      src/lib/server_common/tests/Makefile.am
  34. 118 4
      src/lib/server_common/tests/portconfig_unittest.cc
  35. 570 0
      src/lib/server_common/tests/socket_requestor_test.cc
  36. 1 1
      src/lib/testutils/Makefile.am
  37. 5 5
      src/lib/testutils/portconfig.h
  38. 195 0
      src/lib/testutils/socket_request.h
  39. 1 1
      src/lib/testutils/srv_test.h
  40. 16 19
      src/lib/util/io/fd.cc

+ 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;
@@ -158,6 +159,8 @@ main(int argc, char* argv[]) {
 
         cc_session = new Session(io_service.get_io_service());
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_CONFIG_CHANNEL_CREATED);
+        // Initialize the Socket Requestor
+        isc::server_common::initSocketReqeustor(*cc_session);
 
         // We delay starting listening to new commands/config just before we
         // go into the main loop to avoid confusion due to mixture of

+ 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) {

+ 6 - 7
src/bin/bind10/bind10_src.py.in

@@ -44,12 +44,10 @@ import os
 # installed on the system
 if "B10_FROM_SOURCE" in os.environ:
     SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + "/src/bin/bind10/bob.spec"
-    ADD_LIBEXEC_PATH = False
 else:
     PREFIX = "@prefix@"
     DATAROOTDIR = "@datarootdir@"
     SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-    ADD_LIBEXEC_PATH = True
     
 import subprocess
 import signal
@@ -65,6 +63,7 @@ import pwd
 import posix
 import copy
 
+from bind10_config import LIBEXECPATH
 import isc.cc
 import isc.util.process
 import isc.net.parse
@@ -85,8 +84,8 @@ DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
 DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
 
 # Messages sent over the unix domain socket to indicate if it is followed by a real socket
-CREATOR_SOCKET_OK = "1\n"
-CREATOR_SOCKET_UNAVAILABLE = "0\n"
+CREATOR_SOCKET_OK = b"1\n"
+CREATOR_SOCKET_UNAVAILABLE = b"0\n"
 
 # Assign this process some longer name
 isc.util.process.rename(sys.argv[0])
@@ -151,8 +150,7 @@ class ProcessInfo:
         # on construction (self.env).
         spawn_env = copy.deepcopy(os.environ)
         spawn_env.update(self.env)
-        if ADD_LIBEXEC_PATH:
-            spawn_env['PATH'] = "@@LIBEXECDIR@@:" + spawn_env['PATH']
+        spawn_env['PATH'] = LIBEXECPATH + ':' + spawn_env['PATH']
         self.process = subprocess.Popen(self.args,
                                         stdin=subprocess.PIPE,
                                         stdout=spawn_stdout,
@@ -836,6 +834,7 @@ class BoB:
         identified by the token back over the unix_socket.
         """
         try:
+            token = str(token, 'ASCII') # Convert from bytes to str
             fd = self._socket_cache.get_socket(token, unix_socket.fileno())
             # FIXME: These two calls are blocking in their nature. An OS-level
             # buffer is likely to be large enough to hold all these data, but
@@ -914,7 +913,7 @@ class BoB:
         Accept a socket from the unix domain socket server and put it to the
         others we care about.
         """
-        socket = self._srv_socket.accept()
+        (socket, conn) = self._srv_socket.accept()
         self._unix_sockets[socket.fileno()] = (socket, b'')
 
     def _socket_data(self, socket_fileno):

+ 7 - 7
src/bin/bind10/tests/bind10_test.py.in

@@ -146,7 +146,7 @@ class TestCacheCommands(unittest.TestCase):
         socket.
         """
         def __init__(self):
-            self.send = ""
+            self.send = b""
         def fileno(self):
             """
             The file number. Used for identifying the remote application.
@@ -207,17 +207,17 @@ class TestCacheCommands(unittest.TestCase):
         socket = self.FalseSocket()
         # An exception from the cache
         self.__raise_exception = ValueError("Test value error")
-        self.__boss.socket_request_handler("token", socket)
+        self.__boss.socket_request_handler(b"token", socket)
         # It was called, but it threw, so it is not noted here
         self.assertIsNone(self.__get_socket_called)
-        self.assertEqual("0\n", socket.send)
+        self.assertEqual(b"0\n", socket.send)
         # It should not have sent any socket.
         self.assertIsNone(self.__send_fd_called)
         # Now prepare a valid scenario
         self.__raise_exception = None
-        socket.send = ""
-        self.__boss.socket_request_handler("token", socket)
-        self.assertEqual("1\n", socket.send)
+        socket.send = b""
+        self.__boss.socket_request_handler(b"token", socket)
+        self.assertEqual(b"1\n", socket.send)
         self.assertEqual((42, 13), self.__send_fd_called)
         self.assertEqual(("token", 42), self.__get_socket_called)
 
@@ -1235,7 +1235,7 @@ class SocketSrvTest(unittest.TestCase):
             return self.__fileno
 
         def accept(self):
-            return self.__class__(self.__owner, 13)
+            return (self.__class__(self.__owner, 13), "/path/to/socket")
 
         def recv(self, bufsize, flags=0):
             self.__owner.assertEqual(1, bufsize)

+ 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>
@@ -206,6 +208,7 @@ main(int argc, char* argv[]) {
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);
 
         cc_session = new Session(io_service.get_io_service());
+        isc::server_common::initSocketReqeustor(*cc_session);
         config_session = new ModuleCCSession(specfile, *cc_session,
                                              my_config_handler,
                                              my_command_handler);

+ 25 - 4
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;
@@ -63,7 +64,8 @@ using isc::UnitTestUtil;
 
 namespace {
 const char* const TEST_ADDRESS = "127.0.0.1";
-const char* const TEST_PORT = "53530";
+const char* const TEST_ADDRESS_FAIL = "192.0.2.2";
+const char* const TEST_PORT = "53210";
 
 // An internal exception class
 class TestConfigError : public isc::Exception {
@@ -81,7 +83,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 +101,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) {
@@ -248,7 +255,7 @@ TEST_F(ResolverConfig, listenOnConfigFail) {
                                              "\"listen_on\": ["
                                              " {"
                                              "    \"address\": \"" +
-                                             string(TEST_ADDRESS) + "\","
+                                             string(TEST_ADDRESS_FAIL) + "\","
                                              "    \"port\": " +
                                              string(TEST_PORT) + "}]}"));
     configAnswerCheck(server.updateConfig(config), false);
@@ -264,7 +271,7 @@ TEST_F(ResolverConfig, listenOnAndOtherConfig) {
                             " {\"address\": \"192.0.2.1\","
                             "   \"port\": 53}], "
                             "\"listen_on\": ["
-                            " {\"address\": \"" + string(TEST_ADDRESS) + "\","
+                            " {\"address\": \"" + string(TEST_ADDRESS_FAIL) + "\","
                             "  \"port\": " + string(TEST_PORT) + "}]}");
     // Normally, if listen_on fails the rest of the config parameters will
     // be ignored.
@@ -310,6 +317,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

+ 20 - 3
src/bin/sockcreator/sockcreator.cc

@@ -35,6 +35,14 @@ get_sock(const int type, struct sockaddr *bind_addr, const socklen_t addr_len)
     if (sock == -1) {
         return -1;
     }
+    const int on(1);
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+        return -2; // This is part of the binding process, so it's a bind error
+    }
+    if (bind_addr->sa_family == AF_INET6 &&
+        setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
+        return -2; // This is part of the binding process, so it's a bind error
+    }
     if (bind(sock, bind_addr, addr_len) == -1) {
         return -2;
     }
@@ -62,7 +70,7 @@ get_sock(const int type, struct sockaddr *bind_addr, const socklen_t addr_len)
 
 int
 run(const int input_fd, const int output_fd, const get_sock_t get_sock,
-    const send_fd_t send_fd)
+    const send_fd_t send_fd_fun, const close_t close_fun)
 {
     for (;;) {
         // Read the command
@@ -122,8 +130,17 @@ run(const int input_fd, const int output_fd, const get_sock_t get_sock,
                 int result(get_sock(sock_type, addr, addr_len));
                 if (result >= 0) { // We got the socket
                     WRITE("S", 1);
-                    // FIXME: Check the output and write a test for it
-                    send_fd(output_fd, result);
+                    if (send_fd_fun(output_fd, result) != 0) {
+                        // We'll soon abort ourselves, but make sure we still
+                        // close the socket; don't bother if it fails as the
+                        // higher level result (abort) is the same.
+                        close_fun(result);
+                        return 3;
+                    }
+                    // Don't leak the socket
+                    if (close_fun(result) == -1) {
+                        return 4;
+                    }
                 } else {
                     WRITE("E", 1);
                     switch (result) {

+ 10 - 1
src/bin/sockcreator/sockcreator.h

@@ -27,6 +27,7 @@
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <unistd.h>
 
 namespace isc {
 namespace socket_creator {
@@ -62,6 +63,11 @@ typedef
 int
 (*send_fd_t)(const int, const int);
 
+/// \brief Type of the close() function, so it can be passed as a parameter.
+typedef
+int
+(*close_t)(int);
+
 /**
  * \short Infinite loop parsing commands and returning the sockets.
  *
@@ -88,11 +94,14 @@ int
  * \param send_fd_fun The function that is used to send the socket over
  *     a file descriptor. This should be left on the default value, it is
  *     here for testing purposes.
+ * \param close_fun The close function used to close sockets, coming from
+ *     unistd.h. It can be overriden in tests.
  */
 int
 run(const int input_fd, const int output_fd,
     const get_sock_t get_sock_fun = get_sock,
-    const send_fd_t send_fd_fun = isc::util::io::send_fd);
+    const send_fd_t send_fd_fun = isc::util::io::send_fd,
+    const close_t close_fun = close);
 
 } // End of the namespaces
 }

+ 39 - 2
src/bin/sockcreator/tests/sockcreator_tests.cc

@@ -59,6 +59,15 @@ namespace {
             #SOCK_TYPE " and family " #ADDR_FAMILY ", failed with " \
             << socket << " and error " << strerror(errno); \
         CHECK_SOCK(ADDR_TYPE, socket); \
+        int on; \
+        socklen_t len(sizeof(on)); \
+        EXPECT_EQ(0, getsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &on, &len));\
+        EXPECT_NE(0, on); \
+        if (ADDR_FAMILY == AF_INET6) { \
+            EXPECT_EQ(0, getsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, &on, \
+                                    &len)); \
+            EXPECT_NE(0, on); \
+        } \
         EXPECT_EQ(0, close(socket)); \
     } while (0)
 
@@ -190,6 +199,12 @@ send_fd_dummy(const int destination, const int what)
     }
 }
 
+// Just ignore the fd and pretend success. We close invalid fds in the tests.
+int
+closeIgnore(int) {
+    return (0);
+}
+
 /*
  * Generic test that it works, with various inputs and outputs.
  * It uses different functions to create the socket and send it and pass
@@ -198,7 +213,8 @@ send_fd_dummy(const int destination, const int what)
  */
 void run_test(const char *input_data, const size_t input_size,
     const char *output_data, const size_t output_size,
-    bool should_succeed = true)
+    bool should_succeed = true, const close_t test_close = closeIgnore,
+    const send_fd_t send_fd = send_fd_dummy)
 {
     // Prepare the input feeder and output checker processes
     int input_fd(0), output_fd(0);
@@ -207,7 +223,7 @@ void run_test(const char *input_data, const size_t input_size,
     ASSERT_NE(-1, input) << "Couldn't start input feeder";
     ASSERT_NE(-1, output) << "Couldn't start output checker";
     // Run the body
-    int result(run(input_fd, output_fd, get_sock_dummy, send_fd_dummy));
+    int result(run(input_fd, output_fd, get_sock_dummy, send_fd, test_close));
     // Close the pipes
     close(input_fd);
     close(output_fd);
@@ -270,4 +286,25 @@ TEST(run, bad_sockets) {
         result, result_len);
 }
 
+// A close that fails
+int
+closeFail(int) {
+    return (-1);
+}
+
+TEST(run, cant_close) {
+    run_test("SU4\xff\xff\0\0\0\0", // This has 9 bytes
+             9, "S\x07", 2, false, closeFail);
+}
+
+int
+sendFDFail(const int, const int) {
+    return (FD_SYSTEM_ERROR);
+}
+
+TEST(run, cant_send_fd) {
+    run_test("SU4\xff\xff\0\0\0\0", // This has 9 bytes
+             9, "S", 1, false, closeIgnore, sendFDFail);
+}
+
 }

+ 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);

+ 35 - 1
src/lib/config/ccsession.h

@@ -315,7 +315,41 @@ public:
     isc::data::ConstElementPtr getRemoteConfigValue(
         const std::string& module_name,
         const std::string& identifier) const;
-    
+
+    /**
+     * Send a message to the underlying CC session.
+     * This has the same interface as isc::cc::Session::group_sendmsg()
+     *
+     * \param msg see isc::cc::Session::group_sendmsg()
+     * \param group see isc::cc::Session::group_sendmsg()
+     * \param instance see isc::cc::Session::group_sendmsg()
+     * \param to see isc::cc::Session::group_sendmsg()
+     * \return see isc::cc::Session::group_sendmsg()
+     */
+    int groupSendMsg(isc::data::ConstElementPtr msg,
+                     std::string group,
+                     std::string instance = "*",
+                     std::string to = "*") {
+        return (session_.group_sendmsg(msg, group, instance, to));
+    };
+
+    /**
+     * Receive a message from the underlying CC session.
+     * This has the same interface as isc::cc::Session::group_recvmsg()
+     *
+     * \param envelope see isc::cc::Session::group_recvmsg()
+     * \param msg see isc::cc::Session::group_recvmsg()
+     * \param nonblock see isc::cc::Session::group_recvmsg()
+     * \param seq see isc::cc::Session::group_recvmsg()
+     * \return see isc::cc::Session::group_recvmsg()
+     */
+    bool groupRecvMsg(isc::data::ConstElementPtr& envelope,
+                      isc::data::ConstElementPtr& msg,
+                      bool nonblock = true,
+                      int seq = -1) {
+        return (session_.group_recvmsg(envelope, msg, nonblock, seq));
+    };
+
 private:
     ModuleSpec readModuleSpecification(const std::string& filename);
     void startCheck();

+ 31 - 10
src/lib/python/bind10_config.py.in

@@ -23,24 +23,37 @@ def reload():
     global DATA_PATH
     global PLUGIN_PATHS
     global PREFIX
-    global LIBEXECDIR
-    LIBEXECDIR = ("@libexecdir@/@PACKAGE@"). \
-        replace("${exec_prefix}", "@exec_prefix@"). \
-        replace("${prefix}", "@prefix@")
+    global LIBEXECPATH
     BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
                                            "@PACKAGE_NAME@",
                                            "msgq_socket").replace("${prefix}",
                                                                   "@prefix@")
     PREFIX = "@prefix@"
 
-    # If B10_FROM_SOURCE is set in the environment, we use data files
-    # from a directory relative to the value of that variable, or, if defined,
-    # relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR.  Otherwise
-    # we use the ones installed on the system.
+    # B10_FROM_SOURCE is set in the environment for internal tests and
+    # an experimental run without installagion.  In that case we need to
+    # specialize some configuration variables, generally so that they refer
+    # to somewhere in the source tree instead of the appropriate places
+    # after installation.
+    #
+    # DATA_PATH: used by the config manager to find configuration files.
+    #  When "FROM_SOURCE", we use data files from a directory relative to the
+    #  value of that variable, or, if defined, relative to the value of
+    #  B10_FROM_SOURCE_LOCALSTATEDIR.  Otherwise we use the ones installed on
+    #  the system.
+    # PLUGIN_PATHS: configuration modules that are not associated to specific
+    #  process
+    # LIBEXECPATH: Paths to programs invoked by the boss process
+    #  The boss process (directly or via a helper module) uses this as
+    #  the prefererred PATH before starting a child process.
+    #  When "FROM_SOURCE", it lists the directories where the programs are
+    #  built so that when BIND 10 is experimentally started on the source
+    #  tree the programs in the tree (not installed ones) will be used.
+    #
     # B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
     # tests where we want to use variuos types of configuration within the test
-    # environment.  (We may want to make it even more generic so that the path is
-    # passed from the boss process)
+    # environment.  (We may want to make it even more generic so that the path
+    # is passed from the boss process)
     if "B10_FROM_SOURCE" in os.environ:
         if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
             DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
@@ -48,9 +61,17 @@ def reload():
             DATA_PATH = os.environ["B10_FROM_SOURCE"]
         PLUGIN_PATHS = [os.environ["B10_FROM_SOURCE"] +
                             '/src/bin/cfgmgr/plugins']
+        programdirs = ['auth', 'cfgmgr', 'cmdctl', 'ddns', 'dhcp6', 'msgq',
+                       'resolver', 'sockcreator', 'stats', 'xfrin', 'xfrout',
+                       'zonemgr']
+        LIBEXECPATH = ':'.join(['@abs_top_builddir@/src/bin/' + p for p in
+                                programdirs])
     else:
         DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
         PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
+        LIBEXECPATH = ("@libexecdir@/@PACKAGE@"). \
+            replace("${exec_prefix}", "@exec_prefix@"). \
+            replace("${prefix}", "@prefix@")
     # For testing the plugins so they can find their own spec files
     if "B10_TEST_PLUGIN_DIR" in os.environ:
         PLUGIN_PATHS = os.environ["B10_TEST_PLUGIN_DIR"].split(':')

+ 2 - 2
src/lib/python/isc/bind10/socket_cache.py

@@ -205,9 +205,9 @@ class Cache:
             raise ShareError("Cached socket not compatible with mode " +
                              share_mode + " and name " + share_name)
         # Grab yet unused token
-        token = 't' + str(random.randint(0, 2^32-1))
+        token = 't' + str(random.randint(0, 2 ** 32-1))
         while token in self._live_tokens:
-            token = 't' + str(random.randint(0, 2^32-1))
+            token = 't' + str(random.randint(0, 2 ** 32-1))
         self._waiting_tokens[token] = socket
         self._live_tokens.add(token)
         socket.shares[token] = (share_mode, share_name)

+ 2 - 2
src/lib/python/isc/bind10/special_component.py

@@ -15,7 +15,7 @@
 
 from isc.bind10.component import Component, BaseComponent
 import isc.bind10.sockcreator
-from bind10_config import LIBEXECDIR
+from bind10_config import LIBEXECPATH
 import os
 import posix
 import isc.log
@@ -39,7 +39,7 @@ class SockCreator(BaseComponent):
 
     def _start_internal(self):
         self._boss.curproc = 'b10-sockcreator'
-        self.__creator = isc.bind10.sockcreator.Creator(LIBEXECDIR + ':' +
+        self.__creator = isc.bind10.sockcreator.Creator(LIBEXECPATH + ':' +
                                                         os.environ['PATH'])
         self._boss.register_process(self.pid(), self)
         self._boss.set_creator(self.__creator)

+ 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,

+ 26 - 1
src/lib/server_common/server_common_messages.mes

@@ -16,6 +16,31 @@ $NAMESPACE isc::server_common
 
 # \brief Messages for the server_common library
 
+% SOCKETREQUESTOR_CREATED Socket requestor created
+Debug message.  A socket requesor (client of the socket creator) is created
+for the corresponding application.  Normally this should happen at most
+one time throughout the lifetime of the application.
+
+% SOCKETREQUESTOR_DESTROYED Socket requestor destoryed
+Debug message.  The socket requestor created at SOCKETREQUESTOR_CREATED
+has been destroyed.  This event is generally unexpected other than in
+test cases.
+
+% SOCKETREQUESTOR_GETSOCKET Received a %1 socket for [%2]:%3, FD=%4, token=%5, path=%6
+Debug message. The socket requestor for the corresponding application
+has requested a socket for a set of address, port and protocol (shown
+in the log message) and successfully got it from the creator.  The
+corresponding file descriptor and the associated "token" (an internal
+ID used between the creator and requestor) are shown in the log
+message.
+
+% SOCKETREQUESTOR_RELEASESOCKET Released a socket of token %1
+Debug message.  The socket requestor has released a socket passed by
+the creator.  The associated token of the socket is shown in the
+log message.  If the corresponding SOCKETREQUESTOR_GETSOCKET was logged
+more detailed information of the socket can be identified by matching
+the token.
+
 % SRVCOMM_ADDRESSES_NOT_LIST the address and port specification is not a list in %1
 This points to an error in configuration. What was supposed to be a list of
 IP address - port pairs isn't a list at all but something else.
@@ -38,7 +63,7 @@ message. A valid specification contains an address part (which must be a string
 and must represent a valid IPv4 or IPv6 address) and port (which must be an
 integer in the range valid for TCP/UDP ports on your system).
 
-% SRVCOMM_ADDRESS_UNRECOVERABLE failed to recover original addresses also (%2)
+% SRVCOMM_ADDRESS_UNRECOVERABLE failed to recover original addresses also (%1)
 The recovery of old addresses after SRVCOMM_ADDRESS_FAIL also failed for
 the reason listed.
 

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

@@ -0,0 +1,408 @@
+// 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 <config.h>
+
+#include "socket_request.h"
+#include <server_common/logger.h>
+
+#include <config/ccsession.h>
+#include <cc/session.h>
+#include <cc/data.h>
+#include <util/io/fd.h>
+#include <util/io/fd_share.h>
+
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <cerrno>
+#include <csignal>
+#include <cstddef>
+
+namespace isc {
+namespace server_common {
+
+namespace {
+SocketRequestor* requestor(NULL);
+
+// Before the boss process calls send_fd, it first sends this
+// string to indicate success, followed by the file descriptor
+const std::string& CREATOR_SOCKET_OK() {
+    static const std::string str("1\n");
+    return (str);
+}
+
+// Before the boss process calls send_fd, it sends this
+// string to indicate failure. It will not send a file descriptor.
+const std::string& CREATOR_SOCKET_UNAVAILABLE() {
+    static const std::string str("0\n");
+    return (str);
+}
+
+// The name of the ccsession command to request a socket from boss
+// (the actual format of command and response are hardcoded in their
+// respective methods)
+const std::string& REQUEST_SOCKET_COMMAND() {
+    static const std::string str("get_socket");
+    return (str);
+}
+
+// The name of the ccsession command to tell boss we no longer need
+// a socket (the actual format of command and response are hardcoded
+// in their respective methods)
+const std::string& RELEASE_SOCKET_COMMAND() {
+    static const std::string str("drop_socket");
+    return (str);
+}
+
+// A helper converter from numeric protocol ID to the corresponding string.
+// used both for generating a message for the boss process and for logging.
+inline const char*
+protocolString(SocketRequestor::Protocol protocol) {
+    switch (protocol) {
+    case SocketRequestor::TCP:
+        return ("TCP");
+    case SocketRequestor::UDP:
+        return ("UDP");
+    default:
+        return ("unknown protocol");
+    }
+}
+
+// Creates the cc session message to request a socket.
+// The actual command format is hardcoded, and should match
+// the format as read in bind10_src.py.in
+isc::data::ConstElementPtr
+createRequestSocketMessage(SocketRequestor::Protocol protocol,
+                           const std::string& address, uint16_t port,
+                           SocketRequestor::ShareMode share_mode,
+                           const std::string& share_name)
+{
+    const isc::data::ElementPtr request = isc::data::Element::createMap();
+    request->set("address", isc::data::Element::create(address));
+    request->set("port", isc::data::Element::create(port));
+    if (protocol != SocketRequestor::TCP && protocol != SocketRequestor::UDP) {
+        isc_throw(InvalidParameter, "invalid protocol: " << protocol);
+    }
+    request->set("protocol",
+                 isc::data::Element::create(protocolString(protocol)));
+    switch (share_mode) {
+    case SocketRequestor::DONT_SHARE:
+        request->set("share_mode", isc::data::Element::create("NO"));
+        break;
+    case SocketRequestor::SHARE_SAME:
+        request->set("share_mode", isc::data::Element::create("SAMEAPP"));
+        break;
+    case SocketRequestor::SHARE_ANY:
+        request->set("share_mode", isc::data::Element::create("ANY"));
+        break;
+    default:
+        isc_throw(InvalidParameter, "invalid share mode: " << share_mode);
+    }
+    request->set("share_name", isc::data::Element::create(share_name));
+
+    return (isc::config::createCommand(REQUEST_SOCKET_COMMAND(), request));
+}
+
+isc::data::ConstElementPtr
+createReleaseSocketMessage(const std::string& token) {
+    const isc::data::ElementPtr release = isc::data::Element::createMap();
+    release->set("token", isc::data::Element::create(token));
+
+    return (isc::config::createCommand(RELEASE_SOCKET_COMMAND(), release));
+}
+
+// Checks and parses the response receive from Boss
+// If successful, token and path will be set to the values found in the
+// answer.
+// If the response was an error response, or does not contain the
+// expected elements, a CCSessionError is raised.
+void
+readRequestSocketAnswer(isc::data::ConstElementPtr recv_msg,
+                        std::string& token, std::string& path)
+{
+    int rcode;
+    isc::data::ConstElementPtr answer = isc::config::parseAnswer(rcode,
+                                                                 recv_msg);
+    if (rcode != 0) {
+        isc_throw(isc::config::CCSessionError,
+                  "Error response when requesting socket: " << answer->str());
+    }
+
+    if (!answer || !answer->contains("token") || !answer->contains("path")) {
+        isc_throw(isc::config::CCSessionError,
+                  "Malformed answer when requesting socket");
+    }
+    token = answer->get("token")->stringValue();
+    path = answer->get("path")->stringValue();
+}
+
+// Connect to the domain socket that has been received from Boss.
+// (i.e. the one that is used to pass created sockets over).
+//
+// This should only be called if the socket had not been connected to
+// already. To get the socket and reuse existing ones, use
+// getFdShareSocket()
+//
+// \param path The domain socket to connect to
+// \exception SocketError if the socket cannot be connected to
+// \return the socket file descriptor
+int
+createFdShareSocket(const std::string& path) {
+    // TODO: Current master has socketsession code and better way
+    // of handling errors without potential leaks for this. It is
+    // not public there at this moment, but when this is merged
+    // we should make a ticket to move this functionality to the
+    // SocketSessionReceiver and use that.
+    const int sock_pass_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (sock_pass_fd == -1) {
+        isc_throw(SocketRequestor::SocketError,
+                  "Unable to open domain socket " << path <<
+                  ": " << strerror(errno));
+    }
+    struct sockaddr_un sock_pass_addr;
+    sock_pass_addr.sun_family = AF_UNIX;
+    if (path.size() >= sizeof(sock_pass_addr.sun_path)) {
+        close(sock_pass_fd);
+        isc_throw(SocketRequestor::SocketError,
+                  "Unable to open domain socket " << path <<
+                  ": path too long");
+    }
+#ifdef HAVE_SA_LEN
+    sock_pass_addr.sun_len = path.size();
+#endif
+    strcpy(sock_pass_addr.sun_path, path.c_str());
+    const socklen_t len = path.size() + offsetof(struct sockaddr_un, sun_path);
+    // Yes, C-style cast bad. See previous comment about SocketSessionReceiver.
+    if (connect(sock_pass_fd, (const struct sockaddr*)&sock_pass_addr,
+                len) == -1) {
+        close(sock_pass_fd);
+        isc_throw(SocketRequestor::SocketError,
+                  "Unable to open domain socket " << path <<
+                  ": " << strerror(errno));
+    }
+    return (sock_pass_fd);
+}
+
+// Reads a socket fd over the given socket (using recv_fd()).
+//
+// \exception SocketError if the socket cannot be read
+// \return the socket fd that has been read
+int
+getSocketFd(const std::string& token, int sock_pass_fd) {
+    // Tell the boss the socket token.
+    const std::string token_data = token + "\n";
+    if (!isc::util::io::write_data(sock_pass_fd, token_data.c_str(),
+                                   token_data.size())) {
+        isc_throw(SocketRequestor::SocketError, "Error writing socket token");
+    }
+
+    // Boss first sends some data to signal that getting the socket
+    // from its cache succeeded
+    char status[3];        // We need a space for trailing \0, hence 3
+    memset(status, 0, 3);
+    if (isc::util::io::read_data(sock_pass_fd, status, 2) < 2) {
+        isc_throw(SocketRequestor::SocketError,
+                  "Error reading status code while requesting socket");
+    }
+    // Actual status value hardcoded by boss atm.
+    if (CREATOR_SOCKET_UNAVAILABLE() == status) {
+        isc_throw(SocketRequestor::SocketError,
+                  "CREATOR_SOCKET_UNAVAILABLE returned");
+    } else if (CREATOR_SOCKET_OK() != status) {
+        isc_throw(SocketRequestor::SocketError,
+                  "Unknown status code returned before recv_fd '" << status <<
+                  "'");
+    }
+
+    const int passed_sock_fd = isc::util::io::recv_fd(sock_pass_fd);
+
+    // check for error values of passed_sock_fd (see fd_share.h)
+    if (passed_sock_fd < 0) {
+        switch (passed_sock_fd) {
+        case isc::util::io::FD_SYSTEM_ERROR:
+            isc_throw(SocketRequestor::SocketError,
+                      "FD_SYSTEM_ERROR while requesting socket");
+            break;
+        case isc::util::io::FD_OTHER_ERROR:
+            isc_throw(SocketRequestor::SocketError,
+                      "FD_OTHER_ERROR while requesting socket");
+            break;
+        default:
+            isc_throw(SocketRequestor::SocketError,
+                      "Unknown error while requesting socket");
+        }
+    }
+    return (passed_sock_fd);
+}
+
+// This implementation class for SocketRequestor uses
+// a CC session for communication with the boss process,
+// and fd_share to read out the socket(s).
+// Since we only use a reference to the session, it must never
+// be closed during the lifetime of this class
+class SocketRequestorCCSession : public SocketRequestor {
+public:
+    explicit SocketRequestorCCSession(cc::AbstractSession& session) :
+        session_(session)
+    {
+        // We need to filter SIGPIPE to prevent it from happening in
+        // getSocketFd() while writing to the UNIX domain socket after the
+        // remote end closed it.  See lib/util/io/socketsession for more
+        // background details.
+        // Note: we should eventually unify this level of details into a single
+        // module.  Setting a single filter here should be considered a short
+        // term workaround.
+        if (std::signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
+            isc_throw(Unexpected, "Failed to filter SIGPIPE: " <<
+                      strerror(errno));
+        }
+        LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, SOCKETREQUESTOR_CREATED);
+    }
+
+    ~SocketRequestorCCSession() {
+        closeFdShareSockets();
+        LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, SOCKETREQUESTOR_DESTROYED);
+    }
+
+    virtual SocketID requestSocket(Protocol protocol,
+                                   const std::string& address,
+                                   uint16_t port, ShareMode share_mode,
+                                   const std::string& share_name)
+    {
+        const isc::data::ConstElementPtr request_msg =
+            createRequestSocketMessage(protocol, address, port,
+                                       share_mode, share_name);
+
+        // Send it to boss
+        const int seq = session_.group_sendmsg(request_msg, "Boss");
+
+        // Get the answer from the boss.
+        // Just do a blocking read, we can't really do much anyway
+        isc::data::ConstElementPtr env, recv_msg;
+        if (!session_.group_recvmsg(env, recv_msg, false, seq)) {
+            isc_throw(isc::config::CCSessionError,
+                      "Incomplete response when requesting socket");
+        }
+
+        // Read the socket file from the answer
+        std::string token, path;
+        readRequestSocketAnswer(recv_msg, token, path);
+        // get the domain socket over which we will receive the
+        // real socket
+        const int sock_pass_fd = getFdShareSocket(path);
+
+        // and finally get the socket itself
+        const int passed_sock_fd = getSocketFd(token, sock_pass_fd);
+        LOG_DEBUG(logger, DBGLVL_TRACE_DETAIL, SOCKETREQUESTOR_GETSOCKET).
+            arg(protocolString(protocol)).arg(address).arg(port).
+            arg(passed_sock_fd).arg(token).arg(path);
+        return (SocketID(passed_sock_fd, token));
+    }
+
+    virtual void releaseSocket(const std::string& token) {
+        const isc::data::ConstElementPtr release_msg =
+            createReleaseSocketMessage(token);
+
+        // Send it to boss
+        const int seq = session_.group_sendmsg(release_msg, "Boss");
+        LOG_DEBUG(logger, DBGLVL_TRACE_DETAIL, SOCKETREQUESTOR_RELEASESOCKET).
+            arg(token);
+
+        // Get the answer from the boss.
+        // Just do a blocking read, we can't really do much anyway
+        isc::data::ConstElementPtr env, recv_msg;
+        if (!session_.group_recvmsg(env, recv_msg, false, seq)) {
+            isc_throw(isc::config::CCSessionError,
+                      "Incomplete response when sending drop socket command");
+        }
+
+        // Answer should just be success
+        int rcode;
+        isc::data::ConstElementPtr error = isc::config::parseAnswer(rcode,
+                                                                    recv_msg);
+        if (rcode != 0) {
+            isc_throw(SocketError,
+                      "Error requesting release of socket: " << error->str());
+        }
+    }
+
+private:
+    // Returns the domain socket file descriptor
+    // If we had not opened it yet, opens it now
+    int
+    getFdShareSocket(const std::string& path) {
+        if (fd_share_sockets_.find(path) == fd_share_sockets_.end()) {
+            const int new_fd = createFdShareSocket(path);
+            // Technically, the (creation and) assignment of the new map entry
+            // could thrown an exception and lead to FD leak.  This should be
+            // cleaned up later (see comment about SocketSessionReceiver above)
+            fd_share_sockets_[path] = new_fd;
+            return (new_fd);
+        } else {
+            return (fd_share_sockets_[path]);
+        }
+    }
+
+    // Closes the sockets that has been used for fd_share
+    void
+    closeFdShareSockets() {
+        for (std::map<std::string, int>::const_iterator it =
+                fd_share_sockets_.begin();
+             it != fd_share_sockets_.end();
+             ++it) {
+            close((*it).second);
+        }
+    }
+
+    cc::AbstractSession& session_;
+    std::map<std::string, int> fd_share_sockets_;
+};
+
+}
+
+SocketRequestor&
+socketRequestor() {
+    if (requestor != NULL) {
+        return (*requestor);
+    } else {
+        isc_throw(InvalidOperation, "The socket requestor is not initialized");
+    }
+}
+
+void
+initSocketReqeustor(cc::AbstractSession& session) {
+    if (requestor != NULL) {
+        isc_throw(InvalidOperation,
+                  "The socket requestor was already initialized");
+    } else {
+        requestor = new SocketRequestorCCSession(session);
+    }
+}
+
+void
+initTestSocketRequestor(SocketRequestor* new_requestor) {
+    requestor = new_requestor;
+}
+
+void
+cleanupSocketRequestor() {
+    if (requestor != NULL) {
+        delete requestor;
+        requestor = NULL;
+    } else {
+        isc_throw(InvalidOperation, "The socket requestor is not initialized");
+    }
+}
+
+}
+}

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

@@ -0,0 +1,203 @@
+// 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 cc {
+class AbstractSession;
+};
+
+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 its 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.  This must be
+    /// either UDP or TCP.
+    /// \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.
+    /// This must be one of the defined values of ShareMode.
+    /// \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 InvalidParameter protocol or share_mode is invalid
+    /// \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 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();
+
+/// \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
+void initSocketReqeustor(cc::AbstractSession& 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).
+void initTestSocketRequestor(SocketRequestor* requestor);
+
+/// \brief Destroy the singleton instance
+///
+/// Calling this function is not strictly necessary; the socket
+/// requestor is a singleton anyway. However, for some tests it
+/// is useful to destroy and recreate it, as well as for programs
+/// that want to be completely clean on exit.
+/// After this function has been called, all operations except init
+/// will fail.
+void cleanupSocketRequestor();
+
+}
+}
+
+#endif  // __SOCKET_REQUEST_H

+ 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");
 }
 
 }

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

@@ -0,0 +1,570 @@
+// 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 <config.h>
+
+#include <server_common/socket_request.h>
+
+#include <gtest/gtest.h>
+
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+#include <exceptions/exceptions.h>
+
+#include <server_common/tests/data_path.h>
+
+#include <cstdlib>
+#include <cstddef>
+#include <cerrno>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <util/io/fd.h>
+#include <util/io/fd_share.h>
+
+using namespace isc::data;
+using namespace isc::config;
+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
+    initTestSocketRequestor(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)
+    initTestSocketRequestor(&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
+    initTestSocketRequestor(NULL);
+}
+
+// This class contains a fake (module)ccsession to emulate answers from Boss
+class SocketRequestorTest : public ::testing::Test {
+public:
+    SocketRequestorTest() : session(ElementPtr(new ListElement),
+                                    ElementPtr(new ListElement),
+                                    ElementPtr(new ListElement))
+    {
+        initSocketReqeustor(session);
+    }
+
+    ~SocketRequestorTest() {
+        cleanupSocketRequestor();
+    }
+
+    // Do a standard request with some default values
+    SocketRequestor::SocketID
+    doRequest() {
+        return (socketRequestor().requestSocket(SocketRequestor::UDP,
+                                                "192.0.2.1", 12345,
+                                                SocketRequestor::DONT_SHARE,
+                                                "test"));
+    }
+
+    // Creates a valid socket request answer, as it would be sent by
+    // Boss. 'valid' in terms of format, not values
+    void
+    addAnswer(const std::string& token, const std::string& path) {
+        ElementPtr answer_part = Element::createMap();
+        answer_part->set("token", Element::create(token));
+        answer_part->set("path", Element::create(path));
+        session.getMessages()->add(createAnswer(0, answer_part));
+    }
+
+    // Clears the messages the client sent so far on the fake msgq
+    // (for easier access to new messages later)
+    void
+    clearMsgQueue() {
+        while (session.getMsgQueue()->size() > 0) {
+            session.getMsgQueue()->remove(0);
+        }
+    }
+
+    isc::cc::FakeSession session;
+    const std::string specfile;
+};
+
+// helper function to create the request packet as we expect the
+// socket requestor to send
+ConstElementPtr
+createExpectedRequest(const std::string& address,
+                      int port,
+                      const std::string& protocol,
+                      const std::string& share_mode,
+                      const std::string& share_name)
+{
+    // create command arguments
+    const ElementPtr command_args = Element::createMap();
+    command_args->set("address", Element::create(address));
+    command_args->set("port", Element::create(port));
+    command_args->set("protocol", Element::create(protocol));
+    command_args->set("share_mode", Element::create(share_mode));
+    command_args->set("share_name", Element::create(share_name));
+
+    // create the envelope
+    const ElementPtr packet = Element::createList();
+    packet->add(Element::create("Boss"));
+    packet->add(Element::create("*"));
+    packet->add(createCommand("get_socket", command_args));
+
+    return (packet);
+}
+
+TEST_F(SocketRequestorTest, testSocketRequestMessages) {
+    // For each request, it will raise CCSessionError, since we don't
+    // answer here.
+    // We are only testing the request messages that are sent,
+    // so for this test that is no problem
+    clearMsgQueue();
+    ConstElementPtr expected_request;
+
+    expected_request = createExpectedRequest("192.0.2.1", 12345, "UDP",
+                                             "NO", "test");
+    ASSERT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
+                                   "192.0.2.1", 12345,
+                                   SocketRequestor::DONT_SHARE,
+                                   "test"),
+                 CCSessionError);
+    ASSERT_EQ(1, session.getMsgQueue()->size());
+    ASSERT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+
+    clearMsgQueue();
+    expected_request = createExpectedRequest("192.0.2.2", 1, "TCP",
+                                             "ANY", "test2");
+    ASSERT_THROW(socketRequestor().requestSocket(SocketRequestor::TCP,
+                                   "192.0.2.2", 1,
+                                   SocketRequestor::SHARE_ANY,
+                                   "test2"),
+                 CCSessionError);
+    ASSERT_EQ(1, session.getMsgQueue()->size());
+    ASSERT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+
+    clearMsgQueue();
+    expected_request = createExpectedRequest("::1", 2, "UDP",
+                                             "SAMEAPP", "test3");
+    ASSERT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
+                                   "::1", 2,
+                                   SocketRequestor::SHARE_SAME,
+                                   "test3"),
+                 CCSessionError);
+    ASSERT_EQ(1, session.getMsgQueue()->size());
+    ASSERT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+}
+
+TEST_F(SocketRequestorTest, invalidParameterForSocketRequest) {
+    // Bad protocol
+    EXPECT_THROW(socketRequestor().
+                 requestSocket(static_cast<SocketRequestor::Protocol>(2),
+                               "192.0.2.1", 12345,
+                               SocketRequestor::DONT_SHARE,
+                               "test"),
+                 InvalidParameter);
+
+    // Bad share mode
+    EXPECT_THROW(socketRequestor().
+                 requestSocket(SocketRequestor::UDP,
+                               "192.0.2.1", 12345,
+                               static_cast<SocketRequestor::ShareMode>(3),
+                               "test"),
+                 InvalidParameter);
+}
+
+TEST_F(SocketRequestorTest, testBadRequestAnswers) {
+    // Test various scenarios where the requestor gets back bad answers
+
+    // Should raise CCSessionError if there is no answer
+    ASSERT_THROW(doRequest(), CCSessionError);
+
+    // Also if the answer does not match the format
+    session.getMessages()->add(createAnswer());
+    ASSERT_THROW(doRequest(), CCSessionError);
+
+    // Now a 'real' answer, should fail on socket connect (no such file)
+    addAnswer("foo", "/does/not/exist");
+    ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+
+    // Another failure (domain socket path too long)
+    addAnswer("foo", std::string(1000, 'x'));
+    ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+
+    // Test values around path boundary
+    struct sockaddr_un sock_un;
+    const std::string max_len(sizeof(sock_un.sun_path) - 1, 'x');
+    addAnswer("foo", max_len);
+    // The failure should NOT contain 'too long'
+    // (explicitly checking for existance of nonexistence of 'too long',
+    // as opposed to the actual error, since 'too long' is a value we set).
+    try {
+        doRequest();
+        FAIL() << "doRequest did not throw an exception";
+    } catch (const SocketRequestor::SocketError& se) {
+        ASSERT_EQ(std::string::npos, std::string(se.what()).find("too long"));
+    }
+
+    const std::string too_long(sizeof(sock_un.sun_path), 'x');
+    addAnswer("foo", too_long);
+    // The failure SHOULD contain 'too long'
+    try {
+        doRequest();
+        FAIL() << "doRequest did not throw an exception";
+    } catch (const SocketRequestor::SocketError& se) {
+        ASSERT_NE(std::string::npos, std::string(se.what()).find("too long"));
+    }
+
+    // Send back an error response
+    session.getMessages()->add(createAnswer(1, "error"));
+    ASSERT_THROW(doRequest(), CCSessionError);
+}
+
+// Helper function to create the release commands as we expect
+// them to be sent by the SocketRequestor class
+ConstElementPtr
+createExpectedRelease(const std::string& token) {
+    // create command arguments
+    const ElementPtr command_args = Element::createMap();
+    command_args->set("token", Element::create(token));
+
+    // create the envelope
+    const ElementPtr packet = Element::createList();
+    packet->add(Element::create("Boss"));
+    packet->add(Element::create("*"));
+    packet->add(createCommand("drop_socket", command_args));
+
+    return (packet);
+}
+
+TEST_F(SocketRequestorTest, testSocketReleaseMessages) {
+    ConstElementPtr expected_release;
+
+    session.getMessages()->add(createAnswer());
+
+    clearMsgQueue();
+    expected_release = createExpectedRelease("foo");
+    socketRequestor().releaseSocket("foo");
+    ASSERT_EQ(1, session.getMsgQueue()->size());
+    ASSERT_EQ(*expected_release, *(session.getMsgQueue()->get(0)));
+
+    session.getMessages()->add(createAnswer());
+    clearMsgQueue();
+    expected_release = createExpectedRelease("bar");
+    socketRequestor().releaseSocket("bar");
+    ASSERT_EQ(1, session.getMsgQueue()->size());
+    ASSERT_EQ(*expected_release, *(session.getMsgQueue()->get(0)));
+}
+
+TEST_F(SocketRequestorTest, testBadSocketReleaseAnswers) {
+    // Should fail if there is no answer at all
+    ASSERT_THROW(socketRequestor().releaseSocket("bar"),
+                 CCSessionError);
+
+    // Should also fail if the answer is an error
+    session.getMessages()->add(createAnswer(1, "error"));
+    ASSERT_THROW(socketRequestor().releaseSocket("bar"),
+                 SocketRequestor::SocketError);
+}
+
+// A helper function to impose a read timeout for the server socket
+// in order to avoid deadlock when the client side has a bug and doesn't
+// send expected data.
+// It returns true when the timeout is set successfully; otherwise false.
+bool
+setRecvTimo(int s) {
+    const struct timeval timeo = { 10, 0 }; // 10sec, arbitrary choice
+    if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)) == 0) {
+        return (true);
+    }
+    if (errno == ENOPROTOOPT) { // deviant OS, give up using it.
+        return (false);
+    }
+    isc_throw(isc::Unexpected, "set RCVTIMEO failed: " << strerror(errno));
+}
+
+// Helper test class that creates a randomly named domain socket
+// Upon init, it will only reserve the name (and place an empty file in its
+// place).
+// When run() is called, it creates the socket, forks, and the child will
+// listen for a connection, then send all the data passed to run to that
+// connection, and then close the socket
+class TestSocket {
+public:
+    TestSocket() : fd_(-1) {
+        path_ = strdup("test_socket.XXXXXX");
+        // Misuse mkstemp to generate a file name.
+        const int f = mkstemp(path_);
+        if (f == -1) {
+            isc_throw(Unexpected, "mkstemp failed: " << strerror(errno));
+        }
+        // Just need the name, so immediately close
+        close(f);
+    }
+
+    ~TestSocket() {
+        cleanup();
+    }
+
+    void
+    cleanup() {
+        unlink(path_);
+        if (path_ != NULL) {
+            free(path_);
+            path_ = NULL;
+        }
+        if (fd_ != -1) {
+            close(fd_);
+            fd_ = -1;
+        }
+    }
+
+    // Returns the path used for the socket
+    const char* getPath() const {
+        return (path_);
+    }
+
+    // create socket, fork, and serve if child (child will exit when done).
+    // If the underlying system doesn't allow to set read timeout, tell the
+    // caller that via a false return value so that the caller can avoid
+    // performing tests that could result in a dead lock.
+    bool run(const std::vector<std::pair<std::string, int> >& data) {
+        create();
+        const bool timo_ok = setRecvTimo(fd_);
+        const int child_pid = fork();
+        if (child_pid == 0) {
+            serve(data);
+            exit(0);
+        } else {
+            // parent does not need fd anymore
+            close(fd_);
+            fd_ = -1;
+        }
+        return (timo_ok);
+    }
+private:
+    // Actually create the socket and listen on it
+    void
+    create() {
+        fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+        if (fd_ == -1) {
+            isc_throw(Unexpected, "Unable to create socket");
+        }
+        struct sockaddr_un socket_address;
+        socket_address.sun_family = AF_UNIX;
+        socklen_t len = strlen(path_);
+        if (len > sizeof(socket_address.sun_path)) {
+            isc_throw(Unexpected,
+                      "mkstemp() created a filename too long for sun_path");
+        }
+        strncpy(socket_address.sun_path, path_, len);
+#ifdef HAVE_SA_LEN
+        socket_address.sun_len = len;
+#endif
+
+        len += offsetof(struct sockaddr_un, sun_path);
+        // Remove the random file we created so we can reuse it for
+        // a domain socket connection. This contains a minor race condition
+        // but for the purposes of this test it should be small enough
+        unlink(path_);
+        if (bind(fd_, (const struct sockaddr*)&socket_address, len) == -1) {
+            isc_throw(Unexpected,
+                      "unable to bind to test domain socket " << path_ <<
+                      ": " << strerror(errno));
+        }
+
+        if (listen(fd_, 1) == -1) {
+            isc_throw(Unexpected,
+                      "unable to listen on test domain socket " << path_ <<
+                      ": " << strerror(errno));
+        }
+    }
+
+    // Accept one connection, then for each value of the vector,
+    // read the socket token from the connection and match the string
+    // part of the vector element, and send the integer part of the element
+    // using send_fd() (prepended by a status code 'ok').  For simplicity
+    // we assume the tokens are 4 bytes long; if the test case uses a
+    // different size of token the test will fail.
+    //
+    // There are a few specific exceptions;
+    // when the value is -1, it will send back an error value (signaling
+    // CREATOR_SOCKET_UNAVAILABLE)
+    // when the value is -2, it will send a byte signaling CREATOR_SOCKET_OK
+    // first, and then one byte from some string (i.e. bad data, not using
+    // send_fd())
+    //
+    // NOTE: client_fd could leak on exception.  This should be cleaned up.
+    // See the note about SocketSessionReceiver in socket_request.cc.
+    void
+    serve(const std::vector<std::pair<std::string, int> > data) {
+        const int client_fd = accept(fd_, NULL, NULL);
+        if (client_fd == -1) {
+            isc_throw(Unexpected, "Error in accept(): " << strerror(errno));
+        }
+        if (!setRecvTimo(client_fd)) {
+            // In the loop below we do blocking read.  To avoid deadlock
+            // when the parent is buggy we'll skip it unless we can
+            // set a read timeout on the socket.
+            return;
+        }
+        typedef std::pair<std::string, int> DataPair;
+        BOOST_FOREACH(DataPair cur_data, data) {
+            char buf[5];
+            memset(buf, 0, 5);
+            if (isc::util::io::read_data(client_fd, buf, 4) != 4) {
+                isc_throw(Unexpected, "unable to receive socket token");
+            }
+            if (cur_data.first != buf) {
+                isc_throw(Unexpected, "socket token mismatch: expected="
+                          << cur_data.first << ", actual=" << buf);
+            }
+
+            bool result;
+            if (cur_data.second == -1) {
+                // send 'CREATOR_SOCKET_UNAVAILABLE'
+                result = isc::util::io::write_data(client_fd, "0\n", 2);
+            } else if (cur_data.second == -2) {
+                // send 'CREATOR_SOCKET_OK' first
+                result = isc::util::io::write_data(client_fd, "1\n", 2);
+                if (result) {
+                    if (send(client_fd, "a", 1, 0) != 1) {
+                        result = false;
+                    }
+                }
+            } else {
+                // send 'CREATOR_SOCKET_OK' first
+                result = isc::util::io::write_data(client_fd, "1\n", 2);
+                if (result) {
+                    if (isc::util::io::send_fd(client_fd,
+                                               cur_data.second) != 0) {
+                        result = false;
+                    }
+                }
+            }
+            if (!result) {
+                isc_throw(Exception, "Error in send_fd(): " <<
+                          strerror(errno));
+            }
+        }
+        close(client_fd);
+    }
+
+    int fd_;
+    char* path_;
+};
+
+TEST_F(SocketRequestorTest, testSocketPassing) {
+    TestSocket ts;
+    std::vector<std::pair<std::string, int> > data;
+    data.push_back(std::pair<std::string, int>("foo\n", 1));
+    data.push_back(std::pair<std::string, int>("bar\n", 2));
+    data.push_back(std::pair<std::string, int>("foo\n", 3));
+    data.push_back(std::pair<std::string, int>("foo\n", 1));
+    data.push_back(std::pair<std::string, int>("foo\n", -1));
+    data.push_back(std::pair<std::string, int>("foo\n", -2));
+
+    // run() returns true iff we can specify read timeout so we avoid a
+    // deadlock.  Unless there's a bug the test should succeed even without the
+    // timeout, but we don't want to make the test hang up in case with an
+    // unexpected bug, so we'd rather skip most of the tests in that case.
+    const bool timo_ok = ts.run(data);
+    SocketRequestor::SocketID socket_id;
+    if (timo_ok) {
+        // 1 should be ok
+        addAnswer("foo", ts.getPath());
+        socket_id = doRequest();
+        ASSERT_EQ("foo", socket_id.second);
+        ASSERT_EQ(0, close(socket_id.first));
+
+        // 2 should be ok too
+        addAnswer("bar", ts.getPath());
+        socket_id = doRequest();
+        ASSERT_EQ("bar", socket_id.second);
+        ASSERT_EQ(0, close(socket_id.first));
+
+        // 3 should be ok too (reuse earlier token)
+        addAnswer("foo", ts.getPath());
+        socket_id = doRequest();
+        ASSERT_EQ("foo", socket_id.second);
+        ASSERT_EQ(0, close(socket_id.first));
+    }
+
+    // Create a second socket server, to test that multiple different
+    // domains sockets would work as well (even though we don't actually
+    // use that feature)
+    TestSocket ts2;
+    std::vector<std::pair<std::string, int> > data2;
+    data2.push_back(std::pair<std::string, int>("foo\n", 1));
+    const bool timo_ok2 = ts2.run(data2);
+
+    if (timo_ok2) {
+        // 1 should be ok
+        addAnswer("foo", ts2.getPath());
+        socket_id = doRequest();
+        ASSERT_EQ("foo", socket_id.second);
+        ASSERT_EQ(0, close(socket_id.first));
+    }
+
+    if (timo_ok) {
+        // Now use first socket again
+        addAnswer("foo", ts.getPath());
+        socket_id = doRequest();
+        ASSERT_EQ("foo", socket_id.second);
+        ASSERT_EQ(0, close(socket_id.first));
+
+        // -1 is a "normal" error
+        addAnswer("foo", ts.getPath());
+        ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+
+        // -2 is an unexpected error.  After this point it's not guaranteed the
+        // connection works as intended.
+        addAnswer("foo", ts.getPath());
+        ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+    }
+
+    // Vector is of first socket is now empty, so the socket should be gone
+    addAnswer("foo", ts.getPath());
+    ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+
+    // Vector is of second socket is now empty too, so the socket should be
+    // gone
+    addAnswer("foo", ts2.getPath());
+    ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+}
+
+}

+ 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
+        server_common::initTestSocketRequestor(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
+        server_common::initTestSocketRequestor(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();

+ 16 - 19
src/lib/util/io/fd.cc

@@ -23,23 +23,23 @@ namespace io {
 
 bool
 write_data(const int fd, const void *buffer_v, const size_t length) {
-
     const unsigned char* buffer(static_cast<const unsigned char*>(buffer_v));
     size_t remaining = length;  // Amount remaining to be written
 
+    // Just keep writing until all is written
     while (remaining > 0) {
-        ssize_t amount = write(fd, buffer, remaining);
-        if (amount == -1) {
-            // Some error.  Ignore interrupted system calls otherwise return
-            // an error indication.
-            if (errno != EINTR) {
-                return false;
+        const int written = write(fd, buffer, remaining);
+        if (written == -1) {
+            if (errno == EINTR) { // Just keep going
+                continue;
+            } else {
+                return (false);
             }
 
-        } else if (amount > 0) {
-            // Wrote "amount" bytes from the buffer
-            remaining -= amount;
-            buffer += amount;
+        } else if (written > 0) {
+            // Wrote "written" bytes from the buffer
+            remaining -= written;
+            buffer += written;
 
         } else {
             // Wrote zero bytes from the buffer. We should not get here as any
@@ -54,24 +54,21 @@ write_data(const int fd, const void *buffer_v, const size_t length) {
 
 ssize_t
 read_data(const int fd, void *buffer_v, const size_t length) {
-
     unsigned char* buffer(static_cast<unsigned char*>(buffer_v));
     size_t remaining = length;   // Amount remaining to be read
 
     while (remaining > 0) {
-        ssize_t amount = read(fd, buffer, remaining);
+        const int amount = read(fd, buffer, remaining);
         if (amount == -1) {
-            // Some error.  Ignore interrupted system calls otherwise return
-            // an error indication.
-            if (errno != EINTR) {
-                return -1;
+            if (errno == EINTR) { // Continue on interrupted call
+                continue;
+            } else {
+                return (-1);
             }
-
         } else if (amount > 0) {
             // Read "amount" bytes into the buffer
             remaining -= amount;
             buffer += amount;
-
         } else {
             // EOF - end the read
             break;