Kea 3.3.3
tcp_socket.h
Go to the documentation of this file.
1// Copyright (C) 2011-2026 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#ifndef TCP_SOCKET_H
8#define TCP_SOCKET_H
9
10#ifndef BOOST_ASIO_HPP
11#error "asio.hpp must be included before including this, see asiolink.h as to why"
12#endif
13
16#include <asiolink/io_service.h>
19#include <util/buffer.h>
20#include <util/io.h>
21
22#include <algorithm>
23#include <cstddef>
24#include <vector>
25
26#include <boost/numeric/conversion/cast.hpp>
27
28#include <netinet/in.h>
29#include <sys/socket.h>
30#include <unistd.h> // for some IPC/network system calls
31
32namespace isc {
33namespace asiolink {
34
38class BufferTooLarge : public IOError {
39public:
40 BufferTooLarge(const char* file, size_t line, const char* what) :
41 IOError(file, line, what) {}
42};
43
48template <typename C>
49class TCPSocket : public IOAsioSocket<C> {
50private:
52 TCPSocket(const TCPSocket&);
53 TCPSocket& operator=(const TCPSocket&);
54
55public:
56
62 TCPSocket(boost::asio::ip::tcp::socket& socket);
63
70 TCPSocket(const IOServicePtr& service);
71
73 virtual ~TCPSocket();
74
76 virtual int getNative() const {
77 return (socket_.native_handle());
78 }
79
81 virtual int getProtocol() const {
82 return (IPPROTO_TCP);
83 }
84
88 virtual bool isOpenSynchronous() const {
89 return (false);
90 }
91
98 bool isUsable() const {
99 // If the socket is open it doesn't mean that it is still usable. The connection
100 // could have been closed on the other end. We have to check if we can still
101 // use this socket.
102 if (socket_.is_open()) {
103 // Remember the current non blocking setting.
104 const bool non_blocking_orig = socket_.non_blocking();
105 // Set the socket to non blocking mode. We're going to test if the socket
106 // returns would_block status on the attempt to read from it.
107 socket_.non_blocking(true);
108
109 boost::system::error_code ec;
110 char data[2];
111
112 // Use receive with message peek flag to avoid removing the data awaiting
113 // to be read.
114 static_cast<void>(
115 socket_.receive(boost::asio::buffer(data, sizeof(data)),
116 boost::asio::socket_base::message_peek,
117 ec));
118
119 // Revert the original non_blocking flag on the socket.
120 socket_.non_blocking(non_blocking_orig);
121
122 // If the connection is alive we'd typically get would_block status code.
123 // If there are any data that haven't been read we may also get success
124 // status. We're guessing that try_again may also be returned by some
125 // implementations in some situations. Any other error code indicates a
126 // problem with the connection so we assume that the connection has been
127 // closed.
128 return (!ec || (ec.value() == boost::asio::error::try_again) ||
129 (ec.value() == boost::asio::error::would_block));
130 }
131
132 return (false);
133 }
134
142 virtual void open(const IOEndpoint* endpoint, C& callback);
143
156 virtual void asyncSend(const void* data, size_t length,
157 const IOEndpoint* endpoint, C& callback);
158
171 void asyncSend(const void* data, size_t length, C& callback);
172
184 virtual void asyncReceive(void* data, size_t length, size_t offset,
185 IOEndpoint* endpoint, C& callback);
186
202 virtual bool processReceivedData(const void* staging, size_t length,
203 size_t& cumulative, size_t& offset,
204 size_t& expected,
206
208 virtual void cancel();
209
211 virtual void close();
212
216 virtual boost::asio::ip::tcp::socket& getASIOSocket() const {
217 return (socket_);
218 }
219
220private:
221
223 IOServicePtr io_service_;
224
228
230 std::unique_ptr<boost::asio::ip::tcp::socket> socket_ptr_;
231
233 boost::asio::ip::tcp::socket& socket_;
234
237};
238
239// Constructor - caller manages socket
240
241template <typename C>
242TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
243 socket_ptr_(), socket_(socket), header_() {
244}
245
246// Constructor - create socket on the fly
247
248template <typename C>
249TCPSocket<C>::TCPSocket(const IOServicePtr& io_service) : io_service_(io_service),
250 socket_ptr_(new boost::asio::ip::tcp::socket(io_service_->getInternalIOService())),
251 socket_(*socket_ptr_) {
252}
253
254// Destructor.
255
256template <typename C>
260
261// Open the socket.
262
263template <typename C> void
264TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
265 // If socket is open on this end but has been closed by the peer,
266 // we need to reconnect.
267 if (socket_.is_open() && !isUsable()) {
268 close();
269 }
270 // Ignore opens on already-open socket. Don't throw a failure because
271 // of uncertainties as to what precedes when using asynchronous I/O.
272 // Also allows us a treat a passed-in socket as a self-managed socket.
273 if (!socket_.is_open()) {
274 if (endpoint->getFamily() == AF_INET) {
275 socket_.open(boost::asio::ip::tcp::v4());
276 } else {
277 socket_.open(boost::asio::ip::tcp::v6());
278 }
279
280 // Set options on the socket:
281
282 // Reuse address - allow the socket to bind to a port even if the port
283 // is in the TIMED_WAIT state.
284 socket_.set_option(boost::asio::socket_base::reuse_address(true));
285 }
286
287 // Upconvert to a TCPEndpoint. We need to do this because although
288 // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
289 // contain a method for getting at the underlying endpoint type - that is in
291 isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
292 const TCPEndpoint* tcp_endpoint =
293 static_cast<const TCPEndpoint*>(endpoint);
294
295 // Connect to the remote endpoint. On success, the handler will be
296 // called (with one argument - the length argument will default to
297 // zero).
298 socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
299}
300
301// Send a message. Should never do this if the socket is not open, so throw
302// an exception if this is the case.
303//
304// This uses boost asio buffers which do not own the data so it is
305// the responsibility of the caller to keep the data valid.
306// The header holding the count is a member of the socket object.
307// Boost asio buffers themselves can be released after boot asio calls.
308
309template <typename C> void
310TCPSocket<C>::asyncSend(const void* data, size_t length, C& callback) {
311 if (socket_.is_open()) {
312 // Send the data.
313 socket_.async_send(boost::asio::buffer(data, length), callback);
314 } else {
316 "attempt to send on a TCP socket that is not open");
317 }
318}
319
320template <typename C> void
321TCPSocket<C>::asyncSend(const void* data, size_t length,
322 const IOEndpoint*, C& callback) {
323 if (socket_.is_open()) {
324
327 try {
329 uint16_t count = boost::numeric_cast<uint16_t>(length);
330 header_.reset(new isc::util::OutputBuffer(2));
331 header_->writeUint16(count);
332 std::vector<boost::asio::const_buffer> buffers;
333
334 // Prepare a buffer sequence.
335 buffers.push_back(boost::asio::buffer(header_->getData(),
336 header_->getLength()));
337 buffers.push_back(boost::asio::buffer(data, length));
338
340 socket_.async_send(buffers, callback);
341 } catch (const boost::numeric::bad_numeric_cast&) {
343 "attempt to send buffer larger than 64kB");
344 }
345
346 } else {
348 "attempt to send on a TCP socket that is not open");
349 }
350}
351
352// Receive a message. Note that the "offset" argument is used as an index
353// into the buffer in order to decide where to put the data. It is up to the
354// caller to initialize the data to zero
355template <typename C> void
356TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
357 IOEndpoint* endpoint, C& callback) {
358 if (socket_.is_open()) {
359 // Upconvert to a TCPEndpoint. We need to do this because although
360 // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
361 // does not contain a method for getting at the underlying endpoint
362 // type - that is in the derived class and the two classes differ on
363 // return type.
364 isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
365 TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
366
367 // Write the endpoint details from the communications link. Ideally
368 // we should make IOEndpoint assignable, but this runs in to all sorts
369 // of problems concerning the management of the underlying Boost
370 // endpoint (e.g. if it is not self-managed, is the copied one
371 // self-managed?) The most pragmatic solution is to let Boost take care
372 // of everything and copy details of the underlying endpoint.
373 tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
374
375 // Ensure we can write into the buffer and if so, set the pointer to
376 // where the data will be written.
377 if (offset >= length) {
378 isc_throw(BufferOverflow, "attempt to read into area beyond end of "
379 "TCP receive buffer");
380 }
381 void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
382
383 // ... and kick off the read.
384 socket_.async_receive(boost::asio::buffer(buffer_start, length - offset), callback);
385
386 } else {
388 "attempt to receive from a TCP socket that is not open");
389 }
390}
391
392// Is the receive complete?
393
394template <typename C> bool
395TCPSocket<C>::processReceivedData(const void* staging, size_t length,
396 size_t& cumulative, size_t& offset,
397 size_t& expected,
399 // Point to the data in the staging buffer and note how much there is.
400 const uint8_t* data = static_cast<const uint8_t*>(staging);
401 size_t data_length = length;
402
403 // Is the number is "expected" valid? It won't be unless we have received
404 // at least two bytes of data in total for this set of receives.
405 if (cumulative < 2) {
406
407 // "expected" is not valid. Did this read give us enough data to
408 // work it out?
409 cumulative += length;
410 if (cumulative < 2) {
411
412 // Nope, still not valid. This must have been the first packet and
413 // was only one byte long. Tell the fetch code to read the next
414 // packet into the staging buffer beyond the data that is already
415 // there so that the next time we are called we have a complete
416 // TCP count.
417 offset = cumulative;
418 return (false);
419 }
420
421 // Have enough data to interpret the packet count, so do so now.
422 expected = isc::util::readUint16(data, cumulative);
423
424 // We have two bytes less of data to process. Point to the start of the
425 // data and adjust the packet size. Note that at this point,
426 // "cumulative" is the true amount of data in the staging buffer, not
427 // "length".
428 data += 2;
429 data_length = cumulative - 2;
430 } else {
431
432 // Update total amount of data received.
433 cumulative += length;
434 }
435
436 // Regardless of anything else, the next read goes into the start of the
437 // staging buffer.
438 offset = 0;
439
440 // Work out how much data we still have to put in the output buffer. (This
441 // could be zero if we have just interpreted the TCP count and that was
442 // set to zero.)
443 if (expected >= outbuff->getLength()) {
444
445 // Still need data in the output packet. Copy what we can from the
446 // staging buffer to the output buffer.
447 size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
448 outbuff->writeData(data, copy_amount);
449 }
450
451 // We can now say if we have all the data.
452 return (expected == outbuff->getLength());
453}
454
455// Cancel I/O on the socket. No-op if the socket is not open.
456
457template <typename C> void
459 if (socket_.is_open()) {
460 socket_.cancel();
461 }
462}
463
464// Close the socket down. Can only do this if the socket is open and we are
465// managing it ourself.
466
467template <typename C> void
469 if (socket_.is_open() && socket_ptr_) {
470 socket_.close();
471 }
472}
473
474} // namespace asiolink
475} // namespace isc
476
477#endif // TCP_SOCKET_H
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition buffer.h:355
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition isc_assert.h:18
uint16_t readUint16(void const *const buffer, size_t const length)
uint16_t wrapper over readUint.
Definition io.h:76
boost::shared_ptr< OutputBuffer > OutputBufferPtr
Type of pointers to output buffers.
Definition buffer.h:583
Defines the logger used by the top-level component of kea-lfc.