BIND 10 master, updated. b86fdfc8326c74838f92e8752d8f0421e6b03a36 [master] Merge branch 'trac1452'
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Dec 21 22:27:04 UTC 2011
The branch, master has been updated
via b86fdfc8326c74838f92e8752d8f0421e6b03a36 (commit)
via 3dfa82c4bae8bac305a4781eadc80ecfa6608534 (commit)
via f709af9e07ce8a0b700862fbc086e72c8f46f784 (commit)
via 267a466b3ecac6a2ec07d7c873a3cbb9041f67cb (commit)
via 5050c9607584178e27d9caf196c25df01b3cd651 (commit)
via 43df0d5564a5363deaeb8e1a3b76eff3426adf4c (commit)
via 2b4c9db386e884ba4ef5113bb52cf0f1ada97c46 (commit)
via 8fda3f02eafeb955b965d9cc04f5a703dbab5f02 (commit)
via a27ddbc73f2fbf66172a2f3f47f1d4d4a11a027c (commit)
via 1a05959b2741d390294f50652765e125931fcc16 (commit)
via 39db78f60268fb6cf001307f2ddb3243926ef6b2 (commit)
via 0bcc177400198c0ea2ede57651853302a6d37490 (commit)
via 9176c84be6b46b48cf31a21764db0ada5d0fda26 (commit)
via ed5fa95326dc7233e4e289acda77d7a1fb562f89 (commit)
via 4d5f96b4d083f8ba171bc90e5767ea89e2dc98c6 (commit)
via 1e4d796212bc7c91def18e9edd838c92b042e6b1 (commit)
via 47256b986057875cb535aff393bd604b1d8cd620 (commit)
via 787a439b41c71c78a7de585d3603f477474426cc (commit)
via 12649b6a44db756795a3160d1a70c7fe3a6fc9b1 (commit)
via 8cd3a3f50336eff26b80af13326daf3df337a234 (commit)
via f37822dcf51b014d5fa9f93f2e9ac85dec0d0ede (commit)
via 1007c575bbedf9bd07fef24de28d5c744b4c2293 (commit)
via f942fb1a8c2475bac2fd73e5fbf979fcacbd6f2f (commit)
via 6328a99430a833f72c7faa3515cde78e49b5de12 (commit)
via e0c434fbb5e15c98af42b86248b3de1472a4a7b0 (commit)
via b5e5d9e912722ff8ab9c2cf5e16ca913b64733aa (commit)
via 09ed7a24bf40675619e01ffdc00068c1560f6f31 (commit)
from a47f01b6c860c565516456d329a4aebe63d346a0 (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 -----------------------------------------------------------------
commit b86fdfc8326c74838f92e8752d8f0421e6b03a36
Merge: a47f01b 3dfa82c
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Wed Dec 21 14:17:42 2011 -0800
[master] Merge branch 'trac1452'
-----------------------------------------------------------------------
Summary of changes:
doc/Doxyfile | 2 +-
src/bin/xfrout/xfrout.py.in | 2 +-
src/lib/util/io/Makefile.am | 6 +-
src/lib/util/io/fd_share.cc | 17 +-
src/lib/util/io/fd_share.h | 14 +-
src/lib/util/io/fdshare_python.cc | 11 +-
src/lib/util/io/sockaddr_util.h | 66 ++
src/lib/util/io/socketsession.cc | 434 +++++++++++++
src/lib/util/io/socketsession.h | 466 ++++++++++++++
src/lib/util/tests/Makefile.am | 2 +
src/lib/util/tests/socketsession_unittest.cc | 845 ++++++++++++++++++++++++++
11 files changed, 1846 insertions(+), 19 deletions(-)
create mode 100644 src/lib/util/io/sockaddr_util.h
create mode 100644 src/lib/util/io/socketsession.cc
create mode 100644 src/lib/util/io/socketsession.h
create mode 100644 src/lib/util/tests/socketsession_unittest.cc
-----------------------------------------------------------------------
diff --git a/doc/Doxyfile b/doc/Doxyfile
index ee5aaf8..c9c5c5a 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -573,7 +573,7 @@ INPUT = ../src/lib/exceptions ../src/lib/cc \
../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log \
../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
- ../src/bin/sockcreator/ ../src/lib/util/ \
+ ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
../src/lib/resolve ../src/lib/acl ../src/bin/dhcp6 ../src/lib/dhcp
# This tag can be used to specify the character encoding of the source files
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index 310a0aa..691c7db 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -716,7 +716,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
# This may happen when one xfrout process try to connect to
# xfrout unix socket server, to check whether there is another
# xfrout running.
- if sock_fd == FD_COMM_ERROR:
+ if sock_fd == FD_SYSTEM_ERROR:
logger.error(XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR)
return
diff --git a/src/lib/util/io/Makefile.am b/src/lib/util/io/Makefile.am
index cbcd54d..96b9d25 100644
--- a/src/lib/util/io/Makefile.am
+++ b/src/lib/util/io/Makefile.am
@@ -1,8 +1,12 @@
AM_CXXFLAGS = $(B10_CXXFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
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
+libutil_io_la_SOURCES += socketsession.h socketsession.cc sockaddr_util.h
+libutil_io_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/io/fd_share.cc b/src/lib/util/io/fd_share.cc
index 92576e0..b330b00 100644
--- a/src/lib/util/io/fd_share.cc
+++ b/src/lib/util/io/fd_share.cc
@@ -18,6 +18,7 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
+#include <errno.h>
#include <stdlib.h> // for malloc and free
#include "fd_share.h"
@@ -87,18 +88,22 @@ recv_fd(const int sock) {
msghdr.msg_controllen = cmsg_space(sizeof(int));
msghdr.msg_control = malloc(msghdr.msg_controllen);
if (msghdr.msg_control == NULL) {
- return (FD_OTHER_ERROR);
+ return (FD_SYSTEM_ERROR);
}
- if (recvmsg(sock, &msghdr, 0) < 0) {
+ const int cc = recvmsg(sock, &msghdr, 0);
+ if (cc <= 0) {
free(msghdr.msg_control);
- return (FD_COMM_ERROR);
+ if (cc == 0) {
+ errno = ECONNRESET;
+ }
+ return (FD_SYSTEM_ERROR);
}
const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
int fd = FD_OTHER_ERROR;
if (cmsg != NULL && cmsg->cmsg_len == cmsg_len(sizeof(int)) &&
cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
- fd = *(const int*)CMSG_DATA(cmsg);
+ memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
}
free(msghdr.msg_control);
return (fd);
@@ -127,11 +132,11 @@ send_fd(const int sock, const int fd) {
cmsg->cmsg_len = cmsg_len(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
- *(int*)CMSG_DATA(cmsg) = fd;
+ memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
const int ret = sendmsg(sock, &msghdr, 0);
free(msghdr.msg_control);
- return (ret >= 0 ? 0 : FD_COMM_ERROR);
+ return (ret >= 0 ? 0 : FD_SYSTEM_ERROR);
}
} // End for namespace io
diff --git a/src/lib/util/io/fd_share.h b/src/lib/util/io/fd_share.h
index b74d4ef..2b30abd 100644
--- a/src/lib/util/io/fd_share.h
+++ b/src/lib/util/io/fd_share.h
@@ -25,7 +25,7 @@ namespace isc {
namespace util {
namespace io {
-const int FD_COMM_ERROR = -2;
+const int FD_SYSTEM_ERROR = -2;
const int FD_OTHER_ERROR = -1;
/**
@@ -33,8 +33,11 @@ const int FD_OTHER_ERROR = -1;
* 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.
+ * \return FD_SYSTEM_ERROR when there's an error at the operating system
+ * level (such as a system call failure). The global 'errno' variable
+ * indicates the specific error. 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.
*/
@@ -45,8 +48,9 @@ int recv_fd(const int sock);
* 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.
+ * \return FD_SYSTEM_ERROR when there's an error at the operating system
+ * level (such as a system call failure). The global 'errno' variable
+ * indicates the specific error.
* \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
diff --git a/src/lib/util/io/fdshare_python.cc b/src/lib/util/io/fdshare_python.cc
index 0a41107..249f8b0 100644
--- a/src/lib/util/io/fdshare_python.cc
+++ b/src/lib/util/io/fdshare_python.cc
@@ -67,14 +67,15 @@ PyInit_libutil_io_python(void) {
return (NULL);
}
- PyObject* FD_COMM_ERROR = Py_BuildValue("i", isc::util::io::FD_COMM_ERROR);
- if (FD_COMM_ERROR == NULL) {
+ PyObject* FD_SYSTEM_ERROR = Py_BuildValue("i",
+ isc::util::io::FD_SYSTEM_ERROR);
+ if (FD_SYSTEM_ERROR == NULL) {
Py_XDECREF(mod);
return (NULL);
}
- int ret = PyModule_AddObject(mod, "FD_COMM_ERROR", FD_COMM_ERROR);
- if (-1 == ret) {
- Py_XDECREF(FD_COMM_ERROR);
+ int ret = PyModule_AddObject(mod, "FD_SYSTEM_ERROR", FD_SYSTEM_ERROR);
+ if (ret == -1) {
+ Py_XDECREF(FD_SYSTEM_ERROR);
Py_XDECREF(mod);
return (NULL);
}
diff --git a/src/lib/util/io/sockaddr_util.h b/src/lib/util/io/sockaddr_util.h
new file mode 100644
index 0000000..017852a
--- /dev/null
+++ b/src/lib/util/io/sockaddr_util.h
@@ -0,0 +1,66 @@
+// 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 __SOCKADDR_UTIL_H_
+#define __SOCKADDR_UTIL_H_ 1
+
+#include <cassert>
+
+// This definitions in this file are for the convenience of internal
+// implementation and test code, and are not intended to be used publicly.
+// The namespace "internal" indicates the intent.
+
+namespace isc {
+namespace util {
+namespace io {
+namespace internal {
+
+inline socklen_t
+getSALength(const struct sockaddr& sa) {
+ if (sa.sa_family == AF_INET) {
+ return (sizeof(struct sockaddr_in));
+ } else {
+ assert(sa.sa_family == AF_INET6);
+ return (sizeof(struct sockaddr_in6));
+ }
+}
+
+// Lower level C-APIs require conversion between various variants of
+// sockaddr's, which is not friendly with C++. The following templates
+// are a shortcut of common workaround conversion in such cases.
+
+template <typename SAType>
+const struct sockaddr*
+convertSockAddr(const SAType* sa) {
+ const void* p = sa;
+ return (static_cast<const struct sockaddr*>(p));
+}
+
+template <typename SAType>
+struct sockaddr*
+convertSockAddr(SAType* sa) {
+ void* p = sa;
+ return (static_cast<struct sockaddr*>(p));
+}
+
+}
+}
+}
+}
+
+#endif // __SOCKADDR_UTIL_H_
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/io/socketsession.cc b/src/lib/util/io/socketsession.cc
new file mode 100644
index 0000000..4c50949
--- /dev/null
+++ b/src/lib/util/io/socketsession.cc
@@ -0,0 +1,434 @@
+// 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+
+#include <cerrno>
+#include <csignal>
+#include <cstddef>
+#include <cstring>
+#include <cassert>
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include "fd_share.h"
+#include "socketsession.h"
+#include "sockaddr_util.h"
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace io {
+
+using namespace internal;
+
+// The expected max size of the session header: 2-byte header length,
+// 6 32-bit fields, and 2 sockaddr structure. (see the SocketSessionUtility
+// overview description in the header file). sizeof sockaddr_storage
+// should be the possible max of any sockaddr structure
+const size_t DEFAULT_HEADER_BUFLEN = sizeof(uint16_t) + sizeof(uint32_t) * 6 +
+ sizeof(struct sockaddr_storage) * 2;
+
+// The allowable maximum size of data passed with the socket FD. For now
+// we use a fixed value of 65535, the largest possible size of valid DNS
+// messages. We may enlarge it or make it configurable as we see the need
+// for more flexibility.
+const int MAX_DATASIZE = 65535;
+
+// The initial buffer size for receiving socket session data in the receiver.
+// This value is the maximum message size of DNS messages carried over UDP
+// (without EDNS). In our expected usage (at the moment) this should be
+// sufficiently large (the expected data is AXFR/IXFR query or an UPDATE
+// requests. The former should be generally quite small. While the latter
+// could be large, it would often be small enough for a single UDP message).
+// If it turns out that there are many exceptions, we may want to extend
+// the class so that this value can be customized. Note that the buffer
+// will be automatically extended for longer data and this is only about
+// efficiency.
+const size_t INITIAL_BUFSIZE = 512;
+
+// The (default) socket buffer size for the forwarder and receiver. This is
+// chosen to be sufficiently large to store two full-size DNS messages. We
+// may want to customize this value in future.
+const int SOCKSESSION_BUFSIZE = (DEFAULT_HEADER_BUFLEN + MAX_DATASIZE) * 2;
+
+struct SocketSessionForwarder::ForwarderImpl {
+ ForwarderImpl() : buf_(DEFAULT_HEADER_BUFLEN) {}
+ struct sockaddr_un sock_un_;
+ socklen_t sock_un_len_;
+ int fd_;
+ OutputBuffer buf_;
+};
+
+SocketSessionForwarder::SocketSessionForwarder(const std::string& unix_file) :
+ impl_(NULL)
+{
+ // We need to filter SIGPIPE for subsequent push(). See the class
+ // description.
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
+ isc_throw(Unexpected, "Failed to filter SIGPIPE: " << strerror(errno));
+ }
+
+ ForwarderImpl impl;
+ if (sizeof(impl.sock_un_.sun_path) - 1 < unix_file.length()) {
+ isc_throw(SocketSessionError,
+ "File name for a UNIX domain socket is too long: " <<
+ unix_file);
+ }
+ impl.sock_un_.sun_family = AF_UNIX;
+ // the copy should be safe due to the above check, but we'd be rather
+ // paranoid about making it 100% sure even if the check has a bug (with
+ // triggering the assertion in the worse case)
+ strncpy(impl.sock_un_.sun_path, unix_file.c_str(),
+ sizeof(impl.sock_un_.sun_path));
+ assert(impl.sock_un_.sun_path[sizeof(impl.sock_un_.sun_path) - 1] == '\0');
+ impl.sock_un_len_ = offsetof(struct sockaddr_un, sun_path) +
+ unix_file.length();
+#ifdef HAVE_SA_LEN
+ impl.sock_un_.sun_len = impl.sock_un_len_;
+#endif
+ impl.fd_ = -1;
+
+ impl_ = new ForwarderImpl;
+ *impl_ = impl;
+}
+
+SocketSessionForwarder::~SocketSessionForwarder() {
+ if (impl_->fd_ != -1) {
+ close();
+ }
+ delete impl_;
+}
+
+void
+SocketSessionForwarder::connectToReceiver() {
+ if (impl_->fd_ != -1) {
+ isc_throw(BadValue, "Duplicate connect to UNIX domain "
+ "endpoint " << impl_->sock_un_.sun_path);
+ }
+
+ impl_->fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (impl_->fd_ == -1) {
+ isc_throw(SocketSessionError, "Failed to create a UNIX domain socket: "
+ << strerror(errno));
+ }
+ // Make the socket non blocking
+ int fcntl_flags = fcntl(impl_->fd_, F_GETFL, 0);
+ if (fcntl_flags != -1) {
+ fcntl_flags |= O_NONBLOCK;
+ fcntl_flags = fcntl(impl_->fd_, F_SETFL, fcntl_flags);
+ }
+ if (fcntl_flags == -1) {
+ close(); // note: this is the internal method, not ::close()
+ isc_throw(SocketSessionError,
+ "Failed to make UNIX domain socket non blocking: " <<
+ strerror(errno));
+ }
+ // Ensure the socket send buffer is large enough. If we can't get the
+ // current size, simply set the sufficient size.
+ int sndbuf_size;
+ socklen_t sndbuf_size_len = sizeof(sndbuf_size);
+ if (getsockopt(impl_->fd_, SOL_SOCKET, SO_SNDBUF, &sndbuf_size,
+ &sndbuf_size_len) == -1 ||
+ sndbuf_size < SOCKSESSION_BUFSIZE) {
+ if (setsockopt(impl_->fd_, SOL_SOCKET, SO_SNDBUF, &SOCKSESSION_BUFSIZE,
+ sizeof(SOCKSESSION_BUFSIZE)) == -1) {
+ close();
+ isc_throw(SocketSessionError, "Failed to set send buffer size");
+ }
+ }
+ if (connect(impl_->fd_, convertSockAddr(&impl_->sock_un_),
+ impl_->sock_un_len_) == -1) {
+ close();
+ isc_throw(SocketSessionError, "Failed to connect to UNIX domain "
+ "endpoint " << impl_->sock_un_.sun_path << ": " <<
+ strerror(errno));
+ }
+}
+
+void
+SocketSessionForwarder::close() {
+ if (impl_->fd_ == -1) {
+ isc_throw(BadValue, "Attempt of close before connect");
+ }
+ ::close(impl_->fd_);
+ impl_->fd_ = -1;
+}
+
+void
+SocketSessionForwarder::push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len)
+{
+ if (impl_->fd_ == -1) {
+ isc_throw(BadValue, "Attempt of push before connect");
+ }
+ if ((local_end.sa_family != AF_INET && local_end.sa_family != AF_INET6) ||
+ (remote_end.sa_family != AF_INET && remote_end.sa_family != AF_INET6))
+ {
+ isc_throw(BadValue, "Invalid address family: must be "
+ "AF_INET or AF_INET6; " <<
+ static_cast<int>(local_end.sa_family) << ", " <<
+ static_cast<int>(remote_end.sa_family) << " given");
+ }
+ if (family != local_end.sa_family || family != remote_end.sa_family) {
+ isc_throw(BadValue, "Inconsistent address family: must be "
+ << static_cast<int>(family) << "; "
+ << static_cast<int>(local_end.sa_family) << ", "
+ << static_cast<int>(remote_end.sa_family) << " given");
+ }
+ if (data_len == 0 || data == NULL) {
+ isc_throw(BadValue, "Data for a socket session must not be empty");
+ }
+ if (data_len > MAX_DATASIZE) {
+ isc_throw(BadValue, "Invalid socket session data size: " <<
+ data_len << ", must not exceed " << MAX_DATASIZE);
+ }
+
+ if (send_fd(impl_->fd_, sock) != 0) {
+ isc_throw(SocketSessionError, "FD passing failed: " <<
+ strerror(errno));
+ }
+
+ impl_->buf_.clear();
+ // Leave the space for the header length
+ impl_->buf_.skip(sizeof(uint16_t));
+ // Socket properties: family, type, protocol
+ impl_->buf_.writeUint32(static_cast<uint32_t>(family));
+ impl_->buf_.writeUint32(static_cast<uint32_t>(type));
+ impl_->buf_.writeUint32(static_cast<uint32_t>(protocol));
+ // Local endpoint
+ impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(local_end)));
+ impl_->buf_.writeData(&local_end, getSALength(local_end));
+ // Remote endpoint
+ impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(remote_end)));
+ impl_->buf_.writeData(&remote_end, getSALength(remote_end));
+ // Data length. Must be fit uint32 due to the range check above.
+ const uint32_t data_len32 = static_cast<uint32_t>(data_len);
+ assert(data_len == data_len32); // shouldn't cause overflow.
+ impl_->buf_.writeUint32(data_len32);
+ // Write the resulting header length at the beginning of the buffer
+ impl_->buf_.writeUint16At(impl_->buf_.getLength() - sizeof(uint16_t), 0);
+
+ const struct iovec iov[2] = {
+ { const_cast<void*>(impl_->buf_.getData()), impl_->buf_.getLength() },
+ { const_cast<void*>(data), data_len }
+ };
+ const int cc = writev(impl_->fd_, iov, 2);
+ if (cc != impl_->buf_.getLength() + data_len) {
+ if (cc < 0) {
+ isc_throw(SocketSessionError,
+ "Write failed in forwarding a socket session: " <<
+ strerror(errno));
+ }
+ isc_throw(SocketSessionError,
+ "Incomplete write in forwarding a socket session: " << cc <<
+ "/" << (impl_->buf_.getLength() + data_len));
+ }
+}
+
+SocketSession::SocketSession(int sock, int family, int type, int protocol,
+ const sockaddr* local_end,
+ const sockaddr* remote_end,
+ const void* data, size_t data_len) :
+ sock_(sock), family_(family), type_(type), protocol_(protocol),
+ local_end_(local_end), remote_end_(remote_end),
+ data_(data), data_len_(data_len)
+{
+ if (local_end == NULL || remote_end == NULL) {
+ isc_throw(BadValue, "sockaddr must be non NULL for SocketSession");
+ }
+ if (data_len == 0) {
+ isc_throw(BadValue, "data_len must be non 0 for SocketSession");
+ }
+ if (data == NULL) {
+ isc_throw(BadValue, "data must be non NULL for SocketSession");
+ }
+}
+
+struct SocketSessionReceiver::ReceiverImpl {
+ ReceiverImpl(int fd) : fd_(fd),
+ sa_local_(convertSockAddr(&ss_local_)),
+ sa_remote_(convertSockAddr(&ss_remote_)),
+ header_buf_(DEFAULT_HEADER_BUFLEN),
+ data_buf_(INITIAL_BUFSIZE)
+ {
+ if (setsockopt(fd_, SOL_SOCKET, SO_RCVBUF, &SOCKSESSION_BUFSIZE,
+ sizeof(SOCKSESSION_BUFSIZE)) == -1) {
+ isc_throw(SocketSessionError,
+ "Failed to set receive buffer size");
+ }
+ }
+
+ const int fd_;
+ struct sockaddr_storage ss_local_; // placeholder for local endpoint
+ struct sockaddr* const sa_local_;
+ struct sockaddr_storage ss_remote_; // placeholder for remote endpoint
+ struct sockaddr* const sa_remote_;
+
+ // placeholder for session header and data
+ vector<uint8_t> header_buf_;
+ vector<uint8_t> data_buf_;
+};
+
+SocketSessionReceiver::SocketSessionReceiver(int fd) :
+ impl_(new ReceiverImpl(fd))
+{
+}
+
+SocketSessionReceiver::~SocketSessionReceiver() {
+ delete impl_;
+}
+
+namespace {
+// A shortcut to throw common exception on failure of recv(2)
+void
+readFail(int actual_len, int expected_len) {
+ if (expected_len < 0) {
+ isc_throw(SocketSessionError, "Failed to receive data from "
+ "SocketSessionForwarder: " << strerror(errno));
+ }
+ isc_throw(SocketSessionError, "Incomplete data from "
+ "SocketSessionForwarder: " << actual_len << "/" <<
+ expected_len);
+}
+
+// A helper container for a (socket) file descriptor used in
+// SocketSessionReceiver::pop that ensures the socket is closed unless it
+// can be safely passed to the caller via release().
+struct ScopedSocket : boost::noncopyable {
+ ScopedSocket(int fd) : fd_(fd) {}
+ ~ScopedSocket() {
+ if (fd_ >= 0) {
+ close(fd_);
+ }
+ }
+ int release() {
+ const int fd = fd_;
+ fd_ = -1;
+ return (fd);
+ }
+ int fd_;
+};
+}
+
+SocketSession
+SocketSessionReceiver::pop() {
+ ScopedSocket passed_sock(recv_fd(impl_->fd_));
+ if (passed_sock.fd_ == FD_SYSTEM_ERROR) {
+ isc_throw(SocketSessionError, "Receiving a forwarded FD failed: " <<
+ strerror(errno));
+ } else if (passed_sock.fd_ < 0) {
+ isc_throw(SocketSessionError, "No FD forwarded");
+ }
+
+ uint16_t header_len;
+ const int cc_hlen = recv(impl_->fd_, &header_len, sizeof(header_len),
+ MSG_WAITALL);
+ if (cc_hlen < sizeof(header_len)) {
+ readFail(cc_hlen, sizeof(header_len));
+ }
+ header_len = InputBuffer(&header_len, sizeof(header_len)).readUint16();
+ if (header_len > DEFAULT_HEADER_BUFLEN) {
+ isc_throw(SocketSessionError, "Too large header length: " <<
+ header_len);
+ }
+ impl_->header_buf_.clear();
+ impl_->header_buf_.resize(header_len);
+ const int cc_hdr = recv(impl_->fd_, &impl_->header_buf_[0], header_len,
+ MSG_WAITALL);
+ if (cc_hdr < header_len) {
+ readFail(cc_hdr, header_len);
+ }
+
+ InputBuffer ibuffer(&impl_->header_buf_[0], header_len);
+ try {
+ const int family = static_cast<int>(ibuffer.readUint32());
+ if (family != AF_INET && family != AF_INET6) {
+ isc_throw(SocketSessionError,
+ "Unsupported address family is passed: " << family);
+ }
+ const int type = static_cast<int>(ibuffer.readUint32());
+ const int protocol = static_cast<int>(ibuffer.readUint32());
+ const socklen_t local_end_len = ibuffer.readUint32();
+ const socklen_t endpoint_minlen = (family == AF_INET) ?
+ sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
+ if (local_end_len < endpoint_minlen ||
+ local_end_len > sizeof(impl_->ss_local_)) {
+ isc_throw(SocketSessionError, "Invalid local SA length: " <<
+ local_end_len);
+ }
+ ibuffer.readData(&impl_->ss_local_, local_end_len);
+ const socklen_t remote_end_len = ibuffer.readUint32();
+ if (remote_end_len < endpoint_minlen ||
+ remote_end_len > sizeof(impl_->ss_remote_)) {
+ isc_throw(SocketSessionError, "Invalid remote SA length: " <<
+ remote_end_len);
+ }
+ ibuffer.readData(&impl_->ss_remote_, remote_end_len);
+ if (family != impl_->sa_local_->sa_family ||
+ family != impl_->sa_remote_->sa_family) {
+ isc_throw(SocketSessionError, "SA family inconsistent: " <<
+ static_cast<int>(impl_->sa_local_->sa_family) << ", " <<
+ static_cast<int>(impl_->sa_remote_->sa_family) <<
+ " given, must be " << family);
+ }
+ const size_t data_len = ibuffer.readUint32();
+ if (data_len == 0 || data_len > MAX_DATASIZE) {
+ isc_throw(SocketSessionError,
+ "Invalid socket session data size: " << data_len <<
+ ", must be > 0 and <= " << MAX_DATASIZE);
+ }
+
+ impl_->data_buf_.clear();
+ impl_->data_buf_.resize(data_len);
+ const int cc_data = recv(impl_->fd_, &impl_->data_buf_[0], data_len,
+ MSG_WAITALL);
+ if (cc_data < data_len) {
+ readFail(cc_data, data_len);
+ }
+
+ return (SocketSession(passed_sock.release(), family, type, protocol,
+ impl_->sa_local_, impl_->sa_remote_,
+ &impl_->data_buf_[0], data_len));
+ } catch (const InvalidBufferPosition& ex) {
+ // We catch the case where the given header is too short and convert
+ // the exception to SocketSessionError.
+ isc_throw(SocketSessionError, "bogus socket session header: " <<
+ ex.what());
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/util/io/socketsession.h b/src/lib/util/io/socketsession.h
new file mode 100644
index 0000000..77f18a3
--- /dev/null
+++ b/src/lib/util/io/socketsession.h
@@ -0,0 +1,466 @@
+// 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 __SOCKETSESSION_H_
+#define __SOCKETSESSION_H_ 1
+
+#include <string>
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace util {
+namespace io {
+
+/// \page SocketSessionUtility Socket session utility
+///
+/// This utility defines a set of classes that support forwarding a
+/// "socket session" from one process to another. A socket session is a
+/// conceptual tuple of the following elements:
+/// - A network socket
+/// - The local and remote endpoints of a (IP) communication taking place on
+/// the socket. In practice an endpoint is a pair of an IP address and
+/// TCP or UDP port number.
+/// - Some amount of data sent from the remote endpoint and received on the
+/// socket. We call it (socket) session data in this documentation.
+///
+/// Note that this is a conceptual definition. Depending on the underlying
+/// implementation and/or the network protocol, some of the elements could be
+/// part of others; for example, if it's an established TCP connection,
+/// the local and remote endpoints would be able to be retrieved from the
+/// socket using the standard \c getsockname() and \c getpeername() system
+/// calls. But in this definition we separate these to be more generic.
+/// Also, as a matter of fact our intended usage includes non-connected UDP
+/// communications, in which case at least the remote endpoint should be
+/// provided separately from the socket.
+///
+/// In the actual implementation we represent a socket as a tuple of
+/// socket's file descriptor, address family (e.g. \c AF_INET6),
+/// socket type (e.g. \c SOCK_STREAM), and protocol (e.g. \c IPPROTO_TCP).
+/// The latter three are included in the representation of a socket in order
+/// to provide complete information of how the socket would be created
+/// by the \c socket(2) system call. More specifically in practice, these
+/// parameters could be used to construct a Python socket object from the
+/// file descriptor.
+///
+/// We use the standard \c sockaddr structure to represent endpoints.
+///
+/// Socket session data is an opaque memory region of an arbitrary length
+/// (possibly with some reasonable upper limit).
+///
+/// To forward a socket session between processes, we use connected UNIX
+/// domain sockets established between the processes. The file descriptor
+/// will be forwarded through the sockets as an ancillary data item of
+/// type \c SCM_RIGHTS. Other elements of the session will be transferred
+/// as normal data over the connection.
+///
+/// We provide three classes to help applications forward socket sessions:
+/// \c SocketSessionForwarder is the sender of the UNIX domain connection,
+/// while \c SocketSessionReceiver is the receiver (this interface assumes
+/// one direction of forwarding); \c SocketSession represents a single
+/// socket session.
+///
+/// \c SocketSessionForwarder and \c SocketSessionReceiver objects use a
+/// straightforward protocol to pass elements of socket sessions.
+/// Once the connection is established, the forwarder object first forwards
+/// the file descriptor with 1-byte dummy data. It then forwards a
+/// "(socket) session header", which contains all other elements of the session
+/// except the file descriptor (already forwarded) and session data.
+/// The wire format of the header is as follows:
+/// - The length of the header (16-bit unsigned integer)
+/// - Address family
+/// - Socket type
+/// - Protocol
+/// - Size of the local endpoint in bytes
+/// - Local endpoint (a copy of the memory image of the corresponding
+/// \c sockaddr)
+/// - Size of the remote endpoint in bytes
+/// - Remote endpoint (same as local endpoint)
+/// - Size of session data in bytes
+///
+/// The type of the fields is 32-bit unsigned integer unless explicitly
+/// noted, and all fields are formatted in the network byte order.
+///
+/// The socket session data immediately follows the session header.
+///
+/// Note that the fields do not necessarily be in the network byte order
+/// because they are expected to be exchanged on the same machine. Likewise,
+/// integer elements such as address family do not necessarily be represented
+/// as an fixed-size value (i.e., 32-bit). But fixed size fields are used
+/// in order to ensure maximum portability in such a (rare) case where the
+/// forwarder and the receiver are built with different compilers that have
+/// different definitions of \c int. Also, since \c sockaddr fields are
+/// generally formatted in the network byte order, other fields are defined
+/// so to be consistent.
+///
+/// One basic assumption in the API of this utility is socket sessions should
+/// be forwarded without blocking, thus eliminating the need for incremental
+/// read/write or blocking other important services such as responding to
+/// requests from the application's clients. This assumption should be held
+/// as long as both the forwarder and receiver have sufficient resources
+/// to handle the forwarding process since the communication is local.
+/// But a forward attempt could still block if the receiver is busy (or even
+/// hang up) and cannot keep up with the volume of incoming sessions.
+///
+/// So, in this implementation, the forwarder uses non blocking writes to
+/// forward sessions. If a write attempt could block, it immediately gives
+/// up the operation with an exception. The corresponding application is
+/// expected to catch it, close the connection, and perform any necessary
+/// recovery for that application (that would normally be re-establish the
+/// connection with a new receiver, possibly after confirming the receiving
+/// side is still alive). On the other hand, the receiver implementation
+/// assumes it's possible that it only receive incomplete elements of a
+/// session (such as in the case where the forwarder writes part of the
+/// entire session and gives up the connection). The receiver implementation
+/// throws an exception when it encounters an incomplete session. Like the
+/// case of the forwarder application, the receiver application is expected
+/// to catch it, close the connection, and perform any necessary recovery
+/// steps.
+///
+/// Note that the receiver implementation uses blocking read. So it's
+/// application's responsibility to ensure that there's at least some data
+/// in the connection when the receiver object is requested to receive a
+/// session (unless this operation can be blocking, e.g., by the use of
+/// a separate thread). Also, if the forwarder implementation or application
+/// is malicious or extremely buggy and intentionally sends partial session
+/// and keeps the connection, the receiver could block in receiving a session.
+/// In general, we assume the forwarder doesn't do intentional blocking
+/// as it's a local node and is generally a module of the same (BIND 10)
+/// system. The minimum requirement for the forwarder implementation (and
+/// application) is to make sure the connection is closed once it detects
+/// an error on it. Even a naive implementation that simply dies due to
+/// the exception will meet this requirement.
+
+/// An exception indicating general errors that takes place in the
+/// socket session related class objects.
+///
+/// In general the errors are unusual but possible failures such as unexpected
+/// connection reset, and suggest the application to close the connection and
+/// (if necessary) reestablish it.
+class SocketSessionError: public Exception {
+public:
+ SocketSessionError(const char *file, size_t line, const char *what):
+ isc::Exception(file, line, what) {}
+};
+
+/// The forwarder of socket sessions
+///
+/// An object of this class maintains a UNIX domain socket (normally expected
+/// to be connected to a \c SocketSessionReceiver object) and forwards
+/// socket sessions to the receiver.
+///
+/// See the description of \ref SocketSessionUtility for other details of how
+/// the session forwarding works.
+class SocketSessionForwarder : boost::noncopyable {
+public:
+ /// The constructor.
+ ///
+ /// It's constructed with path information of the intended receiver,
+ /// but does not immediately establish a connection to the receiver;
+ /// \c connectToReceiver() must be called to establish it. These are
+ /// separated so that an object of class can be initialized (possibly
+ /// as an attribute of a higher level application class object) without
+ /// knowing the receiver is ready for accepting new forwarders. The
+ /// separate connect interface allows the object to be reused when it
+ /// detects connection failure and tries to re-establish it after closing
+ /// the failed one.
+ ///
+ /// On construction, it also installs a signal filter for SIGPIPE to
+ /// ignore it. Since this class uses a stream-type connected UNIX domain
+ /// socket, if the receiver (abruptly) closes the connection a subsequent
+ /// write operation on the socket would trigger a SIGPIPE signal, which
+ /// kills the caller process by default. This behavior would be
+ /// undesirable in many cases, so this implementation always disables
+ /// the signal.
+ ///
+ /// This approach has some drawbacks, however; first, since signal handling
+ /// is process (or thread) wide, ignoring it may not what the application
+ /// wants. On the other hand, if the application changes how the signal is
+ /// handled after instantiating this class, the new behavior affects the
+ /// class operation. Secondly, even if ignoring the signal is the desired
+ /// operation, it's a waste to set the filter every time this class object
+ /// is constructed. It's sufficient to do it once. We still adopt this
+ /// behavior based on the observation that in most cases applications would
+ /// like to ignore SIGPIPE (or simply doesn't care about it) and that this
+ /// class is not instantiated so often (so the wasteful setting overhead
+ /// should be marginal). On the other hand, doing it every time is
+ /// beneficial if the application is threaded and different threads
+ /// create different forwarder objects (and if signals work per thread).
+ ///
+ /// \exception SocketSessionError \c unix_file is invalid as a path name
+ /// of a UNIX domain socket.
+ /// \exception Unexpected Error in setting a filter for SIGPIPE (see above)
+ /// \exception std::bad_alloc resource allocation failure
+ ///
+ /// \param unix_file Path name of the receiver.
+ explicit SocketSessionForwarder(const std::string& unix_file);
+
+ /// The destructor.
+ ///
+ /// If a connection has been established, it's automatically closed in
+ /// the destructor.
+ ~SocketSessionForwarder();
+
+ /// Establish a connection to the receiver.
+ ///
+ /// This method establishes a connection to the receiver at the path
+ /// given on construction. It makes the underlying UNIX domain socket
+ /// non blocking, so this method (or subsequent \c push() calls) does not
+ /// block.
+ ///
+ /// \exception BadValue The method is called while an already
+ /// established connection is still active.
+ /// \exception SocketSessionError A system error in socket operation.
+ void connectToReceiver();
+
+ /// Close the connection to the receiver.
+ ///
+ /// The connection must have been established by \c connectToReceiver().
+ /// As long as it's met this method is exception free.
+ ///
+ /// \exception BadValue The connection hasn't been established.
+ void close();
+
+ /// Forward a socket session to the receiver.
+ ///
+ /// This method takes a set of parameters that represent a single socket
+ /// session, renders them in the "wire" format according to the internal
+ /// protocol (see \ref SocketSessionUtility) and forwards them to
+ /// the receiver through the UNIX domain connection.
+ ///
+ /// The connection must have been established by \c connectToReceiver().
+ ///
+ /// For simplicity and for the convenience of detecting application
+ /// errors, this method imposes some restrictions on the parameters:
+ /// - Socket family must be either \c AF_INET or \c AF_INET6
+ /// - The address family (\c sa_family) member of the local and remote
+ /// end points must be equal to the \c family parameter
+ /// - Socket session data must not be empty (\c data_len must not be 0
+ /// and \c data must not be NULL)
+ /// - Data length must not exceed 65535
+ /// These are not architectural limitation, and might be loosened in
+ /// future versions as we see the need for flexibility.
+ ///
+ /// Since the underlying UNIX domain socket is non blocking
+ /// (see the description for the constructor), a call to this method
+ /// should either return immediately or result in exception (in case of
+ /// "would block").
+ ///
+ /// \exception BadValue The method is called before establishing a
+ /// connection or given parameters are invalid.
+ /// \exception SocketSessionError A system error in socket operation,
+ /// including the case where the write operation would block.
+ ///
+ /// \param sock The socket file descriptor
+ /// \param family The address family (such as AF_INET6) of the socket
+ /// \param type The socket type (such as SOCK_DGRAM) of the socket
+ /// \param protocol The transport protocol (such as IPPROTO_UDP) of the
+ /// socket
+ /// \param local_end The local end point of the session in the form of
+ /// \c sockaddr.
+ /// \param remote_end The remote end point of the session in the form of
+ /// \c sockaddr.
+ /// \param data A pointer to the beginning of the memory region for the
+ /// session data
+ /// \param data_len The size of the session data in bytes.
+ void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len);
+
+private:
+ struct ForwarderImpl;
+ ForwarderImpl* impl_;
+};
+
+/// Socket session object.
+///
+/// The \c SocketSession class provides a convenient encapsulation
+/// for the notion of a socket session. It's instantiated with straightforward
+/// parameters corresponding to a socket session, and provides read only
+/// accessors to the parameters to ensure data integrity.
+///
+/// In the initial design and implementation it's only used as a return type
+/// of \c SocketSessionReceiver::pop(), but it could also be used by
+/// the \c SocketSessionForwarder class or for other purposes.
+///
+/// It is assumed that the original owner of a \c SocketSession object
+/// (e.g. a class or a function that constructs it) is responsible for validity
+/// of the data passed to the object. See the description of
+/// \c SocketSessionReceiver::pop() for the specific case of that usage.
+class SocketSession {
+public:
+ /// The constructor.
+ ///
+ /// This is a trivial constructor, taking a straightforward representation
+ /// of session parameters and storing them internally to ensure integrity.
+ ///
+ /// As long as the given parameters are valid it never throws an exception.
+ ///
+ /// \exception BadValue Given parameters don't meet the requirement
+ /// (see the parameter descriptions).
+ ///
+ /// \param sock The socket file descriptor
+ /// \param family The address family (such as AF_INET6) of the socket
+ /// \param type The socket type (such as SOCK_DGRAM) of the socket
+ /// \param protocol The transport protocol (such as IPPROTO_UDP) of the
+ /// socket.
+ /// \param local_end The local end point of the session in the form of
+ /// \c sockaddr. Must not be NULL.
+ /// \param remote_end The remote end point of the session in the form of
+ /// \c sockaddr. Must not be NULL.
+ /// \param data A pointer to the beginning of the memory region for the
+ /// session data. Must not be NULL, and the subsequent \c data_len bytes
+ /// must be valid.
+ /// \param data_len The size of the session data in bytes. Must not be 0.
+ SocketSession(int sock, int family, int type, int protocol,
+ const sockaddr* local_end, const sockaddr* remote_end,
+ const void* data, size_t data_len);
+
+ /// Return the socket file descriptor.
+ int getSocket() const { return (sock_); }
+
+ /// Return the address family (such as AF_INET6) of the socket.
+ int getFamily() const { return (family_); }
+
+ /// Return the socket type (such as SOCK_DGRAM) of the socket.
+ int getType() const { return (type_); }
+
+ /// Return the transport protocol (such as IPPROTO_UDP) of the socket.
+ int getProtocol() const { return (protocol_); }
+
+ /// Return the local end point of the session in the form of \c sockaddr.
+ const sockaddr& getLocalEndpoint() const { return (*local_end_); }
+
+ /// Return the remote end point of the session in the form of \c sockaddr.
+ const sockaddr& getRemoteEndpoint() const { return (*remote_end_); }
+
+ /// Return a pointer to the beginning of the memory region for the session
+ /// data.
+ ///
+ /// In the current implementation it should never be NULL, and the region
+ /// of the size returned by \c getDataLength() is expected to be valid.
+ const void* getData() const { return (data_); }
+
+ /// Return the size of the session data in bytes.
+ ///
+ /// In the current implementation it should be always larger than 0.
+ size_t getDataLength() const { return (data_len_); }
+
+private:
+ const int sock_;
+ const int family_;
+ const int type_;
+ const int protocol_;
+ const sockaddr* local_end_;
+ const sockaddr* remote_end_;
+ const void* const data_;
+ const size_t data_len_;
+};
+
+/// The receiver of socket sessions
+///
+/// An object of this class holds a UNIX domain socket for an
+/// <em>established connection</em>, receives socket sessions from
+/// the remote forwarder, and provides the session to the application
+/// in the form of a \c SocketSession object.
+///
+/// Note that this class is instantiated with an already connected socket;
+/// it's not a listening socket that is accepting connection requests from
+/// forwarders. It's application's responsibility to create the listening
+/// socket, listen on it and accept connections. Once the connection is
+/// established, the application would construct a \c SocketSessionReceiver
+/// object with the socket for the newly established connection.
+/// This behavior is based on the design decision that the application should
+/// decide when it performs (possibly) blocking operations (see \ref
+/// SocketSessionUtility for more details).
+///
+/// See the description of \ref SocketSessionUtility for other details of how
+/// the session forwarding works.
+class SocketSessionReceiver : boost::noncopyable {
+public:
+ /// The constructor.
+ ///
+ /// \exception SocketSessionError Any error on an operation that is
+ /// performed on the given socket as part of initialization.
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \param fd A UNIX domain socket for an established connection with
+ /// a forwarder.
+ explicit SocketSessionReceiver(int fd);
+
+ /// The destructor.
+ ///
+ /// The destructor does \c not close the socket given on construction.
+ /// It's up to the application what to do with it (note that the
+ /// application would have to maintain the socket itself for detecting
+ /// the existence of a new socket session asynchronously).
+ ~SocketSessionReceiver();
+
+ /// Receive a socket session from the forwarder.
+ ///
+ /// This method receives wire-format data (see \ref SocketSessionUtility)
+ /// for a socket session on the UNIX domain socket, performs some
+ /// validation on the data, and returns the session information in the
+ /// form of a \c SocketSession object.
+ ///
+ /// The returned SocketSession object is valid only until the next time
+ /// this method is called or until the \c SocketSessionReceiver object is
+ /// destructed.
+ ///
+ /// The caller is responsible for closing the received socket (whose
+ /// file descriptor is accessible via \c SocketSession::getSocket()).
+ /// If the caller copies the returned \c SocketSession object, it's also
+ /// responsible for making sure the descriptor is closed at most once.
+ /// On the other hand, the caller is not responsible for freeing the
+ /// socket session data (accessible via \c SocketSession::getData());
+ /// the \c SocketSessionReceiver object will clean it up automatically.
+ ///
+ /// It ensures the following:
+ /// - The address family is either \c AF_INET or \c AF_INET6
+ /// - The address family (\c sa_family) member of the local and remote
+ /// end points must be equal to the \c family parameter
+ /// - The socket session data is not empty and does not exceed 65535
+ /// bytes.
+ /// If the validation fails or an unexpected system error happens
+ /// (including a connection close in the meddle of reception), it throws
+ /// an SocketSessionError exception. When this happens, it's very
+ /// unlikely that a subsequent call to this method succeeds, so in reality
+ /// the application is expected to destruct it and close the socket in
+ /// such a case.
+ ///
+ /// \exception SocketSessionError Invalid data is received or a system
+ /// error on socket operation happens.
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \return A \c SocketSession object corresponding to the extracted
+ /// socket session.
+ SocketSession pop();
+
+private:
+ struct ReceiverImpl;
+ ReceiverImpl* impl_;
+};
+
+}
+}
+}
+
+#endif // __SOCKETSESSION_H_
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index 47243f8..98d90d0 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -2,6 +2,7 @@ SUBDIRS = .
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_STATIC_LINK
@@ -26,6 +27,7 @@ run_unittests_SOURCES += lru_list_unittest.cc
run_unittests_SOURCES += qid_gen_unittest.cc
run_unittests_SOURCES += random_number_generator_unittest.cc
run_unittests_SOURCES += sha1_unittest.cc
+run_unittests_SOURCES += socketsession_unittest.cc
run_unittests_SOURCES += strutil_unittest.cc
run_unittests_SOURCES += time_utilities_unittest.cc
diff --git a/src/lib/util/tests/socketsession_unittest.cc b/src/lib/util/tests/socketsession_unittest.cc
new file mode 100644
index 0000000..eade68a
--- /dev/null
+++ b/src/lib/util/tests/socketsession_unittest.cc
@@ -0,0 +1,845 @@
+// 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <fcntl.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstring>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/io/fd_share.h>
+#include <util/io/socketsession.h>
+#include <util/io/sockaddr_util.h>
+
+using namespace std;
+using namespace isc;
+using boost::scoped_ptr;
+using namespace isc::util::io;
+using namespace isc::util::io::internal;
+
+namespace {
+
+const char* const TEST_UNIX_FILE = TEST_DATA_BUILDDIR "/test.unix";
+const char* const TEST_PORT = "53535";
+const char TEST_DATA[] = "BIND10 test";
+
+// A simple helper structure to automatically close test sockets on return
+// or exception in a RAII manner. non copyable to prevent duplicate close.
+struct ScopedSocket : boost::noncopyable {
+ ScopedSocket() : fd(-1) {}
+ ScopedSocket(int sock) : fd(sock) {}
+ ~ScopedSocket() {
+ closeSocket();
+ }
+ void reset(int sock) {
+ closeSocket();
+ fd = sock;
+ }
+ int fd;
+private:
+ void closeSocket() {
+ if (fd >= 0) {
+ close(fd);
+ }
+ }
+};
+
+// A helper function that makes a test socket non block so that a certain
+// kind of test failure (such as missing send) won't cause hangup.
+void
+setNonBlock(int s, bool on) {
+ int fcntl_flags = fcntl(s, F_GETFL, 0);
+ if (on) {
+ fcntl_flags |= O_NONBLOCK;
+ } else {
+ fcntl_flags &= ~O_NONBLOCK;
+ }
+ if (fcntl(s, F_SETFL, fcntl_flags) == -1) {
+ isc_throw(isc::Unexpected, "fcntl(O_NONBLOCK) failed: " <<
+ strerror(errno));
+ }
+}
+
+// A helper to impose some reasonable amount of wait on recv(from)
+// if possible. It returns an option flag to be set for the system call
+// (when necessary).
+int
+setRecvDelay(int s) {
+ const struct timeval timeo = { 10, 0 };
+ if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)) == -1) {
+ if (errno == ENOPROTOOPT) {
+ // Workaround for Solaris: see recursive_query_unittest
+ return (MSG_DONTWAIT);
+ } else {
+ isc_throw(isc::Unexpected, "set RCVTIMEO failed: " <<
+ strerror(errno));
+ }
+ }
+ return (0);
+}
+
+// A shortcut type that is convenient to be used for socket related
+// system calls, which generally require this pair
+typedef pair<const struct sockaddr*, socklen_t> SockAddrInfo;
+
+// A helper class to convert textual representation of IP address and port
+// to a pair of sockaddr and its length (in the form of a SockAddrInfo
+// pair). Its get method uses getaddrinfo(3) for the conversion and stores
+// the result in the addrinfo_list_ vector until the object is destructed.
+// The allocated resources will be automatically freed in an RAII manner.
+class SockAddrCreator {
+public:
+ ~SockAddrCreator() {
+ vector<struct addrinfo*>::const_iterator it;
+ for (it = addrinfo_list_.begin(); it != addrinfo_list_.end(); ++it) {
+ freeaddrinfo(*it);
+ }
+ }
+ SockAddrInfo get(const string& addr_str, const string& port_str) {
+ struct addrinfo hints, *res;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; // could be either DGRAM or STREAM here
+ const int error = getaddrinfo(addr_str.c_str(), port_str.c_str(),
+ &hints, &res);
+ if (error != 0) {
+ isc_throw(isc::Unexpected, "getaddrinfo failed for " <<
+ addr_str << ", " << port_str << ": " <<
+ gai_strerror(error));
+ }
+
+ // Technically, this is not entirely exception safe; if push_back
+ // throws, the resources allocated for 'res' will leak. We prefer
+ // brevity here and ignore the minor failure mode.
+ addrinfo_list_.push_back(res);
+
+ return (SockAddrInfo(res->ai_addr, res->ai_addrlen));
+ }
+private:
+ vector<struct addrinfo*> addrinfo_list_;
+};
+
+class ForwardTest : public ::testing::Test {
+protected:
+ ForwardTest() : listen_fd_(-1), forwarder_(TEST_UNIX_FILE),
+ large_text_(65535, 'a'),
+ test_un_len_(2 + strlen(TEST_UNIX_FILE))
+ {
+ unlink(TEST_UNIX_FILE);
+ test_un_.sun_family = AF_UNIX;
+ strncpy(test_un_.sun_path, TEST_UNIX_FILE, sizeof(test_un_.sun_path));
+#ifdef HAVE_SA_LEN
+ test_un_.sun_len = test_un_len_;
+#endif
+ }
+
+ ~ForwardTest() {
+ if (listen_fd_ != -1) {
+ close(listen_fd_);
+ }
+ unlink(TEST_UNIX_FILE);
+ }
+
+ // Start an internal "socket session server".
+ void startListen() {
+ if (listen_fd_ != -1) {
+ isc_throw(isc::Unexpected, "duplicate call to startListen()");
+ }
+ listen_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (listen_fd_ == -1) {
+ isc_throw(isc::Unexpected, "failed to create UNIX domain socket" <<
+ strerror(errno));
+ }
+ if (bind(listen_fd_, convertSockAddr(&test_un_), test_un_len_) == -1) {
+ isc_throw(isc::Unexpected, "failed to bind UNIX domain socket" <<
+ strerror(errno));
+ }
+ // 10 is an arbitrary choice, should be sufficient for a single test
+ if (listen(listen_fd_, 10) == -1) {
+ isc_throw(isc::Unexpected, "failed to listen on UNIX domain socket"
+ << strerror(errno));
+ }
+ }
+
+ int dummyConnect() const {
+ const int s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s == -1) {
+ isc_throw(isc::Unexpected,
+ "failed to create a test UNIX domain socket");
+ }
+ setNonBlock(s, true);
+ if (connect(s, convertSockAddr(&test_un_), sizeof(test_un_)) == -1) {
+ isc_throw(isc::Unexpected,
+ "failed to connect to the test SocketSessionForwarder");
+ }
+ return (s);
+ }
+
+ // Accept a new connection from a SocketSessionForwarder and return
+ // the socket FD of the new connection. This assumes startListen()
+ // has been called.
+ int acceptForwarder() {
+ setNonBlock(listen_fd_, true); // prevent the test from hanging up
+ struct sockaddr_un from;
+ socklen_t from_len = sizeof(from);
+ const int s = accept(listen_fd_, convertSockAddr(&from), &from_len);
+ if (s == -1) {
+ isc_throw(isc::Unexpected, "accept failed: " << strerror(errno));
+ }
+ // Make sure the socket is *blocking*. We may pass large data, through
+ // it, and apparently non blocking read could cause some unexpected
+ // partial read on some systems.
+ setNonBlock(s, false);
+ return (s);
+ }
+
+ // A convenient shortcut for the namespace-scope version of getSockAddr
+ SockAddrInfo getSockAddr(const string& addr_str, const string& port_str) {
+ return (addr_creator_.get(addr_str, port_str));
+ }
+
+ // A helper method that creates a specified type of socket that is
+ // supposed to be passed via a SocketSessionForwarder. It will bound
+ // to the specified address and port in sainfo. If do_listen is true
+ // and it's a TCP socket, it will also start listening to new connection
+ // requests.
+ int createSocket(int family, int type, int protocol,
+ const SockAddrInfo& sainfo, bool do_listen)
+ {
+ int s = socket(family, type, protocol);
+ if (s < 0) {
+ isc_throw(isc::Unexpected, "socket(2) failed: " <<
+ strerror(errno));
+ }
+ const int on = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ isc_throw(isc::Unexpected, "setsockopt(SO_REUSEADDR) failed: " <<
+ strerror(errno));
+ }
+ if (bind(s, sainfo.first, sainfo.second) < 0) {
+ close(s);
+ isc_throw(isc::Unexpected, "bind(2) failed: " <<
+ strerror(errno));
+ }
+ if (do_listen && protocol == IPPROTO_TCP) {
+ if (listen(s, 1) == -1) {
+ isc_throw(isc::Unexpected, "listen(2) failed: " <<
+ strerror(errno));
+ }
+ }
+ return (s);
+ }
+
+ // A helper method to push some (normally bogus) socket session header
+ // via a Unix domain socket that pretends to be a valid
+ // SocketSessionForwarder. It first opens the Unix domain socket,
+ // and connect to the test receiver server (startListen() is expected to
+ // be called beforehand), forwards a valid file descriptor ("stdin" is
+ // used for simplicity), the pushed a 2-byte header length field of the
+ // session header. The internal receiver_ pointer will be set to a
+ // newly created receiver object for the connection.
+ //
+ // \param hdrlen: The header length to be pushed. It may or may not be
+ // valid.
+ // \param hdrlen_len: The length of the actually pushed data as "header
+ // length". Normally it should be 2 (the default), but
+ // could be a bogus value for testing.
+ // \param push_fd: Whether to forward the FD. Normally it should be true,
+ // but can be false for testing.
+ void pushSessionHeader(uint16_t hdrlen,
+ size_t hdrlen_len = sizeof(uint16_t),
+ bool push_fd = true,
+ int fd = 0)
+ {
+ isc::util::OutputBuffer obuffer(0);
+ obuffer.clear();
+
+ dummy_forwarder_.reset(dummyConnect());
+ if (push_fd && send_fd(dummy_forwarder_.fd, fd) != 0) {
+ isc_throw(isc::Unexpected, "Failed to pass FD");
+ }
+ obuffer.writeUint16(hdrlen);
+ if (hdrlen_len > 0) {
+ if (send(dummy_forwarder_.fd, obuffer.getData(), hdrlen_len, 0) !=
+ hdrlen_len) {
+ isc_throw(isc::Unexpected,
+ "Failed to pass session header len");
+ }
+ }
+ accept_sock_.reset(acceptForwarder());
+ receiver_.reset(new SocketSessionReceiver(accept_sock_.fd));
+ }
+
+ // A helper method to push some (normally bogus) socket session via a
+ // Unix domain socket pretending to be a valid SocketSessionForwarder.
+ // It internally calls pushSessionHeader() for setup and pushing the
+ // header, and pass (often bogus) header data and session data based
+ // on the function parameters. The parameters are generally compatible
+ // to those for SocketSessionForwarder::push, but could be invalid for
+ // testing purposes. For session data, we use TEST_DATA and its size
+ // by default for simplicity, but the size can be tweaked for testing.
+ void pushSession(int family, int type, int protocol, socklen_t local_len,
+ const sockaddr& local, socklen_t remote_len,
+ const sockaddr& remote,
+ size_t data_len = sizeof(TEST_DATA))
+ {
+ isc::util::OutputBuffer obuffer(0);
+ obuffer.writeUint32(static_cast<uint32_t>(family));
+ obuffer.writeUint32(static_cast<uint32_t>(type));
+ obuffer.writeUint32(static_cast<uint32_t>(protocol));
+ obuffer.writeUint32(static_cast<uint32_t>(local_len));
+ obuffer.writeData(&local, min(local_len, getSALength(local)));
+ obuffer.writeUint32(static_cast<uint32_t>(remote_len));
+ obuffer.writeData(&remote, min(remote_len, getSALength(remote)));
+ obuffer.writeUint32(static_cast<uint32_t>(data_len));
+ pushSessionHeader(obuffer.getLength());
+ if (send(dummy_forwarder_.fd, obuffer.getData(), obuffer.getLength(),
+ 0) != obuffer.getLength()) {
+ isc_throw(isc::Unexpected, "Failed to pass session header");
+ }
+ if (send(dummy_forwarder_.fd, TEST_DATA, sizeof(TEST_DATA), 0) !=
+ sizeof(TEST_DATA)) {
+ isc_throw(isc::Unexpected, "Failed to pass session data");
+ }
+ }
+
+ // See below
+ void checkPushAndPop(int family, int type, int protocoal,
+ const SockAddrInfo& local,
+ const SockAddrInfo& remote, const void* const data,
+ size_t data_len, bool new_connection);
+
+protected:
+ int listen_fd_;
+ SocketSessionForwarder forwarder_;
+ ScopedSocket dummy_forwarder_; // forwarder "like" socket to pass bad data
+ scoped_ptr<SocketSessionReceiver> receiver_;
+ ScopedSocket accept_sock_;
+ const string large_text_;
+
+private:
+ struct sockaddr_un test_un_;
+ const socklen_t test_un_len_;
+ SockAddrCreator addr_creator_;
+};
+
+TEST_F(ForwardTest, construct) {
+ // On construction the existence of the file doesn't matter.
+ SocketSessionForwarder("some_file");
+
+ // But too long a path should be rejected
+ struct sockaddr_un s; // can't be const; some compiler complains
+ EXPECT_THROW(SocketSessionForwarder(string(sizeof(s.sun_path), 'x')),
+ SocketSessionError);
+ // If it's one byte shorter it should be okay
+ SocketSessionForwarder(string(sizeof(s.sun_path) - 1, 'x'));
+}
+
+TEST_F(ForwardTest, connect) {
+ // File doesn't exist (we assume the file "no_such_file" doesn't exist)
+ SocketSessionForwarder forwarder("no_such_file");
+ EXPECT_THROW(forwarder.connectToReceiver(), SocketSessionError);
+ // The socket should be closed internally, so close() should result in
+ // error.
+ EXPECT_THROW(forwarder.close(), BadValue);
+
+ // Set up the receiver and connect. It should succeed.
+ SocketSessionForwarder forwarder2(TEST_UNIX_FILE);
+ startListen();
+ forwarder2.connectToReceiver();
+ // And it can be closed successfully.
+ forwarder2.close();
+ // Duplicate close should fail
+ EXPECT_THROW(forwarder2.close(), BadValue);
+ // Once closed, reconnect is okay.
+ forwarder2.connectToReceiver();
+ forwarder2.close();
+
+ // Duplicate connect should be rejected
+ forwarder2.connectToReceiver();
+ EXPECT_THROW(forwarder2.connectToReceiver(), BadValue);
+
+ // Connect then destroy. Should be internally closed, but unfortunately
+ // it's not easy to test it directly. We only check no disruption happens.
+ SocketSessionForwarder* forwarderp =
+ new SocketSessionForwarder(TEST_UNIX_FILE);
+ forwarderp->connectToReceiver();
+ delete forwarderp;
+}
+
+TEST_F(ForwardTest, close) {
+ // can't close before connect
+ EXPECT_THROW(SocketSessionForwarder(TEST_UNIX_FILE).close(), BadValue);
+}
+
+void
+checkSockAddrs(const sockaddr& expected, const sockaddr& actual) {
+ char hbuf_expected[NI_MAXHOST], sbuf_expected[NI_MAXSERV],
+ hbuf_actual[NI_MAXHOST], sbuf_actual[NI_MAXSERV];
+ EXPECT_EQ(0, getnameinfo(&expected, getSALength(expected),
+ hbuf_expected, sizeof(hbuf_expected),
+ sbuf_expected, sizeof(sbuf_expected),
+ NI_NUMERICHOST | NI_NUMERICSERV));
+ EXPECT_EQ(0, getnameinfo(&actual, getSALength(actual),
+ hbuf_actual, sizeof(hbuf_actual),
+ sbuf_actual, sizeof(sbuf_actual),
+ NI_NUMERICHOST | NI_NUMERICSERV));
+ EXPECT_EQ(string(hbuf_expected), string(hbuf_actual));
+ EXPECT_EQ(string(sbuf_expected), string(sbuf_actual));
+}
+
+// This is a commonly used test case that confirms normal behavior of
+// session passing. It first creates a "local" socket (which is supposed
+// to act as a "server") bound to the 'local' parameter. It then forwards
+// the descriptor of the FD of the local socket along with given data.
+// Next, it creates an Receiver object to receive the forwarded FD itself,
+// receives the FD, and sends test data from the received FD. The
+// test finally checks if it can receive the test data from the local socket
+// at the Forwarder side. In the case of TCP it's a bit complicated because
+// it first needs to establish a new connection, but essentially the test
+// scenario is the same. See the diagram below for more details.
+//
+// UDP:
+// Forwarder Receiver
+// sock -- (pass) --> passed_sock
+// (check) <-------- send TEST_DATA
+//
+// TCP:
+// Forwarder Receiver
+// server_sock---(pass)--->passed_sock
+// ^ |
+// |(connect) |
+// client_sock |
+// (check)<---------send TEST_DATA
+void
+ForwardTest::checkPushAndPop(int family, int type, int protocol,
+ const SockAddrInfo& local,
+ const SockAddrInfo& remote,
+ const void* const data,
+ size_t data_len, bool new_connection)
+{
+ // Create an original socket to be passed
+ const ScopedSocket sock(createSocket(family, type, protocol, local, true));
+ int fwd_fd = sock.fd; // default FD to be forwarded
+ ScopedSocket client_sock; // for TCP test we need a separate "client"..
+ ScopedSocket server_sock; // ..and a separate socket for the connection
+ if (protocol == IPPROTO_TCP) {
+ // Use unspecified port for the "client" to avoid bind(2) failure
+ const SockAddrInfo client_addr = getSockAddr(family == AF_INET6 ?
+ "::1" : "127.0.0.1", "0");
+ client_sock.reset(createSocket(family, type, protocol, client_addr,
+ false));
+ setNonBlock(client_sock.fd, true);
+ // This connect would "fail" due to EINPROGRESS. Ignore it for now.
+ connect(client_sock.fd, local.first, local.second);
+ sockaddr_storage ss;
+ socklen_t salen = sizeof(ss);
+ server_sock.reset(accept(sock.fd, convertSockAddr(&ss), &salen));
+ if (server_sock.fd == -1) {
+ isc_throw(isc::Unexpected, "internal accept failed: " <<
+ strerror(errno));
+ }
+ fwd_fd = server_sock.fd;
+ }
+
+ // If a new connection is required, start the "server", have the
+ // internal forwarder connect to it, and then internally accept it.
+ if (new_connection) {
+ startListen();
+ forwarder_.connectToReceiver();
+ accept_sock_.reset(acceptForwarder());
+ }
+
+ // Then push one socket session via the forwarder.
+ forwarder_.push(fwd_fd, family, type, protocol, *local.first,
+ *remote.first, data, data_len);
+
+ // Pop the socket session we just pushed from a local receiver, and
+ // check the content. Since we do blocking read on the receiver's socket,
+ // we set up an alarm to prevent hangup in case there's a bug that really
+ // makes the blocking happen.
+ SocketSessionReceiver receiver(accept_sock_.fd);
+ alarm(1); // set up 1-sec timer, an arbitrary choice.
+ const SocketSession sock_session = receiver.pop();
+ alarm(0); // then cancel it.
+ const ScopedSocket passed_sock(sock_session.getSocket());
+ EXPECT_LE(0, passed_sock.fd);
+ // The passed FD should be different from the original FD
+ EXPECT_NE(fwd_fd, passed_sock.fd);
+ EXPECT_EQ(family, sock_session.getFamily());
+ EXPECT_EQ(type, sock_session.getType());
+ EXPECT_EQ(protocol, sock_session.getProtocol());
+ checkSockAddrs(*local.first, sock_session.getLocalEndpoint());
+ checkSockAddrs(*remote.first, sock_session.getRemoteEndpoint());
+ ASSERT_EQ(data_len, sock_session.getDataLength());
+ EXPECT_EQ(0, memcmp(data, sock_session.getData(), data_len));
+
+ // Check if the passed FD is usable by sending some data from it.
+ setNonBlock(passed_sock.fd, false);
+ if (protocol == IPPROTO_UDP) {
+ EXPECT_EQ(sizeof(TEST_DATA),
+ sendto(passed_sock.fd, TEST_DATA, sizeof(TEST_DATA), 0,
+ convertSockAddr(local.first), local.second));
+ } else {
+ server_sock.reset(-1);
+ EXPECT_EQ(sizeof(TEST_DATA),
+ send(passed_sock.fd, TEST_DATA, sizeof(TEST_DATA), 0));
+ }
+ // We don't use non blocking read below as it doesn't seem to be always
+ // reliable. Instead we impose some reasonably large upper time limit of
+ // blocking (normally it shouldn't even block at all; the limit is to
+ // force the test to stop even if there's some bug and recv fails).
+ char recvbuf[sizeof(TEST_DATA)];
+ sockaddr_storage ss;
+ socklen_t sa_len = sizeof(ss);
+ if (protocol == IPPROTO_UDP) {
+ EXPECT_EQ(sizeof(recvbuf),
+ recvfrom(fwd_fd, recvbuf, sizeof(recvbuf),
+ setRecvDelay(fwd_fd), convertSockAddr(&ss),
+ &sa_len));
+ } else {
+ setNonBlock(client_sock.fd, false);
+ EXPECT_EQ(sizeof(recvbuf),
+ recv(client_sock.fd, recvbuf, sizeof(recvbuf),
+ setRecvDelay(client_sock.fd)));
+ }
+ EXPECT_EQ(string(TEST_DATA), string(recvbuf));
+}
+
+TEST_F(ForwardTest, pushAndPop) {
+ // Pass a UDP/IPv6 session.
+ const SockAddrInfo sai_local6(getSockAddr("::1", TEST_PORT));
+ const SockAddrInfo sai_remote6(getSockAddr("2001:db8::1", "5300"));
+ {
+ SCOPED_TRACE("Passing UDP/IPv6 session");
+ checkPushAndPop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, sai_local6,
+ sai_remote6, TEST_DATA, sizeof(TEST_DATA), true);
+ }
+ // Pass a TCP/IPv6 session.
+ {
+ SCOPED_TRACE("Passing TCP/IPv6 session");
+ checkPushAndPop(AF_INET6, SOCK_STREAM, IPPROTO_TCP, sai_local6,
+ sai_remote6, TEST_DATA, sizeof(TEST_DATA), false);
+ }
+
+ // Pass a UDP/IPv4 session. This reuses the same pair of forwarder and
+ // receiver, which should be usable for multiple attempts of passing,
+ // regardless of family of the passed session
+ const SockAddrInfo sai_local4(getSockAddr("127.0.0.1", TEST_PORT));
+ const SockAddrInfo sai_remote4(getSockAddr("192.0.2.2", "5300"));
+ {
+ SCOPED_TRACE("Passing UDP/IPv4 session");
+ checkPushAndPop(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local4,
+ sai_remote4, TEST_DATA, sizeof(TEST_DATA), false);
+ }
+ // Pass a TCP/IPv4 session.
+ {
+ SCOPED_TRACE("Passing TCP/IPv4 session");
+ checkPushAndPop(AF_INET, SOCK_STREAM, IPPROTO_TCP, sai_local4,
+ sai_remote4, TEST_DATA, sizeof(TEST_DATA), false);
+ }
+
+ // Also try large data
+ {
+ SCOPED_TRACE("Passing UDP/IPv6 session with large data");
+ checkPushAndPop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, sai_local6,
+ sai_remote6, large_text_.c_str(), large_text_.length(),
+ false);
+ }
+ {
+ SCOPED_TRACE("Passing TCP/IPv6 session with large data");
+ checkPushAndPop(AF_INET6, SOCK_STREAM, IPPROTO_TCP, sai_local6,
+ sai_remote6, large_text_.c_str(), large_text_.length(),
+ false);
+ }
+ {
+ SCOPED_TRACE("Passing UDP/IPv4 session with large data");
+ checkPushAndPop(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local4,
+ sai_remote4, large_text_.c_str(), large_text_.length(),
+ false);
+ }
+ {
+ SCOPED_TRACE("Passing TCP/IPv4 session with large data");
+ checkPushAndPop(AF_INET, SOCK_STREAM, IPPROTO_TCP, sai_local4,
+ sai_remote4, large_text_.c_str(), large_text_.length(),
+ false);
+ }
+}
+
+TEST_F(ForwardTest, badPush) {
+ // push before connect
+ EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ *getSockAddr("192.0.2.1", "53").first,
+ *getSockAddr("192.0.2.2", "53").first,
+ TEST_DATA, sizeof(TEST_DATA)),
+ BadValue);
+
+ // Now connect the forwarder for the rest of tests
+ startListen();
+ forwarder_.connectToReceiver();
+
+ // Invalid address family
+ struct sockaddr sockaddr_unspec;
+ sockaddr_unspec.sa_family = AF_UNSPEC;
+ EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ sockaddr_unspec,
+ *getSockAddr("192.0.2.2", "53").first,
+ TEST_DATA, sizeof(TEST_DATA)),
+ BadValue);
+ EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ *getSockAddr("192.0.2.2", "53").first,
+ sockaddr_unspec, TEST_DATA,
+ sizeof(TEST_DATA)),
+ BadValue);
+
+ // Inconsistent address family
+ EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ *getSockAddr("2001:db8::1", "53").first,
+ *getSockAddr("192.0.2.2", "53").first,
+ TEST_DATA, sizeof(TEST_DATA)),
+ BadValue);
+ EXPECT_THROW(forwarder_.push(1, AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+ *getSockAddr("2001:db8::1", "53").first,
+ *getSockAddr("192.0.2.2", "53").first,
+ TEST_DATA, sizeof(TEST_DATA)),
+ BadValue);
+
+ // Empty data: we reject them at least for now
+ EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ *getSockAddr("192.0.2.1", "53").first,
+ *getSockAddr("192.0.2.2", "53").first,
+ TEST_DATA, 0),
+ BadValue);
+ EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ *getSockAddr("192.0.2.1", "53").first,
+ *getSockAddr("192.0.2.2", "53").first,
+ NULL, sizeof(TEST_DATA)),
+ BadValue);
+
+ // Too big data: we reject them at least for now
+ EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ *getSockAddr("192.0.2.1", "53").first,
+ *getSockAddr("192.0.2.2", "53").first,
+ string(65536, 'd').c_str(), 65536),
+ BadValue);
+
+ // Close the receiver before push. It will result in SIGPIPE (should be
+ // ignored) and EPIPE, which will be converted to SocketSessionError.
+ const int receiver_fd = acceptForwarder();
+ close(receiver_fd);
+ EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ *getSockAddr("192.0.2.1", "53").first,
+ *getSockAddr("192.0.2.2", "53").first,
+ TEST_DATA, sizeof(TEST_DATA)),
+ SocketSessionError);
+}
+
+// A subroutine for pushTooFast, continuously pushing socket sessions
+// with full-size DNS messages (65535 bytes) without receiving them.
+// the push attempts will eventually fill the socket send buffer and trigger
+// an exception. Unfortunately exactly how many we can forward depends on
+// the internal system implementation; it should be close to 3, because
+// in our current implementation it sets the send buffer to a size that
+// is sufficiently large to hold 2 sessions (but not much larger than that),
+// but (for example) Linux internally doubles the specified upper limit.
+// Experimentally we know 10 is enough to produce a reliable result, but
+// if it turns out to be not the case, we should do it a bit harder, e.g.,
+// by probing the actual buffer size by getsockopt(SO_SNDBUF).
+void
+multiPush(SocketSessionForwarder& forwarder, const struct sockaddr& sa,
+ const void* data, size_t data_len)
+{
+ for (int i = 0; i < 10; ++i) {
+ forwarder.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP, sa, sa,
+ data, data_len);
+ }
+}
+
+TEST_F(ForwardTest, pushTooFast) {
+ // Emulate the situation where the forwarder is pushing sessions too fast.
+ // It should eventually fail without blocking.
+ startListen();
+ forwarder_.connectToReceiver();
+ EXPECT_THROW(multiPush(forwarder_, *getSockAddr("192.0.2.1", "53").first,
+ large_text_.c_str(), large_text_.length()),
+ SocketSessionError);
+}
+
+TEST_F(ForwardTest, badPop) {
+ startListen();
+
+ // Close the forwarder socket before pop() without sending anything.
+ pushSessionHeader(0, 0, false);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Pretending to be a forwarder but don't actually pass FD.
+ pushSessionHeader(0, 1, false);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Pass a valid FD (stdin), but provide short data for the hdrlen
+ pushSessionHeader(0, 1);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Pass a valid FD, but provides too large hdrlen
+ pushSessionHeader(0xffff);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Don't provide full header
+ pushSessionHeader(sizeof(uint32_t));
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Pushed header is too short
+ const uint8_t dummy_data = 0;
+ pushSessionHeader(1);
+ send(dummy_forwarder_.fd, &dummy_data, 1, 0);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // socket addresses commonly used below (the values don't matter).
+ const SockAddrInfo sai_local(getSockAddr("192.0.2.1", "53535"));
+ const SockAddrInfo sai_remote(getSockAddr("192.0.2.2", "53536"));
+ const SockAddrInfo sai6(getSockAddr("2001:db8::1", "53537"));
+
+ // Pass invalid address family (AF_UNSPEC)
+ pushSession(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, sai_local.second,
+ *sai_local.first, sai_remote.second, *sai_remote.first);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Pass inconsistent address family for local
+ pushSession(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai6.second,
+ *sai6.first, sai_remote.second, *sai_remote.first);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Same for remote
+ pushSession(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local.second,
+ *sai_local.first, sai6.second, *sai6.first);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Pass too big sa length for local
+ pushSession(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ sizeof(struct sockaddr_storage) + 1, *sai_local.first,
+ sai_remote.second, *sai_remote.first);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Same for remote
+ pushSession(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local.second,
+ *sai_local.first, sizeof(struct sockaddr_storage) + 1,
+ *sai_remote.first);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Pass too small sa length for local
+ pushSession(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ sizeof(struct sockaddr_in) - 1, *sai_local.first,
+ sai_remote.second, *sai_remote.first);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Same for remote
+ pushSession(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+ sai6.second, *sai6.first, sizeof(struct sockaddr_in6) - 1,
+ *sai6.first);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Data length is too large
+ pushSession(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local.second,
+ *sai_local.first, sai_remote.second,
+ *sai_remote.first, 65536);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Empty data
+ pushSession(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local.second,
+ *sai_local.first, sai_remote.second,
+ *sai_remote.first, 0);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Not full data are passed
+ pushSession(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local.second,
+ *sai_local.first, sai_remote.second,
+ *sai_remote.first, sizeof(TEST_DATA) + 1);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+
+ // Check the forwarded FD is closed on failure
+ ScopedSocket sock(createSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ getSockAddr("127.0.0.1", TEST_PORT),
+ false));
+ pushSessionHeader(0, 1, true, sock.fd);
+ dummy_forwarder_.reset(-1);
+ EXPECT_THROW(receiver_->pop(), SocketSessionError);
+ // Close the original socket
+ sock.reset(-1);
+ // The passed one should have been closed, too, so we should be able
+ // to bind a new socket to the same port.
+ ScopedSocket(createSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ getSockAddr("127.0.0.1", TEST_PORT),
+ false));
+}
+
+TEST(SocketSessionTest, badValue) {
+ // normal cases are confirmed in ForwardTest. We only check some
+ // abnormal cases here.
+
+ SockAddrCreator addr_creator;
+
+ EXPECT_THROW(SocketSession(42, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL,
+ addr_creator.get("192.0.2.1", "53").first,
+ TEST_DATA, sizeof(TEST_DATA)),
+ BadValue);
+ EXPECT_THROW(SocketSession(42, AF_INET6, SOCK_STREAM, IPPROTO_TCP,
+ addr_creator.get("2001:db8::1", "53").first,
+ NULL, TEST_DATA , sizeof(TEST_DATA)), BadValue);
+ EXPECT_THROW(SocketSession(42, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ addr_creator.get("192.0.2.1", "53").first,
+ addr_creator.get("192.0.2.2", "5300").first,
+ TEST_DATA, 0), BadValue);
+ EXPECT_THROW(SocketSession(42, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ addr_creator.get("192.0.2.1", "53").first,
+ addr_creator.get("192.0.2.2", "5300").first,
+ NULL, sizeof(TEST_DATA)), BadValue);
+}
+}
More information about the bind10-changes
mailing list