mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 14:05:33 +00:00
[1452] overall documentation update
This commit is contained in:
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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:
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user