diff options
author | JINMEI Tatuya <jinmei@isc.org> | 2011-12-15 00:49:25 +0100 |
---|---|---|
committer | JINMEI Tatuya <jinmei@isc.org> | 2011-12-15 00:49:25 +0100 |
commit | ed5fa95326dc7233e4e289acda77d7a1fb562f89 (patch) | |
tree | df2ef98e6b2beeba1bba17311f5869cf7ae946d5 | |
parent | [1452] impose upper limit on the data length for SocketSessionForwarder::push, (diff) | |
download | kea-ed5fa95326dc7233e4e289acda77d7a1fb562f89.tar.xz kea-ed5fa95326dc7233e4e289acda77d7a1fb562f89.zip |
[1452] overall documentation update
-rw-r--r-- | doc/Doxyfile | 2 | ||||
-rw-r--r-- | src/lib/util/io/socketsession.cc | 34 | ||||
-rw-r--r-- | src/lib/util/io/socketsession.h | 369 | ||||
-rw-r--r-- | src/lib/util/tests/socketsession_unittest.cc | 95 |
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); } } |