Kea 2.5.8
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 getsockname() and 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. AF_INET6), socket type (e.g. SOCK_STREAM), and protocol (e.g. 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 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 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 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: SocketSessionForwarder is the sender of the UNIX domain connection, while SocketSessionReceiver is the receiver (this interface assumes one direction of forwarding); SocketSession represents a single socket session.

SocketSessionForwarder and 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 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 int. Also, since 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.