Browse Source

[1452] overall documentation update

JINMEI Tatuya 13 years ago
parent
commit
ed5fa95326

+ 1 - 1
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/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log \
     ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../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
     ../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
 # This tag can be used to specify the character encoding of the source files

+ 17 - 17
src/lib/util/io/socketsession.cc

@@ -73,7 +73,8 @@ struct SocketSessionForwarder::ForwarderImpl {
 SocketSessionForwarder::SocketSessionForwarder(const std::string& unix_file) :
 SocketSessionForwarder::SocketSessionForwarder(const std::string& unix_file) :
     impl_(NULL)
     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) {
     if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
         isc_throw(Unexpected, "Failed to filter SIGPIPE: " << strerror(errno));
         isc_throw(Unexpected, "Failed to filter SIGPIPE: " << strerror(errno));
     }
     }
@@ -108,7 +109,7 @@ SocketSessionForwarder::~SocketSessionForwarder() {
 void
 void
 SocketSessionForwarder::connectToReceptor() {
 SocketSessionForwarder::connectToReceptor() {
     if (impl_->fd_ != -1) {
     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);
                   "endpoint " << impl_->sock_un_.sun_path);
     }
     }
 
 
@@ -146,41 +147,40 @@ SocketSessionForwarder::connectToReceptor() {
 void
 void
 SocketSessionForwarder::close() {
 SocketSessionForwarder::close() {
     if (impl_->fd_ == -1) {
     if (impl_->fd_ == -1) {
-        isc_throw(SocketSessionError, "Attempt of close before connect");
+        isc_throw(BadValue, "Attempt of close before connect");
     }
     }
     ::close(impl_->fd_);
     ::close(impl_->fd_);
     impl_->fd_ = -1;
     impl_->fd_ = -1;
 }
 }
 
 
 void
 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& local_end,
                              const struct sockaddr& remote_end,
                              const struct sockaddr& remote_end,
                              const void* data, size_t data_len)
                              const void* data, size_t data_len)
 {
 {
     if (impl_->fd_ == -1) {
     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) ||
     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))
         (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; " <<
                   "AF_INET or AF_INET6; " <<
                   static_cast<int>(local_end.sa_family) << ", " <<
                   static_cast<int>(local_end.sa_family) << ", " <<
                   static_cast<int>(remote_end.sa_family) << " given");
                   static_cast<int>(remote_end.sa_family) << " given");
     }
     }
     if (family != local_end.sa_family || family != remote_end.sa_family) {
     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>(family) << "; "
                   << static_cast<int>(local_end.sa_family) << ", "
                   << static_cast<int>(local_end.sa_family) << ", "
                   << static_cast<int>(remote_end.sa_family) << " given");
                   << static_cast<int>(remote_end.sa_family) << " given");
     }
     }
     if (data_len == 0 || data == NULL) {
     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) {
     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);
                   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));
     impl_->buf_.skip(sizeof(uint16_t));
     // Socket properties: family, type, protocol
     // Socket properties: family, type, protocol
     impl_->buf_.writeUint32(static_cast<uint32_t>(family));
     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));
     impl_->buf_.writeUint32(static_cast<uint32_t>(protocol));
     // Local endpoint
     // Local endpoint
     impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(local_end)));
     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,
 SocketSession::SocketSession(int sock, int family, int type, int protocol,
                              const sockaddr* local_end,
                              const sockaddr* local_end,
                              const sockaddr* remote_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),
     sock_(sock), family_(family), type_(type), protocol_(protocol),
     local_end_(local_end), remote_end_(remote_end),
     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) {
     if (local_end == NULL || remote_end == NULL) {
         isc_throw(BadValue, "sockaddr must be non NULL for SocketSession");
         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_storage ss_remote_; // placeholder
     struct sockaddr* const sa_remote_;
     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) :
 SocketSessionReceptor::SocketSessionReceptor(int fd) :
@@ -363,8 +363,8 @@ SocketSessionReceptor::pop() {
         }
         }
 
 
         return (SocketSession(passed_fd, family, type, protocol,
         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) {
     } catch (const InvalidBufferPosition& ex) {
         // We catch the case where the given header is too short and convert
         // We catch the case where the given header is too short and convert
         // the exception to SocketSessionError.
         // the exception to SocketSessionError.

+ 363 - 6
src/lib/util/io/socketsession.h

@@ -25,26 +25,258 @@ namespace isc {
 namespace util {
 namespace util {
 namespace io {
 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 {
 class SocketSessionError: public Exception {
 public:
 public:
     SocketSessionError(const char *file, size_t line, const char *what):
     SocketSessionError(const char *file, size_t line, const char *what):
         isc::Exception(file, line, 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 {
 class SocketSessionForwarder : boost::noncopyable {
 public:
 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);
     explicit SocketSessionForwarder(const std::string& unix_file);
 
 
+    /// The destructor.
+    ///
+    /// If a connection has been established, it's automatically closed in
+    /// the destructor.
     ~SocketSessionForwarder();
     ~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();
     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 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& local_end,
               const struct sockaddr& remote_end,
               const struct sockaddr& remote_end,
               const void* data, size_t data_len);
               const void* data, size_t data_len);
@@ -54,18 +286,78 @@ private:
     ForwarderImpl* impl_;
     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 {
 class SocketSession {
 public:
 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,
     SocketSession(int sock, int family, int type, int protocol,
                   const sockaddr* local_end, const sockaddr* remote_end,
                   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_); }
     int getSocket() const { return (sock_); }
+
+    /// Return the address family (such as AF_INET6) of the socket.
     int getFamily() const { return (family_); }
     int getFamily() const { return (family_); }
+
+    /// Return the socket type (such as SOCK_DGRAM) of the socket.
     int getType() const { return (type_); }
     int getType() const { return (type_); }
+
+    /// Return the transport protocol (such as IPPROTO_UDP) of the socket.
     int getProtocol() const { return (protocol_); }
     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_); }
     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_); }
     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_); }
     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_); }
     size_t getDataLength() const { return (data_len_); }
 
 
 private:
 private:
@@ -75,14 +367,79 @@ private:
     const int protocol_;
     const int protocol_;
     const sockaddr* local_end_;
     const sockaddr* local_end_;
     const sockaddr* remote_end_;
     const sockaddr* remote_end_;
-    const size_t data_len_;
     const void* const data_;
     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 {
 class SocketSessionReceptor : boost::noncopyable {
 public:
 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);
     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();
     ~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();
     SocketSession pop();
 
 
 private:
 private:

+ 56 - 39
src/lib/util/tests/socketsession_unittest.cc

@@ -147,11 +147,11 @@ private:
     vector<struct addrinfo*> addrinfo_list_;
     vector<struct addrinfo*> addrinfo_list_;
 };
 };
 
 
-class ForwarderTest : public ::testing::Test {
+class ForwardTest : public ::testing::Test {
 protected:
 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);
         unlink(TEST_UNIX_FILE);
         test_un_.sun_family = AF_UNIX;
         test_un_.sun_family = AF_UNIX;
@@ -161,7 +161,7 @@ protected:
 #endif
 #endif
     }
     }
 
 
-    ~ForwarderTest() {
+    ~ForwardTest() {
         if (listen_fd_ != -1) {
         if (listen_fd_ != -1) {
             close(listen_fd_);
             close(listen_fd_);
         }
         }
@@ -287,12 +287,24 @@ protected:
         }
         }
         obuffer.writeUint16(hdrlen);
         obuffer.writeUint16(hdrlen);
         if (hdrlen_len > 0) {
         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());
         accept_sock_.reset(acceptForwarder());
         receptor_.reset(new SocketSessionReceptor(accept_sock_.fd));
         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,
     void pushSession(int family, int type, int protocol, socklen_t local_len,
                      const sockaddr& local, socklen_t remote_len,
                      const sockaddr& local, socklen_t remote_len,
                      const sockaddr& remote,
                      const sockaddr& remote,
@@ -308,8 +320,14 @@ protected:
         obuffer.writeData(&remote, getSALength(remote));
         obuffer.writeData(&remote, getSALength(remote));
         obuffer.writeUint32(static_cast<uint32_t>(data_len));
         obuffer.writeUint32(static_cast<uint32_t>(data_len));
         pushSessionHeader(obuffer.getLength());
         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
     // See below
@@ -332,7 +350,7 @@ private:
     SockAddrCreator addr_creator_;
     SockAddrCreator addr_creator_;
 };
 };
 
 
-TEST_F(ForwarderTest, construct) {
+TEST_F(ForwardTest, construct) {
     // On construction the existence of the file doesn't matter.
     // On construction the existence of the file doesn't matter.
     SocketSessionForwarder("some_file");
     SocketSessionForwarder("some_file");
 
 
@@ -344,13 +362,13 @@ TEST_F(ForwarderTest, construct) {
     SocketSessionForwarder(string(sizeof(s.sun_path) - 1, 'x'));
     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)
     // File doesn't exist (we assume the file "no_such_file" doesn't exist)
     SocketSessionForwarder forwarder("no_such_file");
     SocketSessionForwarder forwarder("no_such_file");
     EXPECT_THROW(forwarder.connectToReceptor(), SocketSessionError);
     EXPECT_THROW(forwarder.connectToReceptor(), SocketSessionError);
     // The socket should be closed internally, so close() should result in
     // The socket should be closed internally, so close() should result in
     // error.
     // error.
-    EXPECT_THROW(forwarder.close(), SocketSessionError);
+    EXPECT_THROW(forwarder.close(), BadValue);
 
 
     // Set up the receptor and connect.  It should succeed.
     // Set up the receptor and connect.  It should succeed.
     SocketSessionForwarder forwarder2(TEST_UNIX_FILE);
     SocketSessionForwarder forwarder2(TEST_UNIX_FILE);
@@ -359,14 +377,14 @@ TEST_F(ForwarderTest, connect) {
     // And it can be closed successfully.
     // And it can be closed successfully.
     forwarder2.close();
     forwarder2.close();
     // Duplicate close should fail
     // Duplicate close should fail
-    EXPECT_THROW(forwarder2.close(), SocketSessionError);
+    EXPECT_THROW(forwarder2.close(), BadValue);
     // Once closed, reconnect is okay.
     // Once closed, reconnect is okay.
     forwarder2.connectToReceptor();
     forwarder2.connectToReceptor();
     forwarder2.close();
     forwarder2.close();
 
 
     // Duplicate connect should be rejected
     // Duplicate connect should be rejected
     forwarder2.connectToReceptor();
     forwarder2.connectToReceptor();
-    EXPECT_THROW(forwarder2.connectToReceptor(), SocketSessionError);
+    EXPECT_THROW(forwarder2.connectToReceptor(), BadValue);
 
 
     // Connect then destroy.  Should be internally closed, but unfortunately
     // Connect then destroy.  Should be internally closed, but unfortunately
     // it's not easy to test it directly.  We only check no disruption happens.
     // it's not easy to test it directly.  We only check no disruption happens.
@@ -376,10 +394,9 @@ TEST_F(ForwarderTest, connect) {
     delete forwarderp;
     delete forwarderp;
 }
 }
 
 
-TEST_F(ForwarderTest, close) {
+TEST_F(ForwardTest, close) {
     // can't close before connect
     // can't close before connect
-    EXPECT_THROW(SocketSessionForwarder(TEST_UNIX_FILE).close(),
-                 SocketSessionError);
+    EXPECT_THROW(SocketSessionForwarder(TEST_UNIX_FILE).close(), BadValue);
 }
 }
 
 
 void
 void
@@ -422,11 +439,11 @@ checkSockAddrs(const sockaddr& expected, const sockaddr& actual) {
 //   client_sock                |
 //   client_sock                |
 //      (check)<---------send TEST_DATA
 //      (check)<---------send TEST_DATA
 void
 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
     // Create an original socket to be passed
     const ScopedSocket sock(createSocket(family, type, protocol, local, true));
     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));
     EXPECT_EQ(string(TEST_DATA), string(recvbuf));
 }
 }
 
 
-TEST_F(ForwarderTest, pushAndPop) {
+TEST_F(ForwardTest, pushAndPop) {
     // Pass a UDP/IPv6 session.
     // Pass a UDP/IPv6 session.
     const SockAddrInfo sai_local6(getSockAddr("::1", TEST_PORT));
     const SockAddrInfo sai_local6(getSockAddr("::1", TEST_PORT));
     const SockAddrInfo sai_remote6(getSockAddr("2001:db8::1", "5300"));
     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
     // push before connect
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
                                  *getSockAddr("192.0.2.1", "53").first,
                                  *getSockAddr("192.0.2.1", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  TEST_DATA, sizeof(TEST_DATA)),
                                  TEST_DATA, sizeof(TEST_DATA)),
-                 SocketSessionError);
+                 BadValue);
 
 
     // Now connect the forwarder for the rest of tests
     // Now connect the forwarder for the rest of tests
     startListen();
     startListen();
@@ -595,43 +612,43 @@ TEST_F(ForwarderTest, badPush) {
                                  sockaddr_unspec,
                                  sockaddr_unspec,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  TEST_DATA, sizeof(TEST_DATA)),
                                  TEST_DATA, sizeof(TEST_DATA)),
-                 SocketSessionError);
+                 BadValue);
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  sockaddr_unspec, TEST_DATA,
                                  sockaddr_unspec, TEST_DATA,
                                  sizeof(TEST_DATA)),
                                  sizeof(TEST_DATA)),
-                 SocketSessionError);
+                 BadValue);
 
 
     // Inconsistent address family
     // Inconsistent address family
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
                                  *getSockAddr("2001:db8::1", "53").first,
                                  *getSockAddr("2001:db8::1", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  TEST_DATA, sizeof(TEST_DATA)),
                                  TEST_DATA, sizeof(TEST_DATA)),
-                 SocketSessionError);
+                 BadValue);
     EXPECT_THROW(forwarder_.push(1, AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
     EXPECT_THROW(forwarder_.push(1, AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
                                  *getSockAddr("2001:db8::1", "53").first,
                                  *getSockAddr("2001:db8::1", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  TEST_DATA, sizeof(TEST_DATA)),
                                  TEST_DATA, sizeof(TEST_DATA)),
-                 SocketSessionError);
+                 BadValue);
 
 
     // Empty data: we reject them at least for now
     // Empty data: we reject them at least for now
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
                                  *getSockAddr("192.0.2.1", "53").first,
                                  *getSockAddr("192.0.2.1", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  TEST_DATA, 0),
                                  TEST_DATA, 0),
-                 SocketSessionError);
+                 BadValue);
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
                                  *getSockAddr("192.0.2.1", "53").first,
                                  *getSockAddr("192.0.2.1", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  NULL, sizeof(TEST_DATA)),
                                  NULL, sizeof(TEST_DATA)),
-                 SocketSessionError);
+                 BadValue);
 
 
     // Too big data: we reject them at least for now
     // Too big data: we reject them at least for now
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
     EXPECT_THROW(forwarder_.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
                                  *getSockAddr("192.0.2.1", "53").first,
                                  *getSockAddr("192.0.2.1", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  *getSockAddr("192.0.2.2", "53").first,
                                  string(65536, 'd').c_str(), 65536),
                                  string(65536, 'd').c_str(), 65536),
-                 SocketSessionError);
+                 BadValue);
 
 
     // Close the receptor before push.  It will result in SIGPIPE (should be
     // Close the receptor before push.  It will result in SIGPIPE (should be
     // ignored) and EPIPE, which will be converted to SocketSessionError.
     // 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.
     // Emulate the situation where the forwarder is pushing sessions too fast.
     // It should eventually fail without blocking.
     // It should eventually fail without blocking.
     startListen();
     startListen();
@@ -668,7 +685,7 @@ TEST_F(ForwarderTest, pushTooFast) {
                  SocketSessionError);
                  SocketSessionError);
 }
 }
 
 
-TEST_F(ForwarderTest, badPop) {
+TEST_F(ForwardTest, badPop) {
     startListen();
     startListen();
 
 
     // Close the forwarder socket before pop() without sending anything.
     // Close the forwarder socket before pop() without sending anything.
@@ -761,26 +778,26 @@ TEST_F(ForwarderTest, badPop) {
     EXPECT_THROW(receptor_->pop(), SocketSessionError);
     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.
     // abnormal cases here.
 
 
     SockAddrCreator addr_creator;
     SockAddrCreator addr_creator;
 
 
     EXPECT_THROW(SocketSession(42, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL,
     EXPECT_THROW(SocketSession(42, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL,
                                addr_creator.get("192.0.2.1", "53").first,
                                addr_creator.get("192.0.2.1", "53").first,
-                               sizeof(TEST_DATA), TEST_DATA),
+                               TEST_DATA, sizeof(TEST_DATA)),
                  BadValue);
                  BadValue);
     EXPECT_THROW(SocketSession(42, AF_INET6, SOCK_STREAM, IPPROTO_TCP,
     EXPECT_THROW(SocketSession(42, AF_INET6, SOCK_STREAM, IPPROTO_TCP,
                                addr_creator.get("2001:db8::1", "53").first,
                                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,
     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.1", "53").first,
                                addr_creator.get("192.0.2.2", "5300").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,
     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.1", "53").first,
                                addr_creator.get("192.0.2.2", "5300").first,
                                addr_creator.get("192.0.2.2", "5300").first,
-                               sizeof(TEST_DATA), NULL), BadValue);
+                               NULL, sizeof(TEST_DATA)), BadValue);
 }
 }
 }
 }