// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifndef SOCKETSESSION_H #define SOCKETSESSION_H 1 #include #include #include #include namespace isc { namespace util { namespace io { /// \page SocketSessionUtility Socket session utility /// /// \note This class is currently unused. Once we get to the implementation /// of the remote parts of the management API, we will evaluate whether /// this code is useful or not and either start using it or remove it. /// /// This utility defines a set of classes that support forwarding a /// "socket session" from one process to another. A socket session is a /// conceptual tuple of the following elements: /// - A network socket /// - The local and remote endpoints of a (IP) communication taking place on /// the socket. In practice an endpoint is a pair of an IP address and /// TCP or UDP port number. /// - Some amount of data sent from the remote endpoint and received on the /// socket. We call it (socket) session data in this documentation. /// /// Note that this is a conceptual definition. Depending on the underlying /// implementation and/or the network protocol, some of the elements could be /// part of others; for example, if it's an established TCP connection, /// the local and remote endpoints would be able to be retrieved from the /// socket using the standard \c getsockname() and \c getpeername() system /// calls. But in this definition we separate these to be more generic. /// Also, as a matter of fact our intended usage includes non-connected UDP /// communications, in which case at least the remote endpoint should be /// provided separately from the socket. /// /// In the actual implementation we represent a socket as a tuple of /// socket's file descriptor, address family (e.g. \c AF_INET6), /// socket type (e.g. \c SOCK_STREAM), and protocol (e.g. \c IPPROTO_TCP). /// The latter three are included in the representation of a socket in order /// to provide complete information of how the socket would be created /// by the \c socket(2) system call. More specifically in practice, these /// parameters could be used to construct a Python socket object from the /// file descriptor. /// /// We use the standard \c sockaddr structure to represent endpoints. /// /// Socket session data is an opaque memory region of an arbitrary length /// (possibly with some reasonable upper limit). /// /// To forward a socket session between processes, we use connected UNIX /// domain sockets established between the processes. The file descriptor /// will be forwarded through the sockets as an ancillary data item of /// type \c SCM_RIGHTS. Other elements of the session will be transferred /// as normal data over the connection. /// /// We provide three classes to help applications forward socket sessions: /// \c SocketSessionForwarder is the sender of the UNIX domain connection, /// while \c SocketSessionReceiver is the receiver (this interface assumes /// one direction of forwarding); \c SocketSession represents a single /// socket session. /// /// \c SocketSessionForwarder and \c SocketSessionReceiver objects use a /// straightforward protocol to pass elements of socket sessions. /// Once the connection is established, the forwarder object first forwards /// the file descriptor with 1-byte dummy data. It then forwards a /// "(socket) session header", which contains all other elements of the session /// except the file descriptor (already forwarded) and session data. /// The wire format of the header is as follows: /// - The length of the header (16-bit unsigned integer) /// - Address family /// - Socket type /// - Protocol /// - Size of the local endpoint in bytes /// - Local endpoint (a copy of the memory image of the corresponding /// \c sockaddr) /// - Size of the remote endpoint in bytes /// - Remote endpoint (same as local endpoint) /// - Size of session data in bytes /// /// The type of the fields is 32-bit unsigned integer unless explicitly /// noted, and all fields are formatted in the network byte order. /// /// The socket session data immediately follows the session header. /// /// Note that the fields do not necessarily be in the network byte order /// because they are expected to be exchanged on the same machine. Likewise, /// integer elements such as address family do not necessarily be represented /// as an fixed-size value (i.e., 32-bit). But fixed size fields are used /// in order to ensure maximum portability in such a (rare) case where the /// forwarder and the receiver are built with different compilers that have /// different definitions of \c int. Also, since \c sockaddr fields are /// generally formatted in the network byte order, other fields are defined /// so to be consistent. /// /// One basic assumption in the API of this utility is socket sessions should /// be forwarded without blocking, thus eliminating the need for incremental /// read/write or blocking other important services such as responding to /// requests from the application's clients. This assumption should be held /// as long as both the forwarder and receiver have sufficient resources /// to handle the forwarding process since the communication is local. /// But a forward attempt could still block if the receiver is busy (or even /// hang up) and cannot keep up with the volume of incoming sessions. /// /// So, in this implementation, the forwarder uses non blocking writes to /// forward sessions. If a write attempt could block, it immediately gives /// up the operation with an exception. The corresponding application is /// expected to catch it, close the connection, and perform any necessary /// recovery for that application (that would normally be re-establish the /// connection with a new receiver, possibly after confirming the receiving /// side is still alive). On the other hand, the receiver implementation /// assumes it's possible that it only receive incomplete elements of a /// session (such as in the case where the forwarder writes part of the /// entire session and gives up the connection). The receiver implementation /// throws an exception when it encounters an incomplete session. Like the /// case of the forwarder application, the receiver application is expected /// to catch it, close the connection, and perform any necessary recovery /// steps. /// /// Note that the receiver implementation uses blocking read. So it's /// application's responsibility to ensure that there's at least some data /// in the connection when the receiver object is requested to receive a /// session (unless this operation can be blocking, e.g., by the use of /// a separate thread). Also, if the forwarder implementation or application /// is malicious or extremely buggy and intentionally sends partial session /// and keeps the connection, the receiver could block in receiving a session. /// In general, we assume the forwarder doesn't do intentional blocking /// as it's a local node and is generally a module of the same (Kea) /// 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 "base" class of \c SocketSessionForwarder /// /// This class defines abstract interfaces of the \c SocketSessionForwarder /// class. Although \c SocketSessionForwarder is not intended to be used in /// a polymorphic way, it's not easy to use in tests because it will require /// various low level network operations. So it would be useful if we /// provide a framework for defining a fake or mock version of it. /// An application that needs to use \c SocketSessionForwarder would actually /// refer to this base class, and tests for the application would define /// and use a fake version of the forwarder class. /// /// Normal applications are not expected to define and use their own derived /// version of this base class, while it's not prohibited at the API level. /// /// See description of \c SocketSessionForwarder for the expected interface. class BaseSocketSessionForwarder { protected: BaseSocketSessionForwarder() {} public: virtual ~BaseSocketSessionForwarder() {} virtual void connectToReceiver() = 0; virtual void close() = 0; virtual 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) = 0; }; /// The forwarder of socket sessions /// /// An object of this class maintains a UNIX domain socket (normally expected /// to be connected to a \c SocketSessionReceiver object) and forwards /// socket sessions to the receiver. /// /// See the description of \ref SocketSessionUtility for other details of how /// the session forwarding works. class SocketSessionForwarder : boost::noncopyable, public BaseSocketSessionForwarder { public: /// The constructor. /// /// It's constructed with path information of the intended receiver, /// but does not immediately establish a connection to the receiver; /// \c connectToReceiver() must be called to establish it. These are /// separated so that an object of class can be initialized (possibly /// as an attribute of a higher level application class object) without /// knowing the receiver is ready for accepting new forwarders. The /// separate connect interface allows the object to be reused when it /// detects connection failure and tries to re-establish it after closing /// the failed one. /// /// On construction, it also installs a signal filter for SIGPIPE to /// ignore it. Since this class uses a stream-type connected UNIX domain /// socket, if the receiver (abruptly) closes the connection a subsequent /// write operation on the socket would trigger a SIGPIPE signal, which /// kills the caller process by default. This behavior would be /// undesirable in many cases, so this implementation always disables /// the signal. /// /// This approach has some drawbacks, however; first, since signal handling /// is process (or thread) wide, ignoring it may not what the application /// wants. On the other hand, if the application changes how the signal is /// handled after instantiating this class, the new behavior affects the /// class operation. Secondly, even if ignoring the signal is the desired /// operation, it's a waste to set the filter every time this class object /// is constructed. It's sufficient to do it once. We still adopt this /// behavior based on the observation that in most cases applications would /// like to ignore SIGPIPE (or simply doesn't care about it) and that this /// class is not instantiated so often (so the wasteful setting overhead /// should be marginal). On the other hand, doing it every time is /// beneficial if the application is threaded and different threads /// create different forwarder objects (and if signals work per thread). /// /// \exception SocketSessionError \c unix_file is invalid as a path name /// of a UNIX domain socket. /// \exception Unexpected Error in setting a filter for SIGPIPE (see above) /// \exception std::bad_alloc resource allocation failure /// /// \param unix_file Path name of the receiver. explicit SocketSessionForwarder(const std::string& unix_file); /// The destructor. /// /// If a connection has been established, it's automatically closed in /// the destructor. virtual ~SocketSessionForwarder(); /// Establish a connection to the receiver. /// /// This method establishes a connection to the receiver at the path /// given on construction. It makes the underlying UNIX domain socket /// non blocking, so this method (or subsequent \c push() calls) does not /// block. /// /// \exception BadValue The method is called while an already /// established connection is still active. /// \exception SocketSessionError A system error in socket operation. virtual void connectToReceiver(); /// Close the connection to the receiver. /// /// The connection must have been established by \c connectToReceiver(). /// As long as it's met this method is exception free. /// /// \exception BadValue The connection hasn't been established. virtual void close(); /// Forward a socket session to the receiver. /// /// This method takes a set of parameters that represent a single socket /// session, renders them in the "wire" format according to the internal /// protocol (see \ref SocketSessionUtility) and forwards them to /// the receiver through the UNIX domain connection. /// /// The connection must have been established by \c connectToReceiver(). /// /// For simplicity and for the convenience of detecting application /// errors, this method imposes some restrictions on the parameters: /// - Socket family must be either \c AF_INET or \c AF_INET6 /// - The address family (\c sa_family) member of the local and remote /// end points must be equal to the \c family parameter /// - Socket session data must not be empty (\c data_len must not be 0 /// and \c data must not be NULL) /// - Data length must not exceed 65535 /// These are not architectural limitation, and might be loosened in /// future versions as we see the need for flexibility. /// /// Since the underlying UNIX domain socket is non blocking /// (see the description for the constructor), a call to this method /// should either return immediately or result in exception (in case of /// "would block"). /// /// \exception BadValue The method is called before establishing a /// connection or given parameters are invalid. /// \exception SocketSessionError A system error in socket operation, /// including the case where the write operation would block. /// /// \param sock The socket file descriptor /// \param family The address family (such as AF_INET6) of the socket /// \param type The socket type (such as SOCK_DGRAM) of the socket /// \param protocol The transport protocol (such as IPPROTO_UDP) of the /// socket /// \param local_end The local end point of the session in the form of /// \c sockaddr. /// \param remote_end The remote end point of the session in the form of /// \c sockaddr. /// \param data A pointer to the beginning of the memory region for the /// session data /// \param data_len The size of the session data in bytes. virtual void push(int sock, int family, int type, int protocol, const struct sockaddr& local_end, const struct sockaddr& remote_end, const void* data, size_t data_len); private: struct ForwarderImpl; ForwarderImpl* impl_; }; /// Socket session object. /// /// The \c SocketSession class provides a convenient encapsulation /// for the notion of a socket session. It's instantiated with straightforward /// parameters corresponding to a socket session, and provides read only /// accessors to the parameters to ensure data integrity. /// /// In the initial design and implementation it's only used as a return type /// of \c SocketSessionReceiver::pop(), but it could also be used by /// the \c SocketSessionForwarder class or for other purposes. /// /// It is assumed that the original owner of a \c SocketSession object /// (e.g. a class or a function that constructs it) is responsible for validity /// of the data passed to the object. See the description of /// \c SocketSessionReceiver::pop() for the specific case of that usage. class SocketSession { public: /// The constructor. /// /// This is a trivial constructor, taking a straightforward representation /// of session parameters and storing them internally to ensure integrity. /// /// As long as the given parameters are valid it never throws an exception. /// /// \exception BadValue Given parameters don't meet the requirement /// (see the parameter descriptions). /// /// \param sock The socket file descriptor /// \param family The address family (such as AF_INET6) of the socket /// \param type The socket type (such as SOCK_DGRAM) of the socket /// \param protocol The transport protocol (such as IPPROTO_UDP) of the /// socket. /// \param local_end The local end point of the session in the form of /// \c sockaddr. Must not be NULL. /// \param remote_end The remote end point of the session in the form of /// \c sockaddr. Must not be NULL. /// \param data A pointer to the beginning of the memory region for the /// session data. Must not be NULL, and the subsequent \c data_len bytes /// must be valid. /// \param data_len The size of the session data in bytes. Must not be 0. SocketSession(int sock, int family, int type, int protocol, const sockaddr* local_end, const sockaddr* remote_end, const void* data, size_t data_len); /// Return the socket file descriptor. int getSocket() const { return (sock_); } /// Return the address family (such as AF_INET6) of the socket. int getFamily() const { return (family_); } /// Return the socket type (such as SOCK_DGRAM) of the socket. int getType() const { return (type_); } /// Return the transport protocol (such as IPPROTO_UDP) of the socket. int getProtocol() const { return (protocol_); } /// Return the local end point of the session in the form of \c sockaddr. const sockaddr& getLocalEndpoint() const { return (*local_end_); } /// Return the remote end point of the session in the form of \c sockaddr. const sockaddr& getRemoteEndpoint() const { return (*remote_end_); } /// Return a pointer to the beginning of the memory region for the session /// data. /// /// In the current implementation it should never be NULL, and the region /// of the size returned by \c getDataLength() is expected to be valid. const void* getData() const { return (data_); } /// Return the size of the session data in bytes. /// /// In the current implementation it should be always larger than 0. size_t getDataLength() const { return (data_len_); } private: const int sock_; const int family_; const int type_; const int protocol_; const sockaddr* local_end_; const sockaddr* remote_end_; const void* const data_; const size_t data_len_; }; /// The receiver of socket sessions /// /// An object of this class holds a UNIX domain socket for an /// established connection, receives socket sessions from /// the remote forwarder, and provides the session to the application /// in the form of a \c SocketSession object. /// /// Note that this class is instantiated with an already connected socket; /// it's not a listening socket that is accepting connection requests from /// forwarders. It's application's responsibility to create the listening /// socket, listen on it and accept connections. Once the connection is /// established, the application would construct a \c SocketSessionReceiver /// object with the socket for the newly established connection. /// This behavior is based on the design decision that the application should /// decide when it performs (possibly) blocking operations (see \ref /// SocketSessionUtility for more details). /// /// See the description of \ref SocketSessionUtility for other details of how /// the session forwarding works. class SocketSessionReceiver : boost::noncopyable { public: /// The constructor. /// /// \exception SocketSessionError Any error on an operation that is /// performed on the given socket as part of initialization. /// \exception std::bad_alloc Resource allocation failure /// /// \param fd A UNIX domain socket for an established connection with /// a forwarder. explicit SocketSessionReceiver(int fd); /// The destructor. /// /// The destructor does \c not close the socket given on construction. /// It's up to the application what to do with it (note that the /// application would have to maintain the socket itself for detecting /// the existence of a new socket session asynchronously). ~SocketSessionReceiver(); /// Receive a socket session from the forwarder. /// /// This method receives wire-format data (see \ref SocketSessionUtility) /// for a socket session on the UNIX domain socket, performs some /// validation on the data, and returns the session information in the /// form of a \c SocketSession object. /// /// The returned SocketSession object is valid only until the next time /// this method is called or until the \c SocketSessionReceiver object is /// destroyed. /// /// The caller is responsible for closing the received socket (whose /// file descriptor is accessible via \c SocketSession::getSocket()). /// If the caller copies the returned \c SocketSession object, it's also /// responsible for making sure the descriptor is closed at most once. /// On the other hand, the caller is not responsible for freeing the /// socket session data (accessible via \c SocketSession::getData()); /// the \c SocketSessionReceiver object will clean it up automatically. /// /// It ensures the following: /// - The address family is either \c AF_INET or \c AF_INET6 /// - The address family (\c sa_family) member of the local and remote /// end points must be equal to the \c family parameter /// - The socket session data is not empty and does not exceed 65535 /// bytes. /// If the validation fails or an unexpected system error happens /// (including a connection close in the meddle of reception), it throws /// an SocketSessionError exception. When this happens, it's very /// unlikely that a subsequent call to this method succeeds, so in reality /// the application is expected to destruct it and close the socket in /// such a case. /// /// \exception SocketSessionError Invalid data is received or a system /// error on socket operation happens. /// \exception std::bad_alloc Resource allocation failure /// /// \return A \c SocketSession object corresponding to the extracted /// socket session. SocketSession pop(); private: struct ReceiverImpl; ReceiverImpl* impl_; }; } } } #endif // SOCKETSESSION_H // Local Variables: // mode: c++ // End: