summaryrefslogtreecommitdiffstats
path: root/src/lib/util/io/socketsession.h
blob: 52e33de64d51e697eb22990c982c7e73542c2001 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
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 <boost/noncopyable.hpp>

#include <exceptions/exceptions.h>

#include <string>

#include <sys/socket.h>

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
/// <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 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: