Parcourir la source

[trac366] Update from master

Since the branch is years old.

It compiles, but the tests don't run. There's still some work to do.

Conflicts:
	configure.ac
	doc/Doxyfile
	src/bin/Makefile.am
	src/lib/Makefile.am
	src/lib/util/io/fd_share.h
	src/lib/util/io/fdshare_python.cc
	src/lib/util/io/tests/run_unittests.cc
Michal 'vorner' Vaner il y a 14 ans
Parent
commit
b4111d2495

+ 6 - 0
configure.ac

@@ -624,6 +624,8 @@ AC_CONFIG_FILES([Makefile
                  src/bin/auth/benchmarks/Makefile
                  src/bin/auth/benchmarks/Makefile
                  src/bin/resolver/Makefile
                  src/bin/resolver/Makefile
                  src/bin/resolver/tests/Makefile
                  src/bin/resolver/tests/Makefile
+                 src/bin/sockcreator/Makefile
+                 src/bin/sockcreator/tests/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrout/Makefile
                  src/bin/xfrout/Makefile
@@ -692,6 +694,10 @@ AC_CONFIG_FILES([Makefile
                  src/lib/server_common/tests/Makefile
                  src/lib/server_common/tests/Makefile
                  tests/Makefile
                  tests/Makefile
                  tests/system/Makefile
                  tests/system/Makefile
+                 src/lib/util/Makefile
+                 src/lib/util/io/Makefile
+                 src/lib/util/io/tests/Makefile
+                 src/lib/util/unittests/Makefile
                ])
                ])
 AC_OUTPUT([doc/version.ent
 AC_OUTPUT([doc/version.ent
            src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/cfgmgr/b10-cfgmgr.py

+ 5 - 1
doc/Doxyfile

@@ -568,7 +568,11 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 # with spaces.
 
 
-INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/
+INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns \
+    ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth \
+    ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ \
+    ../src/lib/nsas ../src/lib/testutils ../src/lib/cache \
+    ../src/lib/server_common/ ../src/bin/sockcreator/ ../src/lib/util/
 
 
 # This tag can be used to specify the character encoding of the source files
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

+ 1 - 1
src/bin/Makefile.am

@@ -1,4 +1,4 @@
 SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth xfrin xfrout \
 SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth xfrin xfrout \
-	usermgr zonemgr stats tests resolver
+	usermgr zonemgr stats tests resolver sockcreator
 
 
 check-recursive: all-recursive
 check-recursive: all-recursive

+ 18 - 0
src/bin/sockcreator/Makefile.am

@@ -0,0 +1,18 @@
+SUBDIRS = tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES = *.gcno *.gcda
+
+pkglibexec_PROGRAMS = b10-sockcreator
+
+b10_sockcreator_SOURCES = sockcreator.cc sockcreator.h main.cc
+b10_sockcreator_LDADD = $(top_builddir)/src/lib/util/io/libutil_io.la

+ 49 - 0
src/bin/sockcreator/README

@@ -0,0 +1,49 @@
+The socket creator
+==================
+
+The only thing we need higher rights than standard user is binding sockets to
+ports lower than 1024. So we will have a separate process that keeps the
+rights, while the rests drop them for security reasons.
+
+This process is the socket creator. Its goal is to be as simple as possible
+and to contain as little code as possible to minimise the amount of code
+running with higher privileges (to minimize the number of bugs and make
+checking/auditing it easier). It uses low-level OS API instead of some
+fancy library for that reason. It has only fixed-length reads so there's no
+place for buffer overruns.
+
+Protocol
+--------
+
+It talks with whoever started it by its stdin/stdout. It reads simple
+binary protocol from stdin and does what the commands ask. Command is a single
+byte (usually from the printable range, so it is easier to debug and guess
+what it does), followed by parameters.
+
+Note that as send_fd and recv_fd works only with unix domain socket, it's stdio
+must be a socket, not pipe.
+
+* 'T': It has no parameters. It asks the socket creator to terminate.
+
+* 'S' 'U|T' '4|6' port address: Asks it to create a port. First parameter
+  tels the socket type (either UDP or TCP). The second one is address family
+  (either IPv4 or IPv6). Then there's 2 bytes of the port number, in the
+  network byte order. The last one is either 4 or 16 bytes of address, as
+  they would be passed to bind (note that both parameters are already prepared,
+  like hton called on them).
+
+  The answer to this is either 'S' and then the socket is passed using sendmsg
+  if it is successful. If it fails, 'E' is returned, followed by either
+  'S' or 'B' (either socket() or bind() call failed). Then there is one int
+  (architecture-dependent length and endianess), which is the errno value
+  after the failure.
+
+The creator may also send these messages at any time (but not in the middle
+of another message):
+
+* 'F': A fatal error has been detected. It is followed by one byte of error
+  condition code and then the creator terminates with non-zero status.
+
+  The conditions are:
+  * 'I': Invalid input (eg. someone sent a wrong letter and it does not
+    understand it).

+ 10 - 16
src/lib/xfr/python_xfr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010  CZ NIC
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -12,21 +12,15 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#include <boost/python.hpp>
-#include <boost/python/class.hpp>
-#include <boost/python/module.hpp>
-#include <boost/python/def.hpp>
-#include <boost/python/exception_translator.hpp>
-#include <boost/python/return_internal_reference.hpp>
-#include <boost/python/copy_const_reference.hpp>
-#include <boost/shared_ptr.hpp>
+#include "sockcreator.h"
 
 
-#include <xfr/fd_share.h>
+using namespace isc::socket_creator;
 
 
-using namespace isc::xfr;
-using namespace boost::python;
-
-BOOST_PYTHON_MODULE(bind10_xfr) {
-    def("recv_fd", &recv_fd);
-    def("send_fd", &send_fd);
+int
+main() {
+    /*
+     * TODO Maybe use some OS-specific caps interface and drop everything
+     * but ability to bind ports? It would be nice.
+     */
+    return run(0, 1); // Read commands from stdin, output to stdout
 }
 }

+ 153 - 0
src/bin/sockcreator/sockcreator.cc

@@ -0,0 +1,153 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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 "sockcreator.h"
+
+#include <util/io/fd.h>
+
+#include <unistd.h>
+#include <cerrno>
+#include <cstring>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+using namespace isc::util::io;
+
+namespace isc {
+namespace socket_creator {
+
+int
+get_sock(const int type, struct sockaddr *bind_addr, const socklen_t addr_len)
+{
+    int sock(socket(bind_addr->sa_family, type, 0));
+    if (sock == -1) {
+        return -1;
+    }
+    if (bind(sock, bind_addr, addr_len) == -1) {
+        return -2;
+    }
+    return sock;
+}
+
+int
+send_fd(const int, const int) {
+    return 0;
+}
+
+int
+run(const int input_fd, const int output_fd, const get_sock_t get_sock,
+    const send_fd_t send_fd)
+{
+// These are macros so they can exit the function
+#define READ(WHERE, HOW_MANY) do { \
+        size_t how_many = (HOW_MANY); \
+        if (read_data(input_fd, (WHERE), how_many) < how_many) { \
+            return 1; \
+        } \
+    } while (0)
+#define WRITE(WHAT, HOW_MANY) do { \
+        if (!write_data(output_fd, (WHAT), (HOW_MANY))) { \
+            return 2; \
+        } \
+    } while (0)
+#define DEFAULT \
+    default: /* Unrecognized part of protocol */ \
+        WRITE("FI", 2); \
+        return 3;
+    for (;;) {
+        // Read the command
+        char command;
+        READ(&command, 1);
+        switch (command) {
+            case 'T': // The "terminate" command
+                return 0;
+            case 'S': { // Create a socket
+                // Read what type of socket they want
+                char type[2];
+                READ(type, 2);
+                // Read the address they ask for
+                struct sockaddr *addr(NULL);
+                size_t addr_len(0);
+                struct sockaddr_in addr_in;
+                struct sockaddr_in6 addr_in6;
+                switch (type[1]) { // The address family
+                    /*
+                     * Here are some casts. They are required by C++ and
+                     * the low-level interface (they are implicit in C).
+                     */
+                    case '4':
+                        addr = static_cast<struct sockaddr *>(
+                            static_cast<void *>(&addr_in));
+                        addr_len = sizeof addr_in;
+                        memset(&addr_in, 0, sizeof addr_in);
+                        addr_in.sin_family = AF_INET;
+                        READ(static_cast<char *>(static_cast<void *>(
+                            &addr_in.sin_port)), 2);
+                        READ(static_cast<char *>(static_cast<void *>(
+                            &addr_in.sin_addr.s_addr)), 4);
+                        break;
+                    case '6':
+                        addr = static_cast<struct sockaddr *>(
+                            static_cast<void *>(&addr_in6));
+                        addr_len = sizeof addr_in6;
+                        memset(&addr_in6, 0, sizeof addr_in6);
+                        addr_in6.sin6_family = AF_INET6;
+                        READ(static_cast<char *>(static_cast<void *>(
+                            &addr_in6.sin6_port)), 2);
+                        READ(static_cast<char *>(static_cast<void *>(
+                            &addr_in6.sin6_addr.s6_addr)), 16);
+                        break;
+                    DEFAULT
+                }
+                int sock_type;
+                switch (type[0]) { // Translate the type
+                    case 'T':
+                        sock_type = SOCK_STREAM;
+                        break;
+                    case 'U':
+                        sock_type = SOCK_DGRAM;
+                        break;
+                    DEFAULT
+                }
+                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);
+                } else {
+                    WRITE("E", 1);
+                    switch (result) {
+                        case -1:
+                            WRITE("S", 1);
+                            break;
+                        case -2:
+                            WRITE("B", 1);
+                            break;
+                        default:
+                            return 4;
+                    }
+                    int error(errno);
+                    WRITE(static_cast<char *>(static_cast<void *>(&error)),
+                        sizeof error);
+                }
+                break;
+            }
+            DEFAULT
+        }
+    }
+}
+
+} // End of the namespaces
+}

+ 100 - 0
src/bin/sockcreator/sockcreator.h

@@ -0,0 +1,100 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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.
+
+/**
+ * \file sockcreator.h
+ * \short Socket creator functionality.
+ *
+ * This module holds the functionality of the socket creator. It is
+ * a separate module from main to ease up the tests.
+ */
+
+#ifndef __SOCKCREATOR_H
+#define __SOCKCREATOR_H 1
+
+#include <util/io/fd_share.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+namespace isc {
+namespace socket_creator {
+
+/**
+ * \short Create a socket and bind it.
+ *
+ * This is just a bundle of socket() and bind() calls. The sa_family of
+ * bind_addr is used to determine the domain of the socket.
+ *
+ * \return The file descriptor of the newly created socket, if everything
+ *     goes well. A negative number is returned if an error occurs -
+ *     -1 if the socket() call fails or -2 if bind() fails. In case of error,
+ *     errno is set (or better, left intact from socket() or bind()).
+ * \param type The type of socket to create (SOCK_STREAM, SOCK_DGRAM, etc).
+ * \param bind_addr The address to bind.
+ * \param addr_len The actual length of bind_addr.
+ */
+int
+get_sock(const int type, struct sockaddr *bind_addr, const socklen_t addr_len);
+
+/**
+ * Type of the get_sock function, to pass it as parameter.
+ */
+typedef
+int
+(*get_sock_t)(const int, struct sockaddr *, const socklen_t);
+
+/**
+ * Type of the send_fd() function, so it can be passed as a parameter.
+ */
+typedef
+int
+(*send_fd_t)(const int, const int);
+
+/**
+ * \short Infinite loop parsing commands and returning the sockets.
+ *
+ * This reads commands and socket descriptions from the input_fd
+ * file descriptor, creates sockets and writes the results (socket or
+ * error) to output_fd.
+ *
+ * Current errors are:
+ * - 1: Read error
+ * - 2: Write error
+ * - 3: Protocol error (unknown command, etc)
+ * - 4: Some internal inconsistency detected
+ *
+ * It terminates either if a command asks it to or when unrecoverable
+ * error happens.
+ *
+ * \return Like a return value of a main - 0 means everything OK, anything
+ *     else is error.
+ * \param input_fd Here is where it reads the commads.
+ * \param output_fd Here is where it writes the results.
+ * \param get_sock_fun The function that is used to create the sockets.
+ *     This should be left on the default value, the parameter is here
+ *     for testing purposes.
+ * \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.
+ */
+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);
+
+} // End of the namespaces
+}
+
+#endif // __SOCKCREATOR_H

+ 25 - 0
src/bin/sockcreator/tests/Makefile.am

@@ -0,0 +1,25 @@
+CLEANFILES = *.gcno *.gcda
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = ../sockcreator.cc ../sockcreator.h
+run_unittests_SOURCES += sockcreator_tests.cc
+run_unittests_SOURCES += run_unittests.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
+run_unittests_LDADD += \
+	$(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)

+ 22 - 0
src/bin/sockcreator/tests/run_unittests.cc

@@ -0,0 +1,22 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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 <gtest/gtest.h>
+
+int
+main(int argc, char *argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}

+ 269 - 0
src/bin/sockcreator/tests/sockcreator_tests.cc

@@ -0,0 +1,269 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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 "../sockcreator.h"
+
+#include <util/unittests/fork.h>
+#include <util/io/fd.h>
+
+#include <gtest/gtest.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <cstring>
+#include <cerrno>
+
+using namespace isc::socket_creator;
+using namespace isc::util::unittests;
+using namespace isc::util::io;
+
+namespace {
+
+/*
+ * Generic version of the creation of socket test. It just tries to
+ * create the socket and checks the result is not negative (eg.
+ * it is valid descriptor) and that it can listen.
+ *
+ * This is a macro so ASSERT_* does abort the TEST, not just the
+ * function inside.
+ */
+#define TEST_ANY_CREATE(SOCK_TYPE, ADDR_TYPE, ADDR_FAMILY, FAMILY_FIELD, \
+    ADDR_SET, CHECK_SOCK) \
+    do { \
+        /*
+         * This should create an address that binds on all interfaces
+         * and lets the OS choose a free port.
+         */ \
+        struct ADDR_TYPE addr; \
+        memset(&addr, 0, sizeof addr); \
+        ADDR_SET(addr); \
+        addr.FAMILY_FIELD = ADDR_FAMILY; \
+        struct sockaddr *addr_ptr = static_cast<struct sockaddr *>( \
+            static_cast<void *>(&addr)); \
+        \
+        int socket = get_sock(SOCK_TYPE, addr_ptr, sizeof addr); \
+        /* Provide even nice error message. */ \
+        ASSERT_GE(socket, 0) << "Couldn't create a socket of type " \
+            #SOCK_TYPE " and family " #ADDR_FAMILY ", failed with " \
+            << socket << " and errno " << errno; \
+        CHECK_SOCK(ADDR_TYPE, socket); \
+        EXPECT_EQ(0, close(socket)); \
+    } while (0)
+
+// Just helper macros
+#define INADDR_SET(WHAT) do { WHAT.sin_addr.s_addr = INADDR_ANY; } while (0)
+#define IN6ADDR_SET(WHAT) do { WHAT.sin6_addr = in6addr_any; } while (0)
+// If the get_sock returned something useful, listen must work
+#define TCP_CHECK(UNUSED, SOCKET) do { \
+        EXPECT_EQ(0, listen(SOCKET, 1)); \
+    } while (0)
+// More complicated with UDP, so we send a packet to ourselfs and se if it
+// arrives
+#define UDP_CHECK(ADDR_TYPE, SOCKET) do { \
+        struct ADDR_TYPE addr; \
+        memset(&addr, 0, sizeof addr); \
+        struct sockaddr *addr_ptr = static_cast<struct sockaddr *>( \
+            static_cast<void *>(&addr)); \
+        \
+        socklen_t len = sizeof addr; \
+        ASSERT_EQ(0, getsockname(SOCKET, addr_ptr, &len)); \
+        ASSERT_EQ(5, sendto(SOCKET, "test", 5, 0, addr_ptr, sizeof addr)); \
+        char buffer[5]; \
+        ASSERT_EQ(5, recv(SOCKET, buffer, 5, 0)); \
+        EXPECT_STREQ("test", buffer); \
+    } while (0)
+
+/*
+ * Several tests to ensure we can create the sockets.
+ */
+TEST(get_sock, udp4_create) {
+    TEST_ANY_CREATE(SOCK_DGRAM, sockaddr_in, AF_INET, sin_family, INADDR_SET,
+        UDP_CHECK);
+}
+
+TEST(get_sock, tcp4_create) {
+    TEST_ANY_CREATE(SOCK_STREAM, sockaddr_in, AF_INET, sin_family, INADDR_SET,
+        TCP_CHECK);
+}
+
+TEST(get_sock, udp6_create) {
+    TEST_ANY_CREATE(SOCK_DGRAM, sockaddr_in6, AF_INET6, sin6_family,
+        IN6ADDR_SET, UDP_CHECK);
+}
+
+TEST(get_sock, tcp6_create) {
+    TEST_ANY_CREATE(SOCK_STREAM, sockaddr_in6, AF_INET6, sin6_family,
+        IN6ADDR_SET, TCP_CHECK);
+}
+
+/*
+ * Try to ask the get_sock function some nonsense and test if it
+ * is able to report error.
+ */
+TEST(get_sock, fail_with_nonsense) {
+    struct sockaddr addr;
+    memset(&addr, 0, sizeof addr);
+    ASSERT_LT(get_sock(0, &addr, sizeof addr), 0);
+}
+
+/*
+ * Helper functions to pass to run during testing.
+ */
+int
+get_sock_dummy(const int type, struct sockaddr *addr, const socklen_t)
+{
+    int result(0);
+    int port(0);
+    /*
+     * We encode the type and address family into the int and return it.
+     * Lets ignore the port and address for now
+     * First bit is 1 if it is known type. Second tells if TCP or UDP.
+     * The familly is similar - third bit is known address family,
+     * the fourth is the family.
+     */
+    switch (type) {
+        case SOCK_STREAM:
+            result += 1;
+            break;
+        case SOCK_DGRAM:
+            result += 3;
+            break;
+    }
+    switch (addr->sa_family) {
+        case AF_INET:
+            result += 4;
+            port = static_cast<struct sockaddr_in *>(
+                static_cast<void *>(addr))->sin_port;
+            break;
+        case AF_INET6:
+            result += 12;
+            port = static_cast<struct sockaddr_in6 *>(
+                static_cast<void *>(addr))->sin6_port;
+            break;
+    }
+    /*
+     * The port should be 0xffff. If it's not, we change the result.
+     * The port of 0xbbbb means bind should fail and 0xcccc means
+     * socket should fail.
+     */
+    if (port != 0xffff) {
+        errno = 0;
+        if (port == 0xbbbb) {
+            return -2;
+        } else if (port == 0xcccc) {
+            return -1;
+        } else {
+            result += 16;
+        }
+    }
+    return result;
+}
+
+int
+send_fd_dummy(const int destination, const int what)
+{
+    /*
+     * Make sure it is 1 byte so we know the length. We do not use more during
+     * the test anyway.
+     */
+    char fd_data(what);
+    if (!write_data(destination, &fd_data, 1)) {
+        return -1;
+    } else {
+        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
+ * data to it and check it returns correct data back, to see if the run()
+ * parses the commands correctly.
+ */
+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)
+{
+    // Prepare the input feeder and output checker processes
+    int input_fd(0), output_fd(0);
+    pid_t input(provide_input(&input_fd, input_data, input_size)),
+        output(check_output(&output_fd, output_data, output_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));
+    // Close the pipes
+    close(input_fd);
+    close(output_fd);
+    // Did it run well?
+    if (should_succeed) {
+        EXPECT_EQ(0, result);
+    } else {
+        EXPECT_NE(0, result);
+    }
+    // Check the subprocesses say everything is OK too
+    EXPECT_TRUE(process_ok(input));
+    EXPECT_TRUE(process_ok(output));
+}
+
+/*
+ * Check it terminates successfully when asked to.
+ */
+TEST(run, terminate) {
+    run_test("T", 1, NULL, 0);
+}
+
+/*
+ * Check it rejects incorrect input.
+ */
+TEST(run, bad_input) {
+    run_test("XXX", 3, "FI", 2, false);
+}
+
+/*
+ * Check it correctly parses queries to create sockets.
+ */
+TEST(run, sockets) {
+    run_test(
+        "SU4\xff\xff\0\0\0\0" // This has 9 bytes
+        "ST4\xff\xff\0\0\0\0" // This has 9 bytes
+        "ST6\xff\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // This has 21 bytes
+        "SU6\xff\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // This has 21 bytes
+        "T", 61,
+        "S\x07S\x05S\x0dS\x0f", 8);
+}
+
+/*
+ * Check if failures of get_socket are handled correctly.
+ */
+TEST(run, bad_sockets) {
+    // We need to construct the answer, but it depends on int length.
+    size_t int_len(sizeof(int));
+    size_t result_len(4 + 2 * int_len);
+    char result[result_len];
+    // Both errno parts should be 0
+    memset(result, 0, result_len);
+    // Fill the 2 control parts
+    strcpy(result, "EB");
+    strcpy(result + 2 + int_len, "ES");
+    // Run the test
+    run_test(
+        "SU4\xbb\xbb\0\0\0\0"
+        "SU4\xcc\xcc\0\0\0\0"
+        "T", 19,
+        result, result_len);
+}
+
+}

+ 2 - 2
src/bin/xfrout/tests/Makefile.am

@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
 # required by loadable python modules.
 # required by loadable python modules.
 LIBRARY_PATH_PLACEHOLDER =
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
 if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 endif
 
 
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
@@ -18,7 +18,7 @@ if ENABLE_PYTHON_COVERAGE
 endif
 endif
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
-	env PYTHONPATH=$(abs_top_builddir)/src/bin/xfrout:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
+	env PYTHONPATH=$(abs_top_builddir)/src/bin/xfrout:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 3 - 3
src/bin/xfrout/xfrout.py.in

@@ -37,12 +37,12 @@ from optparse import OptionParser, OptionValueError
 from isc.util import socketserver_mixin
 from isc.util import socketserver_mixin
 
 
 try:
 try:
-    from libxfr_python import *
+    from libutil_io_python import *
     from pydnspp import *
     from pydnspp import *
 except ImportError as e:
 except ImportError as e:
     # C++ loadable module may not be installed; even so the xfrout process
     # C++ loadable module may not be installed; even so the xfrout process
     # must keep running, so we warn about it and move forward.
     # must keep running, so we warn about it and move forward.
-    sys.stderr.write('[b10-xfrout] failed to import DNS or XFR module: %s\n' % str(e))
+    sys.stderr.write('[b10-xfrout] failed to import DNS or isc.util.io module: %s\n' % str(e))
 
 
 isc.util.process.rename()
 isc.util.process.rename()
 
 
@@ -376,7 +376,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
             # This may happen when one xfrout process try to connect to
             # This may happen when one xfrout process try to connect to
             # xfrout unix socket server, to check whether there is another
             # xfrout unix socket server, to check whether there is another
             # xfrout running.
             # xfrout running.
-            if sock_fd == XFR_FD_RECEIVE_FAIL:
+            if sock_fd == FD_COMM_ERROR:
                 self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
                 self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
             return
             return
 
 

+ 1 - 1
src/lib/Makefile.am

@@ -1,2 +1,2 @@
-SUBDIRS = exceptions dns cc config python xfr bench log asiolink \
+SUBDIRS = exceptions dns cc config util python xfr bench log asiolink \
           nsas cache resolve testutils datasrc server_common
           nsas cache resolve testutils datasrc server_common

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

@@ -0,0 +1,3 @@
+SUBDIRS = io unittests io/tests
+# The io/tests is hack, because otherwise we can not order these directories
+# properly. Unittests use io and io/tests use unittest.

+ 16 - 0
src/lib/util/io/Makefile.am

@@ -0,0 +1,16 @@
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+lib_LTLIBRARIES = libutil_io.la
+libutil_io_la_SOURCES = fd.h fd.cc fd_share.h fd_share.cc
+libutil_io_la_CXXFLAGS = $(AM_CXXFLAGS) -fno-strict-aliasing
+
+CLEANFILES = *.gcno *.gcda
+
+pyexec_LTLIBRARIES = libutil_io_python.la
+# Python prefers .so, while some OSes (specifically MacOS) use a different
+# suffix for dynamic objects.  -module is necessary to work this around.
+libutil_io_python_la_LDFLAGS = -module
+libutil_io_python_la_SOURCES = fdshare_python.cc
+libutil_io_python_la_LIBADD = libutil_io.la
+libutil_io_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
+libutil_io_python_la_CXXFLAGS = $(AM_CXXFLAGS)

+ 70 - 0
src/lib/util/io/fd.cc

@@ -0,0 +1,70 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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 "fd.h"
+
+#include <unistd.h>
+#include <cerrno>
+
+namespace isc {
+namespace util {
+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 rest(length);
+    // Just keep writing until all is written
+    while (rest) {
+        ssize_t written(write(fd, buffer, rest));
+        if (rest == -1) {
+            if (errno == EINTR) { // Just keep going
+                continue;
+            } else {
+                return false;
+            }
+        } else { // Wrote something
+            rest -= written;
+            buffer += written;
+        }
+    }
+    return true;
+}
+
+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 rest(length), already(0);
+    while (rest) { // Stil something to read
+        ssize_t amount(read(fd, buffer, rest));
+        if (rest == -1) {
+            if (errno == EINTR) { // Continue on interrupted call
+                continue;
+            } else {
+                return -1;
+            }
+        } else if (amount) {
+            already += amount;
+            rest -= amount;
+            buffer += amount;
+        } else { // EOF
+            return already;
+        }
+    }
+    return already;
+}
+
+}
+}
+}

+ 61 - 0
src/lib/util/io/fd.h

@@ -0,0 +1,61 @@
+// Copyright (C) 2010  CZ NIC
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UTIL_IO_FD_H
+#define __UTIL_IO_FD_H 1
+
+#include <unistd.h>
+
+/**
+ * @file fd.h
+ * @short Wrappers around common unix fd manipulation functions.
+ */
+
+namespace isc {
+namespace util {
+namespace io {
+
+/*
+ * \short write() that writes everything.
+ * Wrapper around write(). The difference is, it never writes less data
+ * and looks successfull (eg. it blocks until all data are written).
+ * Retries on signals.
+ *
+ * \return True if sucessfull, false otherwise. The errno variable is left
+ *     intact.
+ * \param fd Where to write.
+ * \param data The buffer to write.
+ * \param length How much data is there to write.
+ */
+bool
+write_data(const int fd, const void *data, const size_t length);
+
+/*
+ * \short read() that reads everything.
+ * Wrapper around read(). It does not do short reads, if it returns less,
+ * it means there was EOF. It retries on signals.
+ *
+ * \return Number of bytes read or -1 on error.
+ * \param fd Where to read data from.
+ * \param data Where to put the data.
+ * \param length How many of them.
+ */
+ssize_t
+read_data(const int fd, void *buffer, const size_t length);
+
+}
+}
+}
+
+#endif // __UTIL_IO_FD_H

+ 10 - 8
src/lib/xfr/fd_share.cc

@@ -19,10 +19,11 @@
 #include <sys/socket.h>
 #include <sys/socket.h>
 #include <sys/uio.h>
 #include <sys/uio.h>
 #include <stdlib.h>             // for malloc and free
 #include <stdlib.h>             // for malloc and free
-#include <xfr/fd_share.h>
+#include "fd_share.h"
 
 
 namespace isc {
 namespace isc {
-namespace xfr {
+namespace util {
+namespace io {
 
 
 namespace {
 namespace {
 // Not all OSes support advanced CMSG macros: CMSG_LEN and CMSG_SPACE.
 // Not all OSes support advanced CMSG macros: CMSG_LEN and CMSG_SPACE.
@@ -86,15 +87,15 @@ recv_fd(const int sock) {
     msghdr.msg_controllen = cmsg_space(sizeof(int));
     msghdr.msg_controllen = cmsg_space(sizeof(int));
     msghdr.msg_control = malloc(msghdr.msg_controllen);
     msghdr.msg_control = malloc(msghdr.msg_controllen);
     if (msghdr.msg_control == NULL) {
     if (msghdr.msg_control == NULL) {
-        return (-1);
+        return (FD_OTHER_ERROR);
     }
     }
 
 
     if (recvmsg(sock, &msghdr, 0) < 0) {
     if (recvmsg(sock, &msghdr, 0) < 0) {
         free(msghdr.msg_control);
         free(msghdr.msg_control);
-        return (XFR_FD_RECEIVE_FAIL);
+        return (FD_COMM_ERROR);
     }
     }
     const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
     const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
-    int fd = -1;
+    int fd = FD_OTHER_ERROR;
     if (cmsg != NULL && cmsg->cmsg_len == cmsg_len(sizeof(int)) &&
     if (cmsg != NULL && cmsg->cmsg_len == cmsg_len(sizeof(int)) &&
         cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
         cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
         fd = *(const int*)CMSG_DATA(cmsg);
         fd = *(const int*)CMSG_DATA(cmsg);
@@ -119,7 +120,7 @@ send_fd(const int sock, const int fd) {
     msghdr.msg_controllen = cmsg_space(sizeof(int));
     msghdr.msg_controllen = cmsg_space(sizeof(int));
     msghdr.msg_control = malloc(msghdr.msg_controllen);
     msghdr.msg_control = malloc(msghdr.msg_controllen);
     if (msghdr.msg_control == NULL) {
     if (msghdr.msg_control == NULL) {
-        return (-1);
+        return (FD_OTHER_ERROR);
     }
     }
 
 
     struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
     struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
@@ -130,8 +131,9 @@ send_fd(const int sock, const int fd) {
 
 
     const int ret = sendmsg(sock, &msghdr, 0);
     const int ret = sendmsg(sock, &msghdr, 0);
     free(msghdr.msg_control);
     free(msghdr.msg_control);
-    return (ret >= 0 ? 0 : -1);
+    return (ret >= 0 ? 0 : FD_COMM_ERROR);
 }
 }
 
 
-} // End for namespace xfr
+} // End for namespace io
+} // End for namespace util
 } // End for namespace isc
 } // End for namespace isc

+ 65 - 0
src/lib/util/io/fd_share.h

@@ -0,0 +1,65 @@
+// 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 FD_SHARE_H_
+#define FD_SHARE_H_
+
+/**
+ * \file fd_share.h
+ * \short Support to transfer file descriptors between processes.
+ * \todo This interface is very C-ish. Should we have some kind of exceptions?
+ */
+
+namespace isc {
+namespace util {
+namespace io {
+
+const int FD_COMM_ERROR = -2;
+const int FD_OTHER_ERROR = -1;
+
+/**
+ * \short Receives a file descriptor.
+ * This receives a file descriptor sent over an unix domain socket. This
+ * is the counterpart of send_fd().
+ *
+ * \return FD_COMM_ERROR when there's error receiving the socket, FD_OTHER_ERROR
+ *     when there's a different error.
+ * \param sock The unix domain socket to read from. Tested and it does
+ *     not work with a pipe.
+ */
+int recv_fd(const int sock);
+
+/**
+ * \short Sends a file descriptor.
+ * This sends a file descriptor over an unix domain socket. This is the
+ * counterpart of recv_fd().
+ *
+ * \return FD_COMM_ERROR when there's error sending the socket, FD_OTHER_ERROR
+ *     for all other possible errors.
+ * \param sock The unix domain socket to send to. Tested and it does not
+ *     work with a pipe.
+ * \param fd The file descriptor to send. It should work with any valid
+ *     file descriptor.
+ */
+int send_fd(const int sock, const int fd);
+
+} // End for namespace io
+} // End for namespace util
+} // End for namespace isc
+
+#endif
+
+// Local Variables:
+// mode: c++
+// End:

+ 22 - 9
src/lib/xfr/fdshare_python.cc

@@ -18,7 +18,7 @@
 
 
 #include <config.h>
 #include <config.h>
 
 
-#include <xfr/fd_share.h>
+#include "fd_share.h"
 
 
 
 
 static PyObject*
 static PyObject*
@@ -27,7 +27,7 @@ fdshare_recv_fd(PyObject*, PyObject* args) {
     if (!PyArg_ParseTuple(args, "i", &sock)) {
     if (!PyArg_ParseTuple(args, "i", &sock)) {
         return (NULL);
         return (NULL);
     }
     }
-    fd = isc::xfr::recv_fd(sock);
+    fd = isc::util::io::recv_fd(sock);
     return (Py_BuildValue("i", fd));
     return (Py_BuildValue("i", fd));
 }
 }
 
 
@@ -37,7 +37,7 @@ fdshare_send_fd(PyObject*, PyObject* args) {
     if (!PyArg_ParseTuple(args, "ii", &sock, &fd)) {
     if (!PyArg_ParseTuple(args, "ii", &sock, &fd)) {
         return (NULL);
         return (NULL);
     }
     }
-    result = isc::xfr::send_fd(sock, fd);
+    result = isc::util::io::send_fd(sock, fd);
     return (Py_BuildValue("i", result));
     return (Py_BuildValue("i", result));
 }
 }
 
 
@@ -61,20 +61,33 @@ static PyModuleDef bind10_fdshare_python = {
 };
 };
 
 
 PyMODINIT_FUNC
 PyMODINIT_FUNC
-PyInit_libxfr_python(void) {
-    PyObject* mod = PyModule_Create(&bind10_fdshare_python);
+PyInit_libutil_io_python(void) {
+    PyObject *mod = PyModule_Create(&bind10_fdshare_python);
     if (mod == NULL) {
     if (mod == NULL) {
         return (NULL);
         return (NULL);
     }
     }
 
 
-    PyObject* XFR_FD_RECEIVE_FAIL = Py_BuildValue("i", isc::xfr::XFR_FD_RECEIVE_FAIL);
-    if (XFR_FD_RECEIVE_FAIL == NULL) {
+    PyObject* FD_COMM_ERROR = Py_BuildValue("i", isc::util::io::FD_COMM_ERROR);
+    if (FD_COMM_ERROR == NULL) {
         Py_XDECREF(mod);
         Py_XDECREF(mod);
         return (NULL);
         return (NULL);
     }
     }
-    int ret = PyModule_AddObject(mod, "XFR_FD_RECEIVE_FAIL", XFR_FD_RECEIVE_FAIL);
+    int ret = PyModule_AddObject(mod, "FD_COMM_ERROR", FD_COMM_ERROR);
     if (-1 == ret) {
     if (-1 == ret) {
-        Py_XDECREF(XFR_FD_RECEIVE_FAIL);
+        Py_XDECREF(FD_COMM_ERROR);
+        Py_XDECREF(mod);
+        return (NULL);
+    }
+
+    PyObject* FD_OTHER_ERROR = Py_BuildValue("i",
+                                             isc::util::io::FD_OTHER_ERROR);
+    if (FD_OTHER_ERROR == NULL) {
+        Py_XDECREF(mod);
+        return (NULL);
+    }
+    ret = PyModule_AddObject(mod, "FD_OTHER_ERROR", FD_OTHER_ERROR);
+    if (-1 == ret) {
+        Py_XDECREF(FD_OTHER_ERROR);
         Py_XDECREF(mod);
         Py_XDECREF(mod);
         return (NULL);
         return (NULL);
     }
     }

+ 25 - 0
src/lib/util/io/tests/Makefile.am

@@ -0,0 +1,25 @@
+CLEANFILES = *.gcno *.gcda
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += fd_tests.cc
+run_unittests_SOURCES += fd_share_tests.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
+run_unittests_LDADD += \
+	$(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)

+ 74 - 0
src/lib/util/io/tests/fd_share_tests.cc

@@ -0,0 +1,74 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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 "../fd.h"
+#include "../fd_share.h"
+
+#include <util/unittests/fork.h>
+
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <cstdio>
+
+using namespace isc::util::io;
+using namespace isc::util::unittests;
+
+namespace {
+
+// We test that we can transfer a pipe over other pipe
+TEST(FDShare, transfer) {
+    // Get a pipe and fork
+    int pipes[2];
+    ASSERT_NE(-1, socketpair(AF_UNIX, SOCK_STREAM, 0, pipes));
+    pid_t sender(fork());
+    ASSERT_NE(-1, sender);
+    if(sender) { // We are in parent
+        // Close the other side of pipe, we want only writible one
+        EXPECT_NE(-1, close(pipes[0]));
+        // Get a process to check data
+        int fd(0);
+        pid_t checker(check_output(&fd, "data", 4));
+        ASSERT_NE(-1, checker);
+        // Now, send the file descriptor, close it and close the pipe
+        EXPECT_NE(-1, send_fd(pipes[1], fd));
+        EXPECT_NE(-1, close(pipes[1]));
+        EXPECT_NE(-1, close(fd));
+        // Check both subprocesses ended well
+        EXPECT_TRUE(process_ok(sender));
+        EXPECT_TRUE(process_ok(checker));
+    } else { // We are in child. We do not use ASSERT here
+        // Close the write end, we only read
+        if(close(pipes[1])) {
+            exit(1);
+        }
+        // Get the file descriptor
+        int fd(recv_fd(pipes[0]));
+        if(fd == -1) {
+            exit(1);
+        }
+        // This pipe is not needed
+        if(close(pipes[0])) {
+            exit(1);
+        }
+        // Send "data" trough the received fd, close it and be done
+        if(!write_data(fd, "data", 4) || close(fd) == -1) {
+            exit(1);
+        }
+        exit(0);
+    }
+}
+
+}

+ 66 - 0
src/lib/util/io/tests/fd_tests.cc

@@ -0,0 +1,66 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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 "../fd.h"
+
+#include <util/unittests/fork.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util::io;
+using namespace isc::util::unittests;
+
+namespace {
+
+// Make sure the test is large enough and does not fit into one
+// read or write request
+const size_t TEST_DATA_SIZE = 8 * 1024 * 1024;
+
+class FDTest : public ::testing::Test {
+    public:
+        unsigned char *data, *buffer;
+        FDTest() :
+            // We do not care what is inside, we just need it to be the same
+            data(new unsigned char[TEST_DATA_SIZE]),
+            buffer(NULL)
+        { }
+        ~ FDTest() {
+            delete[] data;
+            delete[] buffer;
+        }
+};
+
+// Test we read what was sent
+TEST_F(FDTest, read) {
+    int read_pipe(0);
+    buffer = new unsigned char[TEST_DATA_SIZE];
+    pid_t feeder(provide_input(&read_pipe, data, TEST_DATA_SIZE));
+    ASSERT_GE(feeder, 0);
+    ssize_t received(read_data(read_pipe, buffer, TEST_DATA_SIZE));
+    EXPECT_TRUE(process_ok(feeder));
+    EXPECT_EQ(TEST_DATA_SIZE, received);
+    EXPECT_EQ(0, memcmp(data, buffer, received));
+}
+
+// Test we write the correct thing
+TEST_F(FDTest, write) {
+    int write_pipe(0);
+    pid_t checker(check_output(&write_pipe, data, TEST_DATA_SIZE));
+    ASSERT_GE(checker, 0);
+    EXPECT_TRUE(write_data(write_pipe, data, TEST_DATA_SIZE));
+    EXPECT_EQ(0, close(write_pipe));
+    EXPECT_TRUE(process_ok(checker));
+}
+
+}

+ 22 - 0
src/lib/util/io/tests/run_unittests.cc

@@ -0,0 +1,22 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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 <gtest/gtest.h>
+
+int
+main(int argc, char *argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}

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

@@ -0,0 +1,9 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+lib_LTLIBRARIES = libutil_unittests.la
+libutil_unittests_la_SOURCES = fork.h fork.cc
+libutil_unittests_la_LIBADD = \
+	$(top_builddir)/src/lib/util/io/libutil_io.la
+
+CLEANFILES = *.gcno *.gcda

+ 5 - 0
src/lib/util/unittests/README

@@ -0,0 +1,5 @@
+This directory contains some code that is useful while writing various
+unittest code. It doesn't contain any code that would actually run in
+bind10.
+
+Because this is a test code, we do not test it explicitly.

+ 141 - 0
src/lib/util/unittests/fork.cc

@@ -0,0 +1,141 @@
+// Copyright (C) 2010  CZ NIC
+//
+// 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 "fork.h"
+
+#include <util/io/fd.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <cstring>
+#include <cerrno>
+#include <cstdlib>
+#include <cstdio>
+
+using namespace isc::util::io;
+
+namespace {
+
+// Just a NOP function to ignore a signal but let it interrupt function.
+void no_handler(int) { }
+
+};
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+bool
+process_ok(pid_t process) {
+    // Create a timeout
+    struct sigaction ignored, original;
+    memset(&ignored, 0, sizeof ignored);
+    ignored.sa_handler = no_handler;
+    if (sigaction(SIGALRM, &ignored, &original)) {
+        return false;
+    }
+    alarm(3);
+    int status;
+    int result(waitpid(process, &status, 0) == -1);
+    // Cancel the alarm and return the original handler
+    alarm(0);
+    if (sigaction(SIGALRM, &original, NULL)) {
+        return false;
+    }
+    // Check what we found out
+    if (result) {
+        if (errno == EINTR)
+            kill(process, SIGTERM);
+        return false;
+    }
+    return WIFEXITED(status) && WEXITSTATUS(status) == 0;
+}
+
+/*
+ * This creates a pipe, forks and feeds the pipe with given data.
+ * Used to provide the input in non-blocking/asynchronous way.
+ */
+pid_t
+provide_input(int *read_pipe, const void *input, const size_t length)
+{
+    int pipes[2];
+    if (pipe(pipes)) {
+        return -1;
+    }
+    *read_pipe = pipes[0];
+    pid_t pid(fork());
+    if (pid) { // We are in the parent
+        return pid;
+    } else { // This is in the child, just puth the data there
+        close(pipes[0]);
+        if (!write_data(pipes[1], input, length)) {
+            exit(1);
+        } else {
+            close(pipes[1]);
+            exit(0);
+        }
+    }
+}
+
+/*
+ * This creates a pipe, forks and reads the pipe and compares it
+ * with given data. Used to check output of run in asynchronous way.
+ */
+pid_t
+check_output(int *write_pipe, const void *output, const size_t length)
+{
+    int pipes[2];
+    if (pipe(pipes)) {
+        return -1;
+    }
+    *write_pipe = pipes[1];
+    pid_t pid(fork());
+    if (pid) { // We are in parent
+        return pid;
+    } else {
+        close(pipes[1]);
+        unsigned char buffer[length + 1];
+        // Try to read one byte more to see if the output ends here
+        size_t got_length(read_data(pipes[0], buffer, length + 1));
+        bool ok(true);
+        if (got_length != length) {
+            fprintf(stderr, "Different length (expected %u, got %u)\n",
+                static_cast<unsigned>(length),
+                static_cast<unsigned>(got_length));
+            ok = false;
+        }
+        if(!ok || memcmp(buffer, output, length)) {
+            const unsigned char *output_c(static_cast<const unsigned char *>(
+                output));
+            // If the differ, print what we have
+            for(size_t i(0); i != got_length; ++ i) {
+                fprintf(stderr, "%02hhx", buffer[i]);
+            }
+            fprintf(stderr, "\n");
+            for(size_t i(0); i != length; ++ i) {
+                fprintf(stderr, "%02hhx", output_c[i]);
+            }
+            fprintf(stderr, "\n");
+            exit(1);
+        } else {
+            exit(0);
+        }
+    }
+}
+
+}
+}
+}

+ 52 - 0
src/lib/util/unittests/fork.h

@@ -0,0 +1,52 @@
+// Copyright (C) 2010  CZ NIC
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UTIL_UNITTESTS_FORK_H
+#define __UTIL_UNITTESTS_FORK_H 1
+
+#include <unistd.h>
+
+/**
+ * @file fork.h
+ * @short Help functions to fork the test case process.
+ * Various functions to fork a process and feed some data to pipe, check
+ * its output and such lives here.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/**
+ * @short Checks that a process terminates correctly.
+ * Waits for a process to terminate (with a short timeout, this should be
+ * used whan the process is about tu terminate) and checks its exit code.
+ *
+ * @return True if the process terminates with 0, false otherwise.
+ * @param process The ID of process to wait for.
+ */
+bool
+process_ok(pid_t process);
+
+pid_t
+provide_input(int *read_pipe, const void *input, const size_t length);
+
+pid_t
+check_output(int *write_pipe, const void *output, const size_t length);
+
+} // End of the namespace
+}
+}
+
+#endif // __UTIL_UNITTESTS_FORK_H

