mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
[1452b] Merge branch 'trac1452' into trac1452b
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -1,8 +1,11 @@
|
||||
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
|
||||
|
||||
CLEANFILES = *.gcno *.gcda
|
||||
|
||||
|
@@ -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,12 +88,16 @@ 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;
|
||||
@@ -131,7 +136,7 @@ send_fd(const int sock, const int fd) {
|
||||
|
||||
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
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
66
src/lib/util/io/sockaddr_util.h
Normal file
66
src/lib/util/io/sockaddr_util.h
Normal file
@@ -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 SA_TYPE>
|
||||
const struct sockaddr*
|
||||
convertSockAddr(const SA_TYPE* sa) {
|
||||
const void* p = sa;
|
||||
return (static_cast<const struct sockaddr*>(p));
|
||||
}
|
||||
|
||||
template <typename SA_TYPE>
|
||||
struct sockaddr*
|
||||
convertSockAddr(SA_TYPE* sa) {
|
||||
void* p = sa;
|
||||
return (static_cast<struct sockaddr*>(p));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __SOCKADDR_UTIL_H_
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
393
src/lib/util/io/socketsession.cc
Normal file
393
src/lib/util/io/socketsession.cc
Normal file
@@ -0,0 +1,393 @@
|
||||
// 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 <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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 = 2 + 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 receptor.
|
||||
// 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 receptor. 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;
|
||||
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_ = 2 + unix_file.length();
|
||||
#ifdef HAVE_SA_LEN
|
||||
impl.sock_un_.sun_len = sock_un_len_;
|
||||
#endif
|
||||
impl.fd_ = -1;
|
||||
|
||||
impl_ = new ForwarderImpl;
|
||||
*impl_ = impl;
|
||||
}
|
||||
|
||||
SocketSessionForwarder::~SocketSessionForwarder() {
|
||||
if (impl_->fd_ != -1) {
|
||||
close();
|
||||
}
|
||||
delete impl_;
|
||||
}
|
||||
|
||||
void
|
||||
SocketSessionForwarder::connectToReceptor() {
|
||||
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));
|
||||
}
|
||||
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 SocketSessionReceptor::ReceptorImpl {
|
||||
ReceptorImpl(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_;
|
||||
};
|
||||
|
||||
SocketSessionReceptor::SocketSessionReceptor(int fd) :
|
||||
impl_(new ReceptorImpl(fd))
|
||||
{
|
||||
}
|
||||
|
||||
SocketSessionReceptor::~SocketSessionReceptor() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
SocketSession
|
||||
SocketSessionReceptor::pop() {
|
||||
const int passed_fd = recv_fd(impl_->fd_);
|
||||
if (passed_fd == FD_SYSTEM_ERROR) {
|
||||
isc_throw(SocketSessionError, "Receiving a forwarded FD failed: " <<
|
||||
strerror(errno));
|
||||
} else if (passed_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();
|
||||
if (local_end_len > sizeof(impl_->ss_local_)) {
|
||||
isc_throw(SocketSessionError, "Local SA length too large: " <<
|
||||
local_end_len);
|
||||
}
|
||||
ibuffer.readData(&impl_->ss_local_, local_end_len);
|
||||
const socklen_t remote_end_len = ibuffer.readUint32();
|
||||
if (remote_end_len > sizeof(impl_->ss_remote_)) {
|
||||
isc_throw(SocketSessionError, "Remote SA length too large: " <<
|
||||
remote_end_len);
|
||||
}
|
||||
ibuffer.readData(&impl_->ss_remote_, remote_end_len);
|
||||
if (family != impl_->sa_local_->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_fd, 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
458
src/lib/util/io/socketsession.h
Normal file
458
src/lib/util/io/socketsession.h
Normal file
@@ -0,0 +1,458 @@
|
||||
// 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 SocketSessionReceptor is the receiver (this interface assumes
|
||||
/// one direction of forwarding); \c SocketSession represents a single
|
||||
/// socket session.
|
||||
///
|
||||
/// \c SocketSessionForwarder and \c SocketSessionReceptor 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 receptor 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 receptor have sufficient resources
|
||||
/// to handle the forwarding process since the communication is local.
|
||||
/// But a forward attempt could still block if the receptor 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 receptor, possibly after confirming the receiving
|
||||
/// side is still alive). On the other hand, the receptor 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 receptor implementation
|
||||
/// throws an exception when it encounters an incomplete session. Like the
|
||||
/// case of the forwarder application, the receptor application is expected
|
||||
/// to catch it, close the connection, and perform any necessary recovery
|
||||
/// steps.
|
||||
///
|
||||
/// Note that the receptor implementation uses blocking read. So it's
|
||||
/// application's responsibility to ensure that there's at least some data
|
||||
/// in the connection when the receptor 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 receptor 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 SocketSessionReceptor object) and forwards
|
||||
/// socket sessions to the receptor.
|
||||
///
|
||||
/// 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 receptor,
|
||||
/// but does not immediately establish a connection to the receptor;
|
||||
/// \c connectToReceptor() 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 receptor 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 receptor (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 receptor.
|
||||
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 receptor.
|
||||
///
|
||||
/// This method establishes a connection to the receptor 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 connectToReceptor();
|
||||
|
||||
/// Close the connection to the receptor.
|
||||
///
|
||||
/// The connection must have been established by \c connectToReceptor().
|
||||
/// 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 receptor.
|
||||
///
|
||||
/// 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 receptor through the UNIX domain connection.
|
||||
///
|
||||
/// The connection must have been established by \c connectToReceptor().
|
||||
///
|
||||
/// 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 SocketSessionReceptor::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 SocketSessionReceptor::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 SocketSessionReceptor
|
||||
/// 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 SocketSessionReceptor : 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 SocketSessionReceptor(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).
|
||||
~SocketSessionReceptor();
|
||||
|
||||
/// 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 SocketSessionReceptor object is
|
||||
/// destructed.
|
||||
///
|
||||
/// 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 ReceptorImpl;
|
||||
ReceptorImpl* impl_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __SOCKETSESSION_H_
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
@@ -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
|
||||
|
||||
|
803
src/lib/util/tests/socketsession_unittest.cc
Normal file
803
src/lib/util/tests/socketsession_unittest.cc
Normal file
@@ -0,0 +1,803 @@
|
||||
// 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 <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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 receptor 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 receptor_ pointer will be set to a
|
||||
// newly created receptor 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)
|
||||
{
|
||||
isc::util::OutputBuffer obuffer(0);
|
||||
obuffer.clear();
|
||||
|
||||
dummy_forwarder_.reset(dummyConnect());
|
||||
if (push_fd && send_fd(dummy_forwarder_.fd, 0) != 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());
|
||||
receptor_.reset(new SocketSessionReceptor(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, getSALength(local));
|
||||
obuffer.writeUint32(static_cast<uint32_t>(remote_len));
|
||||
obuffer.writeData(&remote, 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<SocketSessionReceptor> receptor_;
|
||||
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.connectToReceptor(), SocketSessionError);
|
||||
// The socket should be closed internally, so close() should result in
|
||||
// error.
|
||||
EXPECT_THROW(forwarder.close(), BadValue);
|
||||
|
||||
// Set up the receptor and connect. It should succeed.
|
||||
SocketSessionForwarder forwarder2(TEST_UNIX_FILE);
|
||||
startListen();
|
||||
forwarder2.connectToReceptor();
|
||||
// And it can be closed successfully.
|
||||
forwarder2.close();
|
||||
// Duplicate close should fail
|
||||
EXPECT_THROW(forwarder2.close(), BadValue);
|
||||
// Once closed, reconnect is okay.
|
||||
forwarder2.connectToReceptor();
|
||||
forwarder2.close();
|
||||
|
||||
// Duplicate connect should be rejected
|
||||
forwarder2.connectToReceptor();
|
||||
EXPECT_THROW(forwarder2.connectToReceptor(), 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->connectToReceptor();
|
||||
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 Receptor 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 Receptor
|
||||
// sock -- (pass) --> passed_sock
|
||||
// (check) <-------- send TEST_DATA
|
||||
//
|
||||
// TCP:
|
||||
// Forwarder Receptor
|
||||
// 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_.connectToReceptor();
|
||||
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 receptor, and
|
||||
// check the content. Since we do blocking read on the receptor's socket,
|
||||
// we set up an alarm to prevent hangup in case there's a bug that really
|
||||
// makes the blocking happen.
|
||||
SocketSessionReceptor receptor(accept_sock_.fd);
|
||||
alarm(1); // set up 1-sec timer, an arbitrary choice.
|
||||
const SocketSession sock_session = receptor.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
|
||||
// receptor, 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_.connectToReceptor();
|
||||
|
||||
// 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 receptor before push. It will result in SIGPIPE (should be
|
||||
// ignored) and EPIPE, which will be converted to SocketSessionError.
|
||||
const int receptor_fd = acceptForwarder();
|
||||
close(receptor_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. Due to the fixed configuration of the
|
||||
// send buffer size, we shouldn't be able to forward 3 full-size DNS messages
|
||||
// without receiving them. Exactly how many we can forward depends on the
|
||||
// internal system implementation, so we'll at least confirm we can't do for 3.
|
||||
void
|
||||
multiPush(SocketSessionForwarder& forwarder, const struct sockaddr& sa,
|
||||
const void* data, size_t data_len)
|
||||
{
|
||||
for (int i = 0; i < 3; ++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_.connectToReceptor();
|
||||
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(receptor_->pop(), SocketSessionError);
|
||||
|
||||
// Pretending to be a forwarder but don't actually pass FD.
|
||||
pushSessionHeader(0, 1, false);
|
||||
dummy_forwarder_.reset(-1);
|
||||
EXPECT_THROW(receptor_->pop(), SocketSessionError);
|
||||
|
||||
// Pass a valid FD (stdin), but provide short data for the hdrlen
|
||||
pushSessionHeader(0, 1);
|
||||
dummy_forwarder_.reset(-1);
|
||||
EXPECT_THROW(receptor_->pop(), SocketSessionError);
|
||||
|
||||
// Pass a valid FD, but provides too large hdrlen
|
||||
pushSessionHeader(0xffff);
|
||||
dummy_forwarder_.reset(-1);
|
||||
EXPECT_THROW(receptor_->pop(), SocketSessionError);
|
||||
|
||||
// Don't provide full header
|
||||
pushSessionHeader(sizeof(uint32_t));
|
||||
dummy_forwarder_.reset(-1);
|
||||
EXPECT_THROW(receptor_->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(receptor_->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(receptor_->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(receptor_->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);
|
||||
|
||||
// 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(receptor_->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(receptor_->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(receptor_->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(receptor_->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(receptor_->pop(), SocketSessionError);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user