Kea 2.5.8
socketsession.cc
Go to the documentation of this file.
1// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <unistd.h>
10
11#include <sys/types.h>
12#include <sys/socket.h>
13#include <sys/uio.h>
14#include <sys/un.h>
15
16#include <netinet/in.h>
17
18#include <fcntl.h>
19#include <stdint.h>
20
21#include <cerrno>
22#include <csignal>
23#include <cstddef>
24#include <cstring>
25
26#include <string>
27#include <vector>
28
29#include <boost/noncopyable.hpp>
30
33
34#include <util/buffer.h>
35
36#include <util/io/fd_share.h>
39
40using namespace std;
41
42namespace isc {
43namespace util {
44namespace io {
45
46using namespace internal;
47
48// The expected max size of the session header: 2-byte header length,
49// 6 32-bit fields, and 2 sockaddr structure. (see the SocketSessionUtility
50// overview description in the header file). sizeof sockaddr_storage
51// should be the possible max of any sockaddr structure
52const size_t DEFAULT_HEADER_BUFLEN = sizeof(uint16_t) + sizeof(uint32_t) * 6 +
53 sizeof(struct sockaddr_storage) * 2;
54
55// The allowable maximum size of data passed with the socket FD. For now
56// we use a fixed value of 65535, the largest possible size of valid DNS
57// messages. We may enlarge it or make it configurable as we see the need
58// for more flexibility.
59const int MAX_DATASIZE = 65535;
60
61// The initial buffer size for receiving socket session data in the receiver.
62// This value is the maximum message size of DNS messages carried over UDP
63// (without EDNS). In our expected usage (at the moment) this should be
64// sufficiently large (the expected data is AXFR/IXFR query or an UPDATE
65// requests. The former should be generally quite small. While the latter
66// could be large, it would often be small enough for a single UDP message).
67// If it turns out that there are many exceptions, we may want to extend
68// the class so that this value can be customized. Note that the buffer
69// will be automatically extended for longer data and this is only about
70// efficiency.
71const size_t INITIAL_BUFSIZE = 512;
72
73// The (default) socket buffer size for the forwarder and receiver. This is
74// chosen to be sufficiently large to store two full-size DNS messages. We
75// may want to customize this value in future.
77
80 memset(&sock_un_, 0, sizeof(sock_un_));
81 }
82
83 struct sockaddr_un sock_un_;
84 socklen_t sock_un_len_;
85 int fd_;
87};
88
90 impl_(NULL)
91{
92 // We need to filter SIGPIPE for subsequent push(). See the class
93 // description.
94 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
95 isc_throw(Unexpected, "Failed to filter SIGPIPE: " << strerror(errno));
96 }
97
98 ForwarderImpl impl;
99 if (sizeof(impl.sock_un_.sun_path) - 1 < unix_file.length()) {
101 "File name for a UNIX domain socket is too long: " <<
102 unix_file);
103 }
104 impl.sock_un_.sun_family = AF_UNIX;
105 // the copy should be safe due to the above check, but we'd be rather
106 // paranoid about making it 100% sure even if the check has a bug (with
107 // triggering the assertion in the worse case)
108 memset(&impl.sock_un_.sun_path, 0, sizeof(impl.sock_un_.sun_path));
109 strncpy(impl.sock_un_.sun_path, unix_file.c_str(),
110 sizeof(impl.sock_un_.sun_path) - 1);
111 isc_throw_assert(impl.sock_un_.sun_path[sizeof(impl.sock_un_.sun_path) - 1] == '\0');
112 impl.sock_un_len_ = offsetof(struct sockaddr_un, sun_path) +
113 unix_file.length();
114#ifdef HAVE_SA_LEN
115 impl.sock_un_.sun_len = impl.sock_un_len_;
116#endif
117 impl.fd_ = -1;
118
119 impl_ = new ForwarderImpl;
120 *impl_ = impl;
121}
122
124 if (impl_->fd_ != -1) {
125 close();
126 }
127 delete impl_;
128}
129
130void
132 if (impl_->fd_ != -1) {
133 isc_throw(BadValue, "Duplicate connect to UNIX domain "
134 "endpoint " << impl_->sock_un_.sun_path);
135 }
136
137 impl_->fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
138 if (impl_->fd_ == -1) {
139 isc_throw(SocketSessionError, "Failed to create a UNIX domain socket: "
140 << strerror(errno));
141 }
142 // Make the socket non blocking
143 int fcntl_flags = fcntl(impl_->fd_, F_GETFL, 0);
144 if (fcntl_flags != -1) {
145 fcntl_flags |= O_NONBLOCK;
146 fcntl_flags = fcntl(impl_->fd_, F_SETFL, fcntl_flags);
147 }
148 if (fcntl_flags == -1) {
149 close(); // note: this is the internal method, not ::close()
151 "Failed to make UNIX domain socket non blocking: " <<
152 strerror(errno));
153 }
154 // Ensure the socket send buffer is large enough. If we can't get the
155 // current size, simply set the sufficient size.
156 int sndbuf_size;
157 socklen_t sndbuf_size_len = sizeof(sndbuf_size);
158 if (getsockopt(impl_->fd_, SOL_SOCKET, SO_SNDBUF, &sndbuf_size,
159 &sndbuf_size_len) == -1 ||
160 sndbuf_size < SOCKSESSION_BUFSIZE) {
161 if (setsockopt(impl_->fd_, SOL_SOCKET, SO_SNDBUF, &SOCKSESSION_BUFSIZE,
162 sizeof(SOCKSESSION_BUFSIZE)) == -1) {
163 close();
165 "Failed to set send buffer size to " <<
167 }
168 }
169 if (connect(impl_->fd_, convertSockAddr(&impl_->sock_un_),
170 impl_->sock_un_len_) == -1) {
171 close();
172 isc_throw(SocketSessionError, "Failed to connect to UNIX domain "
173 "endpoint " << impl_->sock_un_.sun_path << ": " <<
174 strerror(errno));
175 }
176}
177
178void
180 if (impl_->fd_ == -1) {
181 isc_throw(BadValue, "Attempt of close before connect");
182 }
183 ::close(impl_->fd_);
184 impl_->fd_ = -1;
185}
186
187void
188SocketSessionForwarder::push(int sock, int family, int type, int protocol,
189 const struct sockaddr& local_end,
190 const struct sockaddr& remote_end,
191 const void* data, size_t data_len)
192{
193 if (impl_->fd_ == -1) {
194 isc_throw(BadValue, "Attempt of push before connect");
195 }
196 if ((local_end.sa_family != AF_INET && local_end.sa_family != AF_INET6) ||
197 (remote_end.sa_family != AF_INET && remote_end.sa_family != AF_INET6))
198 {
199 isc_throw(BadValue, "Invalid address family: must be "
200 "AF_INET or AF_INET6; " <<
201 static_cast<int>(local_end.sa_family) << ", " <<
202 static_cast<int>(remote_end.sa_family) << " given");
203 }
204 if (family != local_end.sa_family || family != remote_end.sa_family) {
205 isc_throw(BadValue, "Inconsistent address family: must be "
206 << static_cast<int>(family) << "; "
207 << static_cast<int>(local_end.sa_family) << ", "
208 << static_cast<int>(remote_end.sa_family) << " given");
209 }
210 if (data_len == 0 || data == NULL) {
211 isc_throw(BadValue, "Data for a socket session must not be empty");
212 }
213 if (data_len > MAX_DATASIZE) {
214 isc_throw(BadValue, "Invalid socket session data size: " <<
215 data_len << ", must not exceed " << MAX_DATASIZE);
216 }
217
218 if (send_fd(impl_->fd_, sock) != 0) {
219 isc_throw(SocketSessionError, "FD passing failed: " <<
220 strerror(errno));
221 }
222
223 impl_->buf_.clear();
224 // Leave the space for the header length
225 impl_->buf_.skip(sizeof(uint16_t));
226 // Socket properties: family, type, protocol
227 impl_->buf_.writeUint32(static_cast<uint32_t>(family));
228 impl_->buf_.writeUint32(static_cast<uint32_t>(type));
229 impl_->buf_.writeUint32(static_cast<uint32_t>(protocol));
230 // Local endpoint
231 impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(local_end)));
232 impl_->buf_.writeData(&local_end, getSALength(local_end));
233 // Remote endpoint
234 impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(remote_end)));
235 impl_->buf_.writeData(&remote_end, getSALength(remote_end));
236 // Data length. Must be fit uint32 due to the range check above.
237 const uint32_t data_len32 = static_cast<uint32_t>(data_len);
238 isc_throw_assert(data_len == data_len32); // shouldn't cause overflow.
239 impl_->buf_.writeUint32(data_len32);
240 // Write the resulting header length at the beginning of the buffer
241 impl_->buf_.writeUint16At(impl_->buf_.getLength() - sizeof(uint16_t), 0);
242
243 const struct iovec iov[2] = {
244 { const_cast<void*>(impl_->buf_.getDataAsVoidPtr()),
245 impl_->buf_.getLength() },
246 { const_cast<void*>(data), data_len }
247 };
248 const int cc = writev(impl_->fd_, iov, 2);
249 if (cc != impl_->buf_.getLength() + data_len) {
250 if (cc < 0) {
252 "Write failed in forwarding a socket session: " <<
253 strerror(errno));
254 }
256 "Incomplete write in forwarding a socket session: " << cc <<
257 "/" << (impl_->buf_.getLength() + data_len));
258 }
259}
260
261SocketSession::SocketSession(int sock, int family, int type, int protocol,
262 const sockaddr* local_end,
263 const sockaddr* remote_end,
264 const void* data, size_t data_len) :
265 sock_(sock), family_(family), type_(type), protocol_(protocol),
266 local_end_(local_end), remote_end_(remote_end),
267 data_(data), data_len_(data_len)
268{
269 if (local_end == NULL || remote_end == NULL) {
270 isc_throw(BadValue, "sockaddr must be non NULL for SocketSession");
271 }
272 if (data_len == 0) {
273 isc_throw(BadValue, "data_len must be non 0 for SocketSession");
274 }
275 if (data == NULL) {
276 isc_throw(BadValue, "data must be non NULL for SocketSession");
277 }
278}
279
281 ReceiverImpl(int fd) : fd_(fd),
286 {
287 memset(&ss_local_, 0, sizeof(ss_local_));
288 memset(&ss_remote_, 0, sizeof(ss_remote_));
289
290 if (setsockopt(fd_, SOL_SOCKET, SO_RCVBUF, &SOCKSESSION_BUFSIZE,
291 sizeof(SOCKSESSION_BUFSIZE)) == -1) {
293 "Failed to set receive buffer size to " <<
295 }
296 }
297
298 const int fd_;
299 struct sockaddr_storage ss_local_; // placeholder for local endpoint
300 struct sockaddr* const sa_local_;
301 struct sockaddr_storage ss_remote_; // placeholder for remote endpoint
302 struct sockaddr* const sa_remote_;
303
304 // placeholder for session header and data
305 vector<uint8_t> header_buf_;
306 vector<uint8_t> data_buf_;
307};
308
310 impl_(new ReceiverImpl(fd))
311{
312}
313
315 delete impl_;
316}
317
318namespace {
319// A shortcut to throw common exception on failure of recv(2)
320void
321readFail(int actual_len, int expected_len) {
322 if (expected_len < 0) {
323 isc_throw(SocketSessionError, "Failed to receive data from "
324 "SocketSessionForwarder: " << strerror(errno));
325 }
326 isc_throw(SocketSessionError, "Incomplete data from "
327 "SocketSessionForwarder: " << actual_len << "/" <<
328 expected_len);
329}
330
331// A helper container for a (socket) file descriptor used in
332// SocketSessionReceiver::pop that ensures the socket is closed unless it
333// can be safely passed to the caller via release().
334struct ScopedSocket : boost::noncopyable {
335 ScopedSocket(int fd) : fd_(fd) {}
336 ~ScopedSocket() {
337 if (fd_ >= 0) {
338 close(fd_);
339 }
340 }
341 int release() {
342 const int fd = fd_;
343 fd_ = -1;
344 return (fd);
345 }
346 int fd_;
347};
348}
349
350SocketSession
352 ScopedSocket passed_sock(recv_fd(impl_->fd_));
353 if (passed_sock.fd_ == FD_SYSTEM_ERROR) {
354 isc_throw(SocketSessionError, "Receiving a forwarded FD failed: " <<
355 strerror(errno));
356 } else if (passed_sock.fd_ < 0) {
357 isc_throw(SocketSessionError, "No FD forwarded");
358 }
359
360 uint16_t header_len;
361 const int cc_hlen = recv(impl_->fd_, &header_len, sizeof(header_len),
362 MSG_WAITALL);
363 if (cc_hlen < sizeof(header_len)) {
364 readFail(cc_hlen, sizeof(header_len));
365 }
366 header_len = InputBuffer(&header_len, sizeof(header_len)).readUint16();
367 if (header_len > DEFAULT_HEADER_BUFLEN) {
368 isc_throw(SocketSessionError, "Too large header length: " <<
369 header_len);
370 }
371 impl_->header_buf_.clear();
372 impl_->header_buf_.resize(header_len);
373 const int cc_hdr = recv(impl_->fd_, &impl_->header_buf_[0], header_len,
374 MSG_WAITALL);
375 if (cc_hdr < header_len) {
376 readFail(cc_hdr, header_len);
377 }
378
379 InputBuffer ibuffer(&impl_->header_buf_[0], header_len);
380 try {
381 const int family = static_cast<int>(ibuffer.readUint32());
382 if (family != AF_INET && family != AF_INET6) {
384 "Unsupported address family is passed: " << family);
385 }
386 const int type = static_cast<int>(ibuffer.readUint32());
387 const int protocol = static_cast<int>(ibuffer.readUint32());
388 const socklen_t local_end_len = ibuffer.readUint32();
389 const socklen_t endpoint_minlen = (family == AF_INET) ?
390 sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
391 if (local_end_len < endpoint_minlen ||
392 local_end_len > sizeof(impl_->ss_local_)) {
393 isc_throw(SocketSessionError, "Invalid local SA length: " <<
394 local_end_len);
395 }
396 ibuffer.readData(&impl_->ss_local_, local_end_len);
397 const socklen_t remote_end_len = ibuffer.readUint32();
398 if (remote_end_len < endpoint_minlen ||
399 remote_end_len > sizeof(impl_->ss_remote_)) {
400 isc_throw(SocketSessionError, "Invalid remote SA length: " <<
401 remote_end_len);
402 }
403 ibuffer.readData(&impl_->ss_remote_, remote_end_len);
404 if (family != impl_->sa_local_->sa_family ||
405 family != impl_->sa_remote_->sa_family) {
406 isc_throw(SocketSessionError, "SA family inconsistent: " <<
407 static_cast<int>(impl_->sa_local_->sa_family) << ", " <<
408 static_cast<int>(impl_->sa_remote_->sa_family) <<
409 " given, must be " << family);
410 }
411 const size_t data_len = ibuffer.readUint32();
412 if (data_len == 0 || data_len > MAX_DATASIZE) {
414 "Invalid socket session data size: " << data_len <<
415 ", must be > 0 and <= " << MAX_DATASIZE);
416 }
417
418 impl_->data_buf_.clear();
419 impl_->data_buf_.resize(data_len);
420 const int cc_data = recv(impl_->fd_, &impl_->data_buf_[0], data_len,
421 MSG_WAITALL);
422 if (cc_data < data_len) {
423 readFail(cc_data, data_len);
424 }
425
426 return (SocketSession(passed_sock.release(), family, type, protocol,
427 impl_->sa_local_, impl_->sa_remote_,
428 &impl_->data_buf_[0], data_len));
429 } catch (const OutOfRange& ex) {
430 // We catch the case where the given header is too short and convert
431 // the exception to SocketSessionError.
432 isc_throw(SocketSessionError, "bogus socket session header: " <<
433 ex.what());
434 }
435}
436
437}
438}
439}
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
A generic exception that is thrown when an unexpected error condition occurs.
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition: buffer.h:82
uint32_t readUint32()
Read an unsigned 32-bit integer in network byte order from the buffer, and return it.
Definition: buffer.h:199
void readData(void *data, size_t len)
Read data of the specified length from the buffer and copy it to the caller supplied buffer.
Definition: buffer.h:229
uint16_t readUint16()
Read an unsigned 16-bit integer in network byte order from the buffer, and return it.
Definition: buffer.h:167
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:347
void writeUint16At(uint16_t data, size_t pos)
Write an unsigned 16-bit integer in host byte order at the specified position of the buffer in networ...
Definition: buffer.h:514
const void * getDataAsVoidPtr() const
Return data as a pointer to void.
Definition: buffer.h:407
void writeData(const void *data, size_t len)
Copy an arbitrary length of data into the buffer.
Definition: buffer.h:556
void writeUint32(uint32_t data)
Write an unsigned 32-bit integer in host byte order into the buffer in network byte order.
Definition: buffer.h:528
void skip(size_t len)
Insert a specified length of gap at the end of the buffer.
Definition: buffer.h:447
size_t getLength() const
Return the length of data written in the buffer.
Definition: buffer.h:412
void clear()
Clear buffer content.
Definition: buffer.h:467
An exception indicating general errors that takes place in the socket session related class objects.
virtual ~SocketSessionForwarder()
The destructor.
virtual void close()
Close the connection to the receiver.
virtual void connectToReceiver()
Establish a connection to the receiver.
SocketSessionForwarder(const std::string &unix_file)
The constructor.
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)
Forward a socket session to the receiver.
SocketSession pop()
Receive a socket session from the forwarder.
SocketSessionReceiver(int fd)
The constructor.
Socket session object.
SocketSession(int sock, int family, int type, int protocol, const sockaddr *local_end, const sockaddr *remote_end, const void *data, size_t data_len)
The constructor.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Support to transfer file descriptors between processes.
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition: isc_assert.h:18
const struct sockaddr * convertSockAddr(const SAType *sa)
Definition: sockaddr_util.h:41
socklen_t getSALength(const struct sockaddr &sa)
Definition: sockaddr_util.h:26
const size_t INITIAL_BUFSIZE
const int FD_SYSTEM_ERROR
Definition: fd_share.h:20
int recv_fd(const int sock)
Receives a file descriptor.
Definition: fd_share.cc:71
const size_t DEFAULT_HEADER_BUFLEN
int send_fd(const int sock, const int fd)
Sends a file descriptor.
Definition: fd_share.cc:134
const int SOCKSESSION_BUFSIZE
const int MAX_DATASIZE
Defines the logger used by the top-level component of kea-lfc.
int fd_