BIND 10 master, updated. 1830215f884e3b5efda52bd4dbb120bdca863a6a Merge #805
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Jan 10 02:04:21 UTC 2012
The branch, master has been updated
via 1830215f884e3b5efda52bd4dbb120bdca863a6a (commit)
via 1627c0152628294dea5b55833b1ac6d66a08cbc0 (commit)
via 7c98c9fd24240015fdebbbae3ae8c313c364d52a (commit)
via 91b5b26bc39a66dc82eb3b863f7aa3264a3a436f (commit)
via 138bb39fd8fdf323fbecdf4a1b2cabea4dcd624e (commit)
via b37cfa5481840b795a6891f0f26bfed5e498317b (commit)
via 61d3224981ddac0ce238e61378dff60996288c21 (commit)
via 526b0b5b5615cd258335b14c6417ee26f0d671f5 (commit)
via ca3cc561237dde1fac32c592056051eb339bea03 (commit)
via d66d0cc215a2436ddf91343f266c47a73d0993f2 (commit)
via f786ba4251dac7a4feb71a604cfdf27003305ddf (commit)
via 01d2bc6d797953339d3d99cdb6fdba7ff76f83d0 (commit)
via ef021f7736be3ae23b19e71707cd383bad8d7c9a (commit)
via 619dbae4c1fdd5668db33590b94dbd847691bad2 (commit)
via 3cdb00878c3a0dd3d31fd2a8a64b47a376b87d37 (commit)
via 9f776b729b14aa1d3678325e880504c68bbe8488 (commit)
via ab54494aee9089bac237cc7308f06145f62312b8 (commit)
via f68758a5f5ebb9bc2405202ee5455944f3d95600 (commit)
via 43fda10b4f6f788050dbed8198fa85ea5b385a80 (commit)
via c836c770e7109302fa1a27d51ae2fec573c03cb5 (commit)
via 4fdec3477a07431fadd2f36a68504038a0a78323 (commit)
via 78b0456c73d95d4a3d4a0c702aedd2c547404107 (commit)
via 3fcadc1a85ca56620c418e8b18cf43e43afbe98d (commit)
via 9d4ae52d559448299b5561bd0d76930c2be2275b (commit)
via 908c1b114ba233fb2c238c7e102ddc0fdb605c9e (commit)
via 5565505e948f85534896a03e7a083c30496638e7 (commit)
via 444f92d580471b1ed9474a373a4797d497fdcea4 (commit)
via 50839dcce8152c27e4642f3c073e41a466cca0b8 (commit)
via 63a5191b95065ff4d918df0b9143e9371cd6f5d6 (commit)
via 5e04e03abdb6960f58127868ae91d596f18edd30 (commit)
via 619af407edf1c63ee20fed09ee39a338a41183e3 (commit)
via 8bb7f2c2df4a11a1301ee114ef4d2f6e8b8e71fd (commit)
via 1f9be59567c5c6154c7f840e67c1353163e9e097 (commit)
via 65000f777ce98ccdcb51c2c6d685bf6045d7c511 (commit)
via d93776a7695e631375165090e001bcbe38923d7e (commit)
via d66d5fea076e4174323aa0920dc91eb09c680674 (commit)
via cd0841fa5d7671f14baadaeb4e0177868caf21bc (commit)
via dfe23099c41cc78082d7b5b67cd3a2dcea86c8c6 (commit)
via 5ec29b7633767f56675930402ccc7a7b9a1fdd2d (commit)
via 7319f208048b41a6279c36e5ae5d4b0e5c86751a (commit)
via 849cf2de22c31a2ef5c446a30f9ad5bac23d8c42 (commit)
via e031e1adcfa042da3cb6baa9834e42f573ea66fb (commit)
via bb00c59b03275b564938cbbb32956639bf76e5e8 (commit)
via 0a9bcf007a5e71875e15a0c878c440bd51ca5e40 (commit)
via ad4b9819be1af780f17018d82097dedd6965c19d (commit)
via 17680761ae76b62e33c801732a2f86180d995cd0 (commit)
via 199ebdc9c9db2f7744bfc50f0b13eec40eeed5ed (commit)
via d38b9068dde3191aef574fdda44f381f528b8146 (commit)
via 3e87a325eb979710f55db75c87a70392edd9a3c8 (commit)
via 85bf91d5836306a39b471567952ad1e0af91d948 (commit)
via 0a867ce22896ee7c1075caf58c706cec120fed40 (commit)
via 182024bdd5343af59f6e4550eb977168d1745154 (commit)
via d8ac471ae3dc121fd598c47d19bf047ecb1b443d (commit)
via f02087d57966c11bd12197ad778f781ca0a8ddb6 (commit)
via 80d9d4fa922b82c6f47b5482de84399b1eee3c45 (commit)
via 412ea40020ab62f1b83fa78aa41d2b461f97a996 (commit)
via 67f67098c15323d3760976a15f3d2a5d81b0b83f (commit)
via 6716f216a82cb64d12782c8db4dec0a0b62ea4a8 (commit)
via da770d40d1543308ace33c08182fe33e59f8ca7c (commit)
via b42300ae4b958bef9ff55eec929a227c4db4b5e3 (commit)
via 0524cba1473680ecf3fb6d1f7031622047472f83 (commit)
via 6d541b2acec75ea2ab8811619f8ccda8c05eec69 (commit)
via afc36c12bea2d68fdce04d7e0a5f22c980e61edc (commit)
via 8718c072463cc32d97749c2870f4f659129942ac (commit)
via b82303ac5c494936e9fc7dee82be764b242e48ba (commit)
via cccc0f378a5aa81c51dc529e2b01823f00efc326 (commit)
via af568f35fc96c7d844845b7136cb794bc54391b9 (commit)
via d2fc3f36ec89c950850b1aab3b8bef5d4413aaaf (commit)
via 294566c2b9a3dff9b9d3f929f09ad8f7a9d760c2 (commit)
via 33a396ecf7eddfa82054f4056d471ce9ff0616e4 (commit)
via dc1f905a3c787577a3627a1ead607a13460dbb2f (commit)
via c497ad2631b59d6645a7917d3a96e3d8bed577ee (commit)
via f7df24d2d83828d3e7a0652a83c1736a5aa79ced (commit)
via ddb94ad8340db3f41ede1a4509db23df5288cc05 (commit)
via 9ec8253a3af8de3f85b1bce0ebabc88b7fa5a746 (commit)
via e40c9f512e4e0a388890e81b2825a51d8ce10739 (commit)
via cec38f4ec016d0b510fb884def95898080fa66f3 (commit)
via c8caecc35195080d091611bb0809cbc2834ba3f4 (commit)
via 5345a3c2ba2daf9329b33984782ad50f72734837 (commit)
via d02b99f79bec594fc795936b18954543c87edc67 (commit)
from 24ec2f1431f588dac255930a6cac73519b4fa4ea (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
-----------------------------------------------------------------------
Summary of changes:
src/bin/auth/main.cc | 3 +
src/bin/auth/tests/auth_srv_unittest.cc | 20 +-
src/bin/auth/tests/config_unittest.cc | 7 +-
src/bin/bind10/bind10_src.py.in | 13 +-
src/bin/bind10/tests/bind10_test.py.in | 14 +-
src/bin/resolver/main.cc | 3 +
src/bin/resolver/tests/resolver_config_unittest.cc | 29 +-
src/bin/sockcreator/sockcreator.cc | 23 +-
src/bin/sockcreator/sockcreator.h | 11 +-
src/bin/sockcreator/tests/sockcreator_tests.cc | 41 ++-
src/lib/asiodns/Makefile.am | 1 +
src/lib/asiodns/asiodns_messages.mes | 8 +
src/lib/asiodns/dns_service.cc | 15 +
src/lib/asiodns/dns_service.h | 36 ++
src/lib/asiodns/io_fetch.cc | 8 +-
src/lib/{cache => asiodns}/logger.cc | 10 +-
src/lib/{cache/logger.cc => asiodns/logger.h} | 11 +-
src/lib/asiodns/tcp_server.cc | 29 +-
src/lib/asiodns/tcp_server.h | 14 +
src/lib/asiodns/tests/dns_server_unittest.cc | 354 +++++++++----
src/lib/asiodns/udp_server.cc | 33 +-
src/lib/asiodns/udp_server.h | 14 +
src/lib/config/ccsession.h | 36 ++-
src/lib/python/bind10_config.py.in | 41 ++-
src/lib/python/isc/bind10/socket_cache.py | 4 +-
src/lib/python/isc/bind10/special_component.py | 4 +-
src/lib/server_common/Makefile.am | 1 +
src/lib/server_common/portconfig.cc | 46 ++-
src/lib/server_common/portconfig.h | 14 +-
src/lib/server_common/server_common_messages.mes | 27 +-
src/lib/server_common/socket_request.cc | 408 ++++++++++++++
src/lib/server_common/socket_request.h | 203 +++++++
src/lib/server_common/tests/Makefile.am | 1 +
src/lib/server_common/tests/portconfig_unittest.cc | 122 ++++-
.../server_common/tests/socket_requestor_test.cc | 570 ++++++++++++++++++++
src/lib/testutils/Makefile.am | 2 +-
src/lib/testutils/portconfig.h | 10 +-
src/lib/testutils/socket_request.h | 195 +++++++
src/lib/testutils/srv_test.h | 2 +-
src/lib/util/io/fd.cc | 35 +-
40 files changed, 2215 insertions(+), 203 deletions(-)
copy src/lib/{cache => asiodns}/logger.cc (81%)
copy src/lib/{cache/logger.cc => asiodns/logger.h} (76%)
create mode 100644 src/lib/server_common/socket_request.cc
create mode 100644 src/lib/server_common/socket_request.h
create mode 100644 src/lib/server_common/tests/socket_requestor_test.cc
create mode 100644 src/lib/testutils/socket_request.h
-----------------------------------------------------------------------
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index c8f6762..8b8f63a 100644
--- a/src/bin/auth/main.cc
+++ b/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
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 329a2dc..910b60d 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/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(¬ify_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");
}
}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index dadb0ee..18092c1 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/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) {
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 00858d8..1d43513 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/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):
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index f9537fd..63e1446 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/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)
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index 090f211..ef995c2 100644
--- a/src/bin/resolver/main.cc
+++ b/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);
diff --git a/src/bin/resolver/tests/resolver_config_unittest.cc b/src/bin/resolver/tests/resolver_config_unittest.cc
index 63d75c2..1a30941 100644
--- a/src/bin/resolver/tests/resolver_config_unittest.cc
+++ b/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
diff --git a/src/bin/sockcreator/sockcreator.cc b/src/bin/sockcreator/sockcreator.cc
index 6b50813..827969c 100644
--- a/src/bin/sockcreator/sockcreator.cc
+++ b/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) {
diff --git a/src/bin/sockcreator/sockcreator.h b/src/bin/sockcreator/sockcreator.h
index ddf9a09..216e486 100644
--- a/src/bin/sockcreator/sockcreator.h
+++ b/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
}
diff --git a/src/bin/sockcreator/tests/sockcreator_tests.cc b/src/bin/sockcreator/tests/sockcreator_tests.cc
index 73cbf48..360c750 100644
--- a/src/bin/sockcreator/tests/sockcreator_tests.cc
+++ b/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);
+}
+
}
diff --git a/src/lib/asiodns/Makefile.am b/src/lib/asiodns/Makefile.am
index 2d246ef..b5d030d 100644
--- a/src/lib/asiodns/Makefile.am
+++ b/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
diff --git a/src/lib/asiodns/asiodns_messages.mes b/src/lib/asiodns/asiodns_messages.mes
index feb75d4..8fbafdd 100644
--- a/src/lib/asiodns/asiodns_messages.mes
+++ b/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.
diff --git a/src/lib/asiodns/dns_service.cc b/src/lib/asiodns/dns_service.cc
index ed7d717..967ce15 100644
--- a/src/lib/asiodns/dns_service.cc
+++ b/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_) {
diff --git a/src/lib/asiodns/dns_service.h b/src/lib/asiodns/dns_service.h
index 6b6a6c0..66f8d33 100644
--- a/src/lib/asiodns/dns_service.h
+++ b/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();
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
index 25ec955..b22d067 100644
--- a/src/lib/asiodns/io_fetch.cc
+++ b/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;
diff --git a/src/lib/asiodns/logger.cc b/src/lib/asiodns/logger.cc
new file mode 100644
index 0000000..5801a0b
--- /dev/null
+++ b/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");
+
+}
+}
diff --git a/src/lib/asiodns/logger.h b/src/lib/asiodns/logger.h
new file mode 100644
index 0000000..306d463
--- /dev/null
+++ b/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;
+
+}
+}
diff --git a/src/lib/asiodns/tcp_server.cc b/src/lib/asiodns/tcp_server.cc
index 2606a21..d116bdb 100644
--- a/src/lib/asiodns/tcp_server.cc
+++ b/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
diff --git a/src/lib/asiodns/tcp_server.h b/src/lib/asiodns/tcp_server.h
index 22a2f69..d079e97 100644
--- a/src/lib/asiodns/tcp_server.h
+++ b/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();
diff --git a/src/lib/asiodns/tests/dns_server_unittest.cc b/src/lib/asiodns/tests/dns_server_unittest.cc
index c79ee7f..c9bbe7c 100644
--- a/src/lib/asiodns/tests/dns_server_unittest.cc
+++ b/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);
}
}
diff --git a/src/lib/asiodns/udp_server.cc b/src/lib/asiodns/udp_server.cc
index 72db2bf..820db4c 100644
--- a/src/lib/asiodns/udp_server.cc
+++ b/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
diff --git a/src/lib/asiodns/udp_server.h b/src/lib/asiodns/udp_server.h
index 90fcc6f..c82b78c 100644
--- a/src/lib/asiodns/udp_server.h
+++ b/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);
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index 50bb65c..80ef7c5 100644
--- a/src/lib/config/ccsession.h
+++ b/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();
diff --git a/src/lib/python/bind10_config.py.in b/src/lib/python/bind10_config.py.in
index e54b1a8..b8975cf 100644
--- a/src/lib/python/bind10_config.py.in
+++ b/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(':')
diff --git a/src/lib/python/isc/bind10/socket_cache.py b/src/lib/python/isc/bind10/socket_cache.py
index 26e87d2..d6c1175 100644
--- a/src/lib/python/isc/bind10/socket_cache.py
+++ b/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)
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index c9c7683..29cdf62 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/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)
diff --git a/src/lib/server_common/Makefile.am b/src/lib/server_common/Makefile.am
index c2779b4..714e25f 100644
--- a/src/lib/server_common/Makefile.am
+++ b/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
diff --git a/src/lib/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
index fba8e1a..556f0da 100644
--- a/src/lib/server_common/portconfig.cc
+++ b/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
diff --git a/src/lib/server_common/portconfig.h b/src/lib/server_common/portconfig.h
index e4e7bf6..0c0fa6a 100644
--- a/src/lib/server_common/portconfig.h
+++ b/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,
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 5fbbb0b..0efe388 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/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.
diff --git a/src/lib/server_common/socket_request.cc b/src/lib/server_common/socket_request.cc
new file mode 100644
index 0000000..e7695c7
--- /dev/null
+++ b/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");
+ }
+}
+
+}
+}
diff --git a/src/lib/server_common/socket_request.h b/src/lib/server_common/socket_request.h
new file mode 100644
index 0000000..2983275
--- /dev/null
+++ b/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
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
index d7e113a..b059d47 100644
--- a/src/lib/server_common/tests/Makefile.am
+++ b/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)
diff --git a/src/lib/server_common/tests/portconfig_unittest.cc b/src/lib/server_common/tests/portconfig_unittest.cc
index 65963eb..ee17e56 100644
--- a/src/lib/server_common/tests/portconfig_unittest.cc
+++ b/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");
}
}
diff --git a/src/lib/server_common/tests/socket_requestor_test.cc b/src/lib/server_common/tests/socket_requestor_test.cc
new file mode 100644
index 0000000..4cc4f5e
--- /dev/null
+++ b/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);
+}
+
+}
diff --git a/src/lib/testutils/Makefile.am b/src/lib/testutils/Makefile.am
index a511d24..7a4c8d7 100644
--- a/src/lib/testutils/Makefile.am
+++ b/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
diff --git a/src/lib/testutils/portconfig.h b/src/lib/testutils/portconfig.h
index 8e61ffc..b538478 100644
--- a/src/lib/testutils/portconfig.h
+++ b/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"
" }"
"]"
diff --git a/src/lib/testutils/socket_request.h b/src/lib/testutils/socket_request.h
new file mode 100644
index 0000000..c06c969
--- /dev/null
+++ b/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_;
+};
+
+}
+}
diff --git a/src/lib/testutils/srv_test.h b/src/lib/testutils/srv_test.h
index c92e876..630232c 100644
--- a/src/lib/testutils/srv_test.h
+++ b/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();
diff --git a/src/lib/util/io/fd.cc b/src/lib/util/io/fd.cc
index f9b17a7..49aac39 100644
--- a/src/lib/util/io/fd.cc
+++ b/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;
More information about the bind10-changes
mailing list