summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJINMEI Tatuya <jinmei@isc.org>2011-12-15 00:49:25 +0100
committerJINMEI Tatuya <jinmei@isc.org>2011-12-15 00:49:25 +0100
commited5fa95326dc7233e4e289acda77d7a1fb562f89 (patch)
treedf2ef98e6b2beeba1bba17311f5869cf7ae946d5
parent[1452] impose upper limit on the data length for SocketSessionForwarder::push, (diff)
downloadkea-ed5fa95326dc7233e4e289acda77d7a1fb562f89.tar.xz
kea-ed5fa95326dc7233e4e289acda77d7a1fb562f89.zip
[1452] overall documentation update
-rw-r--r--doc/Doxyfile2
-rw-r--r--src/lib/util/io/socketsession.cc34
-rw-r--r--src/lib/util/io/socketsession.h369
-rw-r--r--src/lib/util/tests/socketsession_unittest.cc95
4 files changed, 437 insertions, 63 deletions
diff --git a/doc/Doxyfile b/doc/Doxyfile
index ee5aaf83bc..c9c5c5a0be 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/lib/util/io/socketsession.cc b/src/lib/util/io/socketsession.cc
index 8dbdf78e15..f1566ad394 100644
--- a/src/lib/util/io/socketsession.cc
+++ b/src/lib/util/io/socketsession.cc
@@ -73,7 +73,8 @@ struct SocketSessionForwarder::ForwarderImpl {
SocketSessionForwarder::SocketSessionForwarder(const std::string& unix_file) :
impl_(NULL)
{
- // We need to filter SIGPIPE for subsequent push(). See the description.
+ // 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));
}
@@ -108,7 +109,7 @@ SocketSessionForwarder::~SocketSessionForwarder() {
void
SocketSessionForwarder::connectToReceptor() {
if (impl_->fd_ != -1) {
- isc_throw(SocketSessionError, "Duplicate connect to UNIX domain "
+ isc_throw(BadValue, "Duplicate connect to UNIX domain "
"endpoint " << impl_->sock_un_.sun_path);
}
@@ -146,41 +147,40 @@ SocketSessionForwarder::connectToReceptor() {
void
SocketSessionForwarder::close() {
if (impl_->fd_ == -1) {
- isc_throw(SocketSessionError, "Attempt of close before connect");
+ isc_throw(BadValue, "Attempt of close before connect");
}
::close(impl_->fd_);
impl_->fd_ = -1;
}
void
-SocketSessionForwarder::push(int sock, int family, int sock_type, int protocol,
+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(SocketSessionError, "Attempt of push before connect");
+ 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(SocketSessionError, "Invalid address family: must be "
+ 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(SocketSessionError, "Inconsistent address family: must be "
+ 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(SocketSessionError,
- "Data for a socket session must not be empty");
+ isc_throw(BadValue, "Data for a socket session must not be empty");
}
if (data_len > MAX_DATASIZE) {
- isc_throw(SocketSessionError, "Invalid socket session data size: " <<
+ isc_throw(BadValue, "Invalid socket session data size: " <<
data_len << ", must not exceed " << MAX_DATASIZE);
}
@@ -194,7 +194,7 @@ SocketSessionForwarder::push(int sock, int family, int sock_type, int protocol,
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>(sock_type));
+ 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)));
@@ -229,10 +229,10 @@ SocketSessionForwarder::push(int sock, int family, int sock_type, int protocol,
SocketSession::SocketSession(int sock, int family, int type, int protocol,
const sockaddr* local_end,
const sockaddr* remote_end,
- size_t data_len, const void* data) :
+ const void* data, size_t data_len) :
sock_(sock), family_(family), type_(type), protocol_(protocol),
local_end_(local_end), remote_end_(remote_end),
- data_len_(data_len), data_(data)
+ data_(data), data_len_(data_len)
{
if (local_end == NULL || remote_end == NULL) {
isc_throw(BadValue, "sockaddr must be non NULL for SocketSession");
@@ -264,8 +264,8 @@ struct SocketSessionReceptor::ReceptorImpl {
struct sockaddr_storage ss_remote_; // placeholder
struct sockaddr* const sa_remote_;
- vector<char> header_buf_;
- vector<char> data_buf_;
+ vector<uint8_t> header_buf_;
+ vector<uint8_t> data_buf_;
};
SocketSessionReceptor::SocketSessionReceptor(int fd) :
@@ -363,8 +363,8 @@ SocketSessionReceptor::pop() {
}
return (SocketSession(passed_fd, family, type, protocol,
- impl_->sa_local_, impl_->sa_remote_, data_len,
- &impl_->data_buf_[0]));
+ 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.
diff --git a/src/lib/util/io/socketsession.h b/src/lib/util/io/socketsession.h
index 11aeb6cd33..c33a6406f2 100644
--- a/src/lib/util/io/socketsession.h
+++ b/src/lib/util/io/socketsession.h
@@ -25,26 +25,258 @@ 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:
- // Note about SIGPIPE. Assuming this class is not often instantiated
- // (so the overhead of signal setting should be marginal) and could also be
- // instantiated by multiple threads, it always set the filter.
+ /// 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();
- void push(int sock, int family, int sock_type, int protocol,
+ /// 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);
@@ -54,18 +286,78 @@ private:
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,
- size_t data_len, const void* data);
+ 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:
@@ -75,14 +367,79 @@ private:
const int protocol_;
const sockaddr* local_end_;
const sockaddr* remote_end_;
- const size_t data_len_;
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:
diff --git a/src/lib/util/tests/socketsession_unittest.cc b/src/lib/util/tests/socketsession_unittest.cc
index 717b0cdd22..2e6a6ec633 100644
--- a/src/lib/util/tests/socketsession_unittest.cc
+++ b/src/lib/util/tests/socketsession_unittest.cc
@@ -147,11 +147,11 @@ private:
vector<struct addrinfo*> addrinfo_list_;
};
-class ForwarderTest : public ::testing::Test {
+class ForwardTest : public ::testing::Test {
protected:
- ForwarderTest() : listen_fd_(-1), forwarder_(TEST_UNIX_FILE),
- large_text_(65535, 'a'),
- test_un_len_(2 + strlen(TEST_UNIX_FILE))
+ 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;
@@ -161,7 +161,7 @@ protected:
#endif
}
- ~ForwarderTest() {
+ ~ForwardTest() {
if (listen_fd_ != -1) {
close(listen_fd_);
}
@@ -287,12 +287,24 @@ protected:
}
obuffer.writeUint16(hdrlen);
if (hdrlen_len > 0) {
- send(dummy_forwarder_.fd, obuffer.getData(), 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,
@@ -308,8 +320,14 @@ protected:
obuffer.writeData(&remote, getSALength(remote));
obuffer.writeUint32(static_cast<uint32_t>(data_len));
pushSessionHeader(obuffer.getLength());
- send(dummy_forwarder_.fd, obuffer.getData(), obuffer.getLength(), 0);
- send(dummy_forwarder_.fd, TEST_DATA, sizeof(TEST_DATA), 0);
+ 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
@@ -332,7 +350,7 @@ private:
SockAddrCreator addr_creator_;
};
-TEST_F(ForwarderTest, construct) {
+TEST_F(ForwardTest, construct) {
// On construction the existence of the file doesn't matter.
SocketSessionForwarder("some_file");
@@ -344,13 +362,13 @@ TEST_F(ForwarderTest, construct) {
SocketSessionForwarder(string(sizeof(s.sun_path) - 1, 'x'));
}
-TEST_F(ForwarderTest, connect) {
+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(), SocketSessionError);
+ EXPECT_THROW(forwarder.close(), BadValue);
// Set up the receptor and connect. It should succeed.
SocketSessionForwarder forwarder2(TEST_UNIX_FILE);
@@ -359,14 +377,14 @@ TEST_F(ForwarderTest, connect) {
// And it can be closed successfully.
forwarder2.close();
// Duplicate close should fail
- EXPECT_THROW(forwarder2.close(), SocketSessionError);
+ 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(), SocketSessionError);
+ 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.
@@ -376,10 +394,9 @@ TEST_F(ForwarderTest, connect) {
delete forwarderp;
}
-TEST_F(ForwarderTest, close) {
+TEST_F(ForwardTest, close) {
// can't close before connect
- EXPECT_THROW(SocketSessionForwarder(TEST_UNIX_FILE).close(),
- SocketSessionError);
+ EXPECT_THROW(SocketSessionForwarder(TEST_UNIX_FILE).close(), BadValue);
}
void
@@ -422,11 +439,11 @@ checkSockAddrs(const sockaddr& expected, const sockaddr& actual) {
// client_sock |
// (check)<---------send TEST_DATA
void
-ForwarderTest::checkPushAndPop(int family, int type, int protocol,
- const SockAddrInfo& local,
- const SockAddrInfo& remote,
- const void* const data,
- size_t data_len, bool new_connection)
+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));
@@ -516,7 +533,7 @@ ForwarderTest::checkPushAndPop(int family, int type, int protocol,
EXPECT_EQ(string(TEST_DATA), string(recvbuf));
}
-TEST_F(ForwarderTest, pushAndPop) {
+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"));
@@ -576,13 +593,13 @@ TEST_F(ForwarderTest, pushAndPop) {
}
}
-TEST_F(ForwarderTest, badPush) {
+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)),
- SocketSessionError);
+ BadValue);
// Now connect the forwarder for the rest of tests
startListen();
@@ -595,43 +612,43 @@ TEST_F(ForwarderTest, badPush) {
sockaddr_unspec,
*getSockAddr("192.0.2.2", "53").first,
TEST_DATA, sizeof(TEST_DATA)),
- SocketSessionError);
+ 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)),
- SocketSessionError);
+ 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)),
- SocketSessionError);
+ 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)),
- SocketSessionError);
+ 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),
- SocketSessionError);
+ 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)),
- SocketSessionError);
+ 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),
- SocketSessionError);
+ BadValue);
// Close the receptor before push. It will result in SIGPIPE (should be
// ignored) and EPIPE, which will be converted to SocketSessionError.
@@ -658,7 +675,7 @@ multiPush(SocketSessionForwarder& forwarder, const struct sockaddr& sa,
}
}
-TEST_F(ForwarderTest, pushTooFast) {
+TEST_F(ForwardTest, pushTooFast) {
// Emulate the situation where the forwarder is pushing sessions too fast.
// It should eventually fail without blocking.
startListen();
@@ -668,7 +685,7 @@ TEST_F(ForwarderTest, pushTooFast) {
SocketSessionError);
}
-TEST_F(ForwarderTest, badPop) {
+TEST_F(ForwardTest, badPop) {
startListen();
// Close the forwarder socket before pop() without sending anything.
@@ -761,26 +778,26 @@ TEST_F(ForwarderTest, badPop) {
EXPECT_THROW(receptor_->pop(), SocketSessionError);
}
-TEST(SocketSession, badValue) {
- // normal cases are confirmed in ForwarderTest. We only check some
+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,
- sizeof(TEST_DATA), TEST_DATA),
+ 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, sizeof(TEST_DATA), TEST_DATA), BadValue);
+ 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,
- 0, TEST_DATA), BadValue);
+ 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,
- sizeof(TEST_DATA), NULL), BadValue);
+ NULL, sizeof(TEST_DATA)), BadValue);
}
}