+ 3 - 11
src/lib/xfr/Makefile.am

@@ -2,7 +2,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 
-AM_CXXFLAGS = $(B10_CXXFLAGS) -Wno-strict-aliasing
+AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS += -Wno-unused-parameter # see src/lib/cc/Makefile.am
 AM_CXXFLAGS += -Wno-unused-parameter # see src/lib/cc/Makefile.am
 if USE_CLANGPP
 if USE_CLANGPP
 AM_CXXFLAGS += -Wno-error
 AM_CXXFLAGS += -Wno-error
@@ -11,13 +11,5 @@ endif
 CLEANFILES = *.gcno *.gcda
 CLEANFILES = *.gcno *.gcda
 
 
 lib_LTLIBRARIES = libxfr.la
 lib_LTLIBRARIES = libxfr.la
-libxfr_la_SOURCES = xfrout_client.h xfrout_client.cc 
-libxfr_la_SOURCES += fd_share.h fd_share.cc
-
-pyexec_LTLIBRARIES = libxfr_python.la
-# Python prefers .so, while some OSes (specifically MacOS) use a different
-# suffix for dynamic objects.  -module is necessary to work this around.
-libxfr_python_la_LDFLAGS = -module
-libxfr_python_la_SOURCES = fdshare_python.cc fd_share.cc fd_share.h
-libxfr_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
-libxfr_python_la_CXXFLAGS = $(AM_CXXFLAGS)
+libxfr_la_SOURCES = xfrout_client.h xfrout_client.cc
+libxfr_la_LIBADD = $(top_builddir)/src/lib/util/io/libutil_io.la

+ 0 - 42
src/lib/xfr/fd_share.h

@@ -1,42 +0,0 @@
-// Copyright (C) 2010  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 FD_SHARE_H_
-#define FD_SHARE_H_
-
-namespace isc {
-namespace xfr {
-
-/// Failed to receive xfr socket descriptor "fd" on unix domain socket 'sock'
-const int XFR_FD_RECEIVE_FAIL = -2;
-
-// Receive socket descriptor on unix domain socket 'sock'.
-// Returned value is the socket descriptor received.
-// Returned XFR_FD_RECEIVE_FAIL if failed to receive xfr socket descriptor
-// Errors are indicated by a return value of -1.
-int recv_fd(const int sock);
-
-// Send socket descriptor "fd" to server over unix domain socket 'sock',
-// the connection from socket 'sock' to unix domain server should be established first.
-// Errors are indicated by a return value of -1.
-int send_fd(const int sock, const int fd);
-
-} // End for namespace xfr
-} // End for namespace isc
-
-#endif
-
-// Local Variables:
-// mode: c++
-// End:

+ 3 - 2
src/lib/xfr/xfrout_client.cc

@@ -20,10 +20,11 @@
 #include <unistd.h>
 #include <unistd.h>
 #include <asio.hpp>
 #include <asio.hpp>
 
 
-#include <xfr/fd_share.h>
+#include <util/io/fd_share.h>
 #include <xfr/xfrout_client.h>
 #include <xfr/xfrout_client.h>
 
 
 using namespace std;
 using namespace std;
+using namespace isc::util::io;
 using asio::local::stream_protocol;
 using asio::local::stream_protocol;
 
 
 namespace isc {
 namespace isc {
@@ -72,7 +73,7 @@ XfroutClient::sendXfroutRequestInfo(const int tcp_sock,
                                     const void* const msg_data,
                                     const void* const msg_data,
                                     const uint16_t msg_len)
                                     const uint16_t msg_len)
 {
 {
-    if (-1 == send_fd(impl_->socket_.native(), tcp_sock)) {
+    if (send_fd(impl_->socket_.native(), tcp_sock) < 0) {
         isc_throw(XfroutError,
         isc_throw(XfroutError,
                   "Failed to send the socket file descriptor "
                   "Failed to send the socket file descriptor "
                   "to xfrout module");
                   "to xfrout module");