Browse Source

Merge branch 'work/sock/cppclient' into scfinal

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

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

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

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

@@ -41,6 +41,7 @@
 #include <testutils/dnsmessage_test.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 #include <testutils/srv_test.h>
 #include <testutils/portconfig.h>
 #include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
 
 
 using namespace std;
 using namespace std;
 using namespace isc::cc;
 using namespace isc::cc;
@@ -68,7 +69,8 @@ protected:
     AuthSrvTest() :
     AuthSrvTest() :
         dnss_(ios_, NULL, NULL, NULL),
         dnss_(ios_, NULL, NULL, NULL),
         server(true, xfrout),
         server(true, xfrout),
-        rrclass(RRClass::IN())
+        rrclass(RRClass::IN()),
+        sock_requestor_(dnss_, address_store_, 53210)
     {
     {
         server.setDNSService(dnss_);
         server.setDNSService(dnss_);
         server.setXfrinSession(&notify_session);
         server.setXfrinSession(&notify_session);
@@ -85,6 +87,8 @@ protected:
     AuthSrv server;
     AuthSrv server;
     const RRClass rrclass;
     const RRClass rrclass;
     vector<uint8_t> response_data;
     vector<uint8_t> response_data;
+    AddressList address_store_;
+    TestSocketRequestor sock_requestor_;
 };
 };
 
 
 // A helper function that builds a response to version.bind/TXT/CH that
 // 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) {
 TEST_F(AuthSrvTest, listenAddresses) {
     isc::testutils::portconfig::listenAddresses(server);
     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/mockups.h>
 #include <testutils/portconfig.h>
 #include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
 
 
 using namespace isc::dns;
 using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::data;
@@ -44,7 +45,8 @@ protected:
     AuthConfigTest() :
     AuthConfigTest() :
         dnss_(ios_, NULL, NULL, NULL),
         dnss_(ios_, NULL, NULL, NULL),
         rrclass(RRClass::IN()),
         rrclass(RRClass::IN()),
-        server(true, xfrout)
+        server(true, xfrout),
+        sock_requestor_(dnss_, address_store_, 53210)
     {
     {
         server.setDNSService(dnss_);
         server.setDNSService(dnss_);
     }
     }
@@ -53,6 +55,9 @@ protected:
     const RRClass rrclass;
     const RRClass rrclass;
     MockXfroutClient xfrout;
     MockXfroutClient xfrout;
     AuthSrv server;
     AuthSrv server;
+    isc::server_common::portconfig::AddressList address_store_;
+private:
+    isc::testutils::TestSocketRequestor sock_requestor_;
 };
 };
 
 
 TEST_F(AuthConfigTest, datasourceConfig) {
 TEST_F(AuthConfigTest, datasourceConfig) {

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

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

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

@@ -49,6 +49,7 @@
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/srv_test.h>
 #include <testutils/srv_test.h>
 #include <testutils/portconfig.h>
 #include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
 
 
 using namespace std;
 using namespace std;
 using boost::scoped_ptr;
 using boost::scoped_ptr;
@@ -81,7 +82,10 @@ protected:
     scoped_ptr<const IOMessage> query_message;
     scoped_ptr<const IOMessage> query_message;
     scoped_ptr<const Client> client;
     scoped_ptr<const Client> client;
     scoped_ptr<const RequestContext> request;
     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);
         server.setDNSService(dnss);
     }
     }
     const RequestContext& createRequest(const string& source_addr) {
     const RequestContext& createRequest(const string& source_addr) {
@@ -96,6 +100,8 @@ protected:
         return (*request);
         return (*request);
     }
     }
     void invalidTest(const string &JSON, const string& name);
     void invalidTest(const string &JSON, const string& name);
+    isc::server_common::portconfig::AddressList address_store_;
+    isc::testutils::TestSocketRequestor sock_requestor_;
 };
 };
 
 
 TEST_F(ResolverConfig, forwardAddresses) {
 TEST_F(ResolverConfig, forwardAddresses) {
@@ -310,6 +316,20 @@ TEST_F(ResolverConfig, invalidForwardAddresses) {
 // Try setting the addresses directly
 // Try setting the addresses directly
 TEST_F(ResolverConfig, listenAddresses) {
 TEST_F(ResolverConfig, listenAddresses) {
     isc::testutils::portconfig::listenAddresses(server);
     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
 // Try setting some addresses and a rollback

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

@@ -25,6 +25,7 @@ libasiodns_la_SOURCES += dns_service.cc dns_service.h
 libasiodns_la_SOURCES += tcp_server.cc tcp_server.h
 libasiodns_la_SOURCES += tcp_server.cc tcp_server.h
 libasiodns_la_SOURCES += udp_server.cc udp_server.h
 libasiodns_la_SOURCES += udp_server.cc udp_server.h
 libasiodns_la_SOURCES += io_fetch.cc io_fetch.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
 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
 $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
 % 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
 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.
 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_;
     DNSLookup *lookup_;
     DNSAnswer *answer_;
     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) {
     void addServer(uint16_t port, const asio::ip::address& address) {
         try {
         try {
             dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
             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));
     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
 void
 DNSService::clearServers() {
 DNSService::clearServers() {
     BOOST_FOREACH(const DNSServiceImpl::DNSServerPtr& s, impl_->servers_) {
     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
     /// \brief Add another server to the service
     void addServer(uint16_t port, const std::string &address);
     void addServer(uint16_t port, const std::string &address);
     void addServer(const char &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
     /// \brief Remove all servers from the service
     void clearServers();
     void clearServers();
 
 

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

@@ -38,15 +38,13 @@
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
 #include <dns/opcode.h>
 #include <dns/opcode.h>
 #include <dns/rcode.h>
 #include <dns/rcode.h>
-#include <log/logger.h>
-#include <log/macros.h>
 
 
-#include <asiodns/asiodns_messages.h>
 #include <asiodns/io_fetch.h>
 #include <asiodns/io_fetch.h>
 
 
 #include <util/buffer.h>
 #include <util/buffer.h>
 #include <util/random/qid_gen.h>
 #include <util/random/qid_gen.h>
 
 
+#include <asiodns/logger.h>
 
 
 using namespace asio;
 using namespace asio;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
@@ -59,10 +57,6 @@ using namespace std;
 namespace isc {
 namespace isc {
 namespace asiodns {
 namespace asiodns {
 
 
-/// Use the ASIO logger
-
-isc::log::Logger logger("asiolink");
-
 // Log debug verbosity
 // Log debug verbosity
 
 
 const int DBG_IMPORTANT = DBGLVL_TRACE_BASIC;
 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/dummy_io_cb.h>
 #include <asiolink/tcp_endpoint.h>
 #include <asiolink/tcp_endpoint.h>
 #include <asiolink/tcp_socket.h>
 #include <asiolink/tcp_socket.h>
-#include <tcp_server.h>
+#include <asiodns/tcp_server.h>
-
+#include <asiodns/logger.h>
 
 
 using namespace asio;
 using namespace asio;
 using asio::ip::udp;
 using asio::ip::udp;
@@ -69,6 +69,31 @@ TCPServer::TCPServer(io_service& io_service,
     acceptor_->listen();
     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
 void
 TCPServer::operator()(asio::error_code ec, size_t length) {
 TCPServer::operator()(asio::error_code ec, size_t length) {
     /// Because the coroutine reentry block is implemented as
     /// 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 DNSLookup* lookup = NULL,
                        const DNSAnswer* answer = 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(),
     void operator()(asio::error_code ec = asio::error_code(),
                     size_t length = 0);
                     size_t length = 0);
     void asyncLookup();
     void asyncLookup();

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

@@ -23,6 +23,8 @@
 #include <asiodns/dns_answer.h>
 #include <asiodns/dns_answer.h>
 #include <asiodns/dns_lookup.h>
 #include <asiodns/dns_lookup.h>
 #include <string>
 #include <string>
+#include <cstring>
+#include <cerrno>
 #include <csignal>
 #include <csignal>
 #include <unistd.h> //for alarm
 #include <unistd.h> //for alarm
 
 
@@ -30,6 +32,8 @@
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
 #include <boost/function.hpp>
 #include <boost/function.hpp>
 
 
+#include <sys/types.h>
+#include <sys/socket.h>
 
 
 /// The following tests focus on stop interface for udp and
 /// The following tests focus on stop interface for udp and
 /// tcp server, there are lots of things can be shared to test
 /// tcp server, there are lots of things can be shared to test
@@ -70,11 +74,12 @@ using namespace isc::asiodns;
 using namespace asio;
 using namespace asio;
 
 
 namespace {
 namespace {
-static const std::string server_ip = "127.0.0.1";
+const char* const server_ip = "::1";
 const int server_port = 5553;
 const int server_port = 5553;
+const char* const server_port_str = "5553";
 //message client send to udp server, which isn't dns package
 //message client send to udp server, which isn't dns package
 //just for simple testing
 //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
 // \brief provide capacity to derived class the ability
 // to stop DNSServer at certern point
 // to stop DNSServer at certern point
@@ -200,15 +205,15 @@ class SimpleClient : public ServerStopper {
 
 
 class UDPClient : public SimpleClient {
 class UDPClient : public SimpleClient {
     public:
     public:
-    //After 1 seconds without feedback client will stop wait
+    //After 1 second without feedback client will stop wait
-    static const unsigned int server_time_out = 1;
+    static const unsigned int SERVER_TIME_OUT = 1;
 
 
     UDPClient(asio::io_service& service, const ip::udp::endpoint& server) :
     UDPClient(asio::io_service& service, const ip::udp::endpoint& server) :
-        SimpleClient(service, server_time_out)
+        SimpleClient(service, SERVER_TIME_OUT)
     {
     {
         server_ = server;
         server_ = server;
         socket_.reset(new ip::udp::socket(service));
         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:
     public:
     // after 2 seconds without feedback client will stop wait,
     // after 2 seconds without feedback client will stop wait,
     // this includes connect, send message and recevice message
     // 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)
     TCPClient(asio::io_service& service, const ip::tcp::endpoint& server)
-        : SimpleClient(service, server_time_out)
+        : SimpleClient(service, SERVER_TIME_OUT)
     {
     {
         server_ = server;
         server_ = server;
         socket_.reset(new ip::tcp::socket(service));
         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_;
     uint16_t data_to_send_len_;
 };
 };
 
 
-
+// \brief provide the context which including two clients and
-
+// two servers, UDP client will only communicate with UDP server, same for TCP
-// \brief provide the context which including two client and
+// client
-// two server, udp client will only communicate with udp server, same for tcp client
+//
-class DNSServerTest : public::testing::Test {
+// 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:
     protected:
-        void SetUp() {
+        DNSServerTestBase() :
-            ip::address server_address = ip::address::from_string(server_ip);
+            server_address_(ip::address::from_string(server_ip)),
-            checker_ = new DummyChecker();
+            checker_(new DummyChecker()),
-            lookup_ = new DummyLookup();
+            lookup_(new DummyLookup()),
-            answer_ = new SimpleAnswer();
+            answer_(new SimpleAnswer()),
-            udp_server_ = new UDPServer(service, server_address, server_port,
+            udp_client_(new UDPClient(service,
-                    checker_, lookup_, answer_);
+                                      ip::udp::endpoint(server_address_,
-            udp_client_ = new UDPClient(service,
+                                                         server_port))),
-                    ip::udp::endpoint(server_address,
+            tcp_client_(new TCPClient(service,
-                        server_port));
+                                      ip::tcp::endpoint(server_address_,
-            tcp_server_ = new TCPServer(service, server_address, server_port,
+                                                        server_port))),
-                    checker_, lookup_, answer_);
+            udp_server_(NULL),
-            tcp_client_ = new TCPClient(service,
+            tcp_server_(NULL)
-                    ip::tcp::endpoint(server_address,
+        {
-                        server_port));
+            current_service = &service;
         }
         }
 
 
-
+        ~ DNSServerTestBase() {
-        void TearDown() {
+            if (udp_server_ != NULL) {
-            udp_server_->stop();
+                udp_server_->stop();
-            tcp_server_->stop();
+            }
+            if (tcp_server_ != NULL) {
+                tcp_server_->stop();
+            }
             delete checker_;
             delete checker_;
             delete lookup_;
             delete lookup_;
             delete answer_;
             delete answer_;
@@ -339,22 +351,26 @@ class DNSServerTest : public::testing::Test {
             delete udp_client_;
             delete udp_client_;
             delete tcp_server_;
             delete tcp_server_;
             delete tcp_client_;
             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,
         void testStopServerByStopper(DNSServer* server, SimpleClient* client,
                 ServerStopper* stopper)
                 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;
             io_service_is_time_out = false;
             stopper->setServerToStop(server);
             stopper->setServerToStop(server);
             (*server)();
             (*server)();
             client->sendDataThenWaitForFeedback(query_message);
             client->sendDataThenWaitForFeedback(query_message);
-            // Since thread hasn't been introduced into the tool box, using signal
+            // Since thread hasn't been introduced into the tool box, using
-            // to make sure run function will eventually return even server stop
+            // signal to make sure run function will eventually return even
-            // failed
+            // server stop failed
-            void (*prev_handler)(int) = std::signal(SIGALRM, DNSServerTest::stopIOService);
+            void (*prev_handler)(int) =
-            alarm(io_service_time_out);
+                std::signal(SIGALRM, DNSServerTestBase::stopIOService);
+            current_service = &service;
+            alarm(IO_SERVICE_TIME_OUT);
             service.run();
             service.run();
             service.reset();
             service.reset();
             //cancel scheduled alarm
             //cancel scheduled alarm
@@ -362,71 +378,155 @@ class DNSServerTest : public::testing::Test {
             std::signal(SIGALRM, prev_handler);
             std::signal(SIGALRM, prev_handler);
         }
         }
 
 
-
         static void stopIOService(int _no_use_parameter) {
         static void stopIOService(int _no_use_parameter) {
             io_service_is_time_out = true;
             io_service_is_time_out = true;
-            service.stop();
+            if (current_service != NULL) {
+                current_service->stop();
+            }
         }
         }
 
 
         bool serverStopSucceed() const {
         bool serverStopSucceed() const {
             return (!io_service_is_time_out);
             return (!io_service_is_time_out);
         }
         }
 
 
-        DummyChecker* checker_;
+        asio::io_service service;
-        DummyLookup*  lookup_;
+        const ip::address server_address_;
-        SimpleAnswer* answer_;
+        DummyChecker* const checker_;
+        DummyLookup*  const lookup_;
+        SimpleAnswer* const answer_;
+        UDPClient*    const udp_client_;
+        TCPClient*    const tcp_client_;
         UDPServer*    udp_server_;
         UDPServer*    udp_server_;
-        UDPClient*    udp_client_;
-        TCPClient*    tcp_client_;
         TCPServer*    tcp_server_;
         TCPServer*    tcp_server_;
 
 
         // To access them in signal handle function, the following
         // To access them in signal handle function, the following
         // variables have to be static.
         // variables have to be static.
-        static asio::io_service service;
+        static asio::io_service* current_service;
         static bool io_service_is_time_out;
         static bool io_service_is_time_out;
 };
 };
 
 
-bool DNSServerTest::io_service_is_time_out = false;
+// Initialization with name and port
-asio::io_service DNSServerTest::service;
+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
 // Test whether server stopped successfully after client get response
 // client will send query and start to wait for response, once client
 // 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
 // get response, udp server will be stopped, the io service won't quit
 // if udp server doesn't stop successfully.
 // if udp server doesn't stop successfully.
-TEST_F(DNSServerTest, stopUDPServerAfterOneQuery) {
+TYPED_TEST(DNSServerTest, stopUDPServerAfterOneQuery) {
-    testStopServerByStopper(udp_server_, udp_client_, udp_client_);
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
-    EXPECT_EQ(query_message, udp_client_->getReceivedData());
+                                  this->udp_client_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 // Test whether udp server stopped successfully before server start to serve
 // Test whether udp server stopped successfully before server start to serve
-TEST_F(DNSServerTest, stopUDPServerBeforeItStartServing) {
+TYPED_TEST(DNSServerTest, stopUDPServerBeforeItStartServing) {
-    udp_server_->stop();
+    this->udp_server_->stop();
-    testStopServerByStopper(udp_server_, udp_client_, udp_client_);
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
-    EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+                                  this->udp_client_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 
 
 // Test whether udp server stopped successfully during message check
 // Test whether udp server stopped successfully during message check
-TEST_F(DNSServerTest, stopUDPServerDuringMessageCheck) {
+TYPED_TEST(DNSServerTest, stopUDPServerDuringMessageCheck) {
-    testStopServerByStopper(udp_server_, udp_client_, checker_);
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
-    EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+                                  this->checker_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 // Test whether udp server stopped successfully during query lookup
 // Test whether udp server stopped successfully during query lookup
-TEST_F(DNSServerTest, stopUDPServerDuringQueryLookup) {
+TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
-    testStopServerByStopper(udp_server_, udp_client_, lookup_);
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
-    EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+                                  this->lookup_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 // Test whether udp server stopped successfully during composing answer
 // Test whether udp server stopped successfully during composing answer
-TEST_F(DNSServerTest, stopUDPServerDuringPrepareAnswer) {
+TYPED_TEST(DNSServerTest, stopUDPServerDuringPrepareAnswer) {
-    testStopServerByStopper(udp_server_, udp_client_, answer_);
+    this->testStopServerByStopper(this->udp_server_, this->udp_client_,
-    EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+                                  this->answer_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 static void stopServerManyTimes(DNSServer *server, unsigned int times) {
 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
 // Test whether udp server stop interface can be invoked several times without
 // throw any exception
 // throw any exception
-TEST_F(DNSServerTest, stopUDPServeMoreThanOnce) {
+TYPED_TEST(DNSServerTest, stopUDPServeMoreThanOnce) {
     ASSERT_NO_THROW({
     ASSERT_NO_THROW({
         boost::function<void()> stop_server_3_times
         boost::function<void()> stop_server_3_times
-            = boost::bind(stopServerManyTimes, udp_server_, 3);
+            = boost::bind(stopServerManyTimes, this->udp_server_, 3);
-        udp_client_->setGetFeedbackCallback(stop_server_3_times);
+        this->udp_client_->setGetFeedbackCallback(stop_server_3_times);
-        testStopServerByStopper(udp_server_, udp_client_, udp_client_);
+        this->testStopServerByStopper(this->udp_server_,
-        EXPECT_EQ(query_message, udp_client_->getReceivedData());
+                                      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) {
+TYPED_TEST(DNSServerTest, stopTCPServerAfterOneQuery) {
-    testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
-    EXPECT_EQ(query_message, tcp_client_->getReceivedData());
+                                  this->tcp_client_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 
 
 // Test whether tcp server stopped successfully before server start to serve
 // Test whether tcp server stopped successfully before server start to serve
-TEST_F(DNSServerTest, stopTCPServerBeforeItStartServing) {
+TYPED_TEST(DNSServerTest, stopTCPServerBeforeItStartServing) {
-    tcp_server_->stop();
+    this->tcp_server_->stop();
-    testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
-    EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
+                                  this->tcp_client_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 
 
 // Test whether tcp server stopped successfully during message check
 // Test whether tcp server stopped successfully during message check
-TEST_F(DNSServerTest, stopTCPServerDuringMessageCheck) {
+TYPED_TEST(DNSServerTest, stopTCPServerDuringMessageCheck) {
-    testStopServerByStopper(tcp_server_, tcp_client_, checker_);
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
-    EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
+                                  this->checker_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 // Test whether tcp server stopped successfully during query lookup
 // Test whether tcp server stopped successfully during query lookup
-TEST_F(DNSServerTest, stopTCPServerDuringQueryLookup) {
+TYPED_TEST(DNSServerTest, stopTCPServerDuringQueryLookup) {
-    testStopServerByStopper(tcp_server_, tcp_client_, lookup_);
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
-    EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
+                                  this->lookup_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 // Test whether tcp server stopped successfully during composing answer
 // Test whether tcp server stopped successfully during composing answer
-TEST_F(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
+TYPED_TEST(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
-    testStopServerByStopper(tcp_server_, tcp_client_, answer_);
+    this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
-    EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
+                                  this->answer_);
-    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+    EXPECT_TRUE(this->serverStopSucceed());
 }
 }
 
 
 
 
 // Test whether tcp server stop interface can be invoked several times without
 // Test whether tcp server stop interface can be invoked several times without
 // throw any exception
 // throw any exception
-TEST_F(DNSServerTest, stopTCPServeMoreThanOnce) {
+TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
     ASSERT_NO_THROW({
     ASSERT_NO_THROW({
         boost::function<void()> stop_server_3_times
         boost::function<void()> stop_server_3_times
-            = boost::bind(stopServerManyTimes, tcp_server_, 3);
+            = boost::bind(stopServerManyTimes, this->tcp_server_, 3);
-        tcp_client_->setGetFeedbackCallback(stop_server_3_times);
+        this->tcp_client_->setGetFeedbackCallback(stop_server_3_times);
-        testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
+        this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
-        EXPECT_EQ(query_message, tcp_client_->getReceivedData());
+                                      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_endpoint.h>
 #include <asiolink/udp_socket.h>
 #include <asiolink/udp_socket.h>
 #include "udp_server.h"
 #include "udp_server.h"
+#include "logger.h"
 
 
 #include <dns/opcode.h>
 #include <dns/opcode.h>
 
 
@@ -53,7 +54,7 @@ namespace asiodns {
  */
  */
 struct UDPServer::Data {
 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
      * This instance will not be used to retrieve and answer the actual
      * query, it will only hold parameters until we wait for the
      * query, it will only hold parameters until we wait for the
      * first packet. But we do initialize the socket in here.
      * first packet. But we do initialize the socket in here.
@@ -74,6 +75,26 @@ struct UDPServer::Data {
         }
         }
         socket_->bind(udp::endpoint(addr, port));
         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
      * 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
 /// The constructor. It just creates new internal state object
 /// and lets it handle the initialization.
 /// and lets it handle the initialization.
 UDPServer::UDPServer(io_service& io_service, const ip::address& addr,
 UDPServer::UDPServer(io_service& io_service, const ip::address& addr,
-    const uint16_t port, SimpleCallback* checkin, DNSLookup* lookup,
+                     const uint16_t port, SimpleCallback* checkin,
-    DNSAnswer* answer) :
+                     DNSLookup* lookup, DNSAnswer* answer) :
     data_(new Data(io_service, addr, port, checkin, lookup, 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"
 /// The function operator is implemented with the "stackless coroutine"
 /// pattern; see internal/coroutine.h for details.
 /// pattern; see internal/coroutine.h for details.
 void
 void

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

@@ -52,6 +52,20 @@ public:
                        DNSLookup* lookup = NULL,
                        DNSLookup* lookup = NULL,
                        DNSAnswer* answer = 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
     /// \brief The function operator
     void operator()(asio::error_code ec = asio::error_code(),
     void operator()(asio::error_code ec = asio::error_code(),
                     size_t length = 0);
                     size_t length = 0);

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

@@ -21,6 +21,7 @@ libserver_common_la_SOURCES = client.h client.cc
 libserver_common_la_SOURCES += keyring.h keyring.cc
 libserver_common_la_SOURCES += keyring.h keyring.cc
 libserver_common_la_SOURCES += portconfig.h portconfig.cc
 libserver_common_la_SOURCES += portconfig.h portconfig.cc
 libserver_common_la_SOURCES += logger.h logger.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.h
 nodist_libserver_common_la_SOURCES += server_common_messages.cc
 nodist_libserver_common_la_SOURCES += server_common_messages.cc
 libserver_common_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 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/portconfig.h>
 #include <server_common/logger.h>
 #include <server_common/logger.h>
+#include <server_common/socket_request.h>
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <asiodns/dns_service.h>
 #include <asiodns/dns_service.h>
@@ -30,6 +31,11 @@ namespace isc {
 namespace server_common {
 namespace server_common {
 namespace portconfig {
 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
 AddressList
 parseAddresses(isc::data::ConstElementPtr addresses,
 parseAddresses(isc::data::ConstElementPtr addresses,
                const std::string& elemName)
                const std::string& elemName)
@@ -76,11 +82,41 @@ parseAddresses(isc::data::ConstElementPtr addresses,
 
 
 namespace {
 namespace {
 
 
+vector<string> current_sockets;
+
 void
 void
 setAddresses(DNSService& service, const AddressList& addresses) {
 setAddresses(DNSService& service, const AddressList& addresses) {
     service.clearServers();
     service.clearServers();
+    BOOST_FOREACH(const string& token, current_sockets) {
+        socketRequestor().releaseSocket(token);
+    }
+    current_sockets.clear();
     BOOST_FOREACH(const AddressPair &address, addresses) {
     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());
         LOG_ERROR(logger, SRVCOMM_ADDRESS_FAIL).arg(e.what());
         try {
         try {
             setAddresses(service, addressStore);
             setAddresses(service, addressStore);
-        }
+        } catch (const exception& e2) {
-        catch (const exception& e2) {
             LOG_FATAL(logger, SRVCOMM_ADDRESS_UNRECOVERABLE).arg(e2.what());
             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
         //Anyway the new configure has problem, we need to notify configure
         //manager the new configure doesn't work
         //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
  * 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
  * 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
+ * fails as well, it doesn't abort the application (to allow reconfiguration),
- * on the new addresses nor on the old ones, the application is useless anyway
+ * but removes all the sockets it listened on. One of the exceptions is
- * and should be restarted by Boss, not to mention that the internal state is
+ * propagated.
- * probably broken).
+ *
+ * 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 newAddresses are the addresses you want to listen on.
  * \param addressStore is the place you store your current addresses. It is
  * \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
  *     the new sockets are handled using this dnsService (and all current
  *     sockets on the service are closed first).
  *     sockets on the service are closed first).
  * \throw asiolink::IOError when initialization or closing of socket fails.
  * \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 std::bad_alloc when allocation fails.
+ * \throw isc::InvalidOperation when the function is called and the
+ *     SocketRequestor isn't initialized yet.
  */
  */
 void
 void
 installListenAddresses(const AddressList& newAddresses,
 installListenAddresses(const AddressList& newAddresses,

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

@@ -0,0 +1,45 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "socket_request.h"
+
+namespace isc {
+namespace server_common {
+
+namespace {
+SocketRequestor* requestor(NULL);
+}
+
+SocketRequestor&
+socketRequestor() {
+    if (requestor != NULL) {
+        return (*requestor);
+    } else {
+        isc_throw(InvalidOperation, "The socket requestor is not initialized");
+    }
+}
+
+void
+SocketRequestor::initTest(SocketRequestor* new_requestor) {
+    requestor = new_requestor;
+}
+
+void
+SocketRequestor::init(config::ModuleCCSession&) {
+    isc_throw(NotImplemented,
+              "The socket requestor will be implemented in #1522");
+}
+
+}
+}

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

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

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

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

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

@@ -0,0 +1,57 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <server_common/socket_request.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::server_common;
+using namespace isc;
+
+namespace {
+
+// Check it throws an exception when it is not initialized
+TEST(SocketRequestorAccess, unitialized) {
+    // Make sure it is not initialized
+    SocketRequestor::initTest(NULL);
+    EXPECT_THROW(socketRequestor(), InvalidOperation);
+}
+
+// It returns whatever it is initialized to
+TEST(SocketRequestorAccess, initialized) {
+    // A concrete implementation that does nothing, just can exist
+    class DummyRequestor : public SocketRequestor {
+    public:
+        DummyRequestor() : SocketRequestor() {}
+        virtual void releaseSocket(const std::string&) {}
+        virtual SocketID requestSocket(Protocol, const std::string&, uint16_t,
+                                       ShareMode, const std::string&)
+        {
+            return (SocketID(0, "")); // Just to silence warnings
+        }
+    };
+    DummyRequestor requestor;
+    // Make sure it is initialized (the test way, of course)
+    SocketRequestor::initTest(&requestor);
+    // It returs the same "pointer" as inserted
+    // The casts are there as the template system seemed to get confused
+    // without them, the types should be correct even without them, but
+    // the EXPECT_EQ wanted to use long long int instead of pointers.
+    EXPECT_EQ(static_cast<const SocketRequestor*>(&requestor),
+              static_cast<const SocketRequestor*>(&socketRequestor()));
+    // Just that we don't have an invalid pointer anyway
+    SocketRequestor::initTest(NULL);
+}
+
+}

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

@@ -14,4 +14,4 @@ libtestutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 libtestutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
 libtestutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
 endif
 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
 void
 listenAddresses(Server& server) {
 listenAddresses(Server& server) {
     using namespace isc::server_common::portconfig;
     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());
     EXPECT_TRUE(server.getListenAddresses().empty());
 
 
     // Try putting there some addresses
     // Try putting there some addresses
@@ -61,7 +61,8 @@ listenAddresses(Server& server) {
     addresses.clear();
     addresses.clear();
     EXPECT_EQ(2, server.getListenAddresses().size());
     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);
     server.setListenAddresses(addresses);
     EXPECT_TRUE(server.getListenAddresses().empty());
     EXPECT_TRUE(server.getListenAddresses().empty());
 }
 }
@@ -95,12 +96,11 @@ listenAddressConfig(Server& server) {
     EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
     EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
     EXPECT_EQ(53210, server.getListenAddresses()[0].second);
     EXPECT_EQ(53210, server.getListenAddresses()[0].second);
 
 
-    // As this is example address, the machine should not have it on
+    // This address is rejected by the test socket requestor
-    // any interface
     config = Element::fromJSON("{"
     config = Element::fromJSON("{"
                                "\"listen_on\": ["
                                "\"listen_on\": ["
                                "   {"
                                "   {"
-                               "       \"address\": \"192.0.2.0\","
+                               "       \"address\": \"192.0.2.2\","
                                "       \"port\": 53210"
                                "       \"port\": 53210"
                                "   }"
                                "   }"
                                "]"
                                "]"

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

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

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

@@ -44,7 +44,7 @@ extern const unsigned int RA_FLAG;
 extern const unsigned int AD_FLAG;
 extern const unsigned int AD_FLAG;
 extern const unsigned int CD_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 {
 class SrvTestBase : public ::testing::Test {
 protected:
 protected:
     SrvTestBase();
     SrvTestBase();