From 040eee1aa49b49df4698d83a05af57c220127fd1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 13:36:04 +0200 Subject: Adding upstream version 2.2.0. Signed-off-by: Daniel Baumann --- src/lib/util/io/socketsession.h | 495 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 495 insertions(+) create mode 100644 src/lib/util/io/socketsession.h (limited to 'src/lib/util/io/socketsession.h') diff --git a/src/lib/util/io/socketsession.h b/src/lib/util/io/socketsession.h new file mode 100644 index 0000000..52e33de --- /dev/null +++ b/src/lib/util/io/socketsession.h @@ -0,0 +1,495 @@ +// 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: -- cgit v1.2.3