Kea 3.0.0
tcp_socket.h
Go to the documentation of this file.
1// Copyright (C) 2011-2025 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
25#include <boost/numeric/conversion/cast.hpp>
26
27#include <netinet/in.h>
28#include <sys/socket.h>
29#include <unistd.h> // for some IPC/network system calls
30
31namespace isc {
32namespace asiolink {
33
37class BufferTooLarge : public IOError {
38public:
39 BufferTooLarge(const char* file, size_t line, const char* what) :
40 IOError(file, line, what) {}
41};
42
47template <typename C>
48class TCPSocket : public IOAsioSocket<C> {
49private:
51 TCPSocket(const TCPSocket&);
52 TCPSocket& operator=(const TCPSocket&);
53
54public:
55
61 TCPSocket(boost::asio::ip::tcp::socket& socket);
62
69 TCPSocket(const IOServicePtr& service);
70
72 virtual ~TCPSocket();
73
75 virtual int getNative() const {
76 return (socket_.native_handle());
77 }
78
80 virtual int getProtocol() const {
81 return (IPPROTO_TCP);
82 }
83
87 virtual bool isOpenSynchronous() const {
88 return (false);
89 }
90
97 bool isUsable() const {
98 // If the socket is open it doesn't mean that it is still usable. The connection
99 // could have been closed on the other end. We have to check if we can still
100 // use this socket.
101 if (socket_.is_open()) {
102 // Remember the current non blocking setting.
103 const bool non_blocking_orig = socket_.non_blocking();
104 // Set the socket to non blocking mode. We're going to test if the socket
105 // returns would_block status on the attempt to read from it.
106 socket_.non_blocking(true);
107
108 boost::system::error_code ec;
109 char data[2];
110
111 // Use receive with message peek flag to avoid removing the data awaiting
112 // to be read.
113 static_cast<void>(
114 socket_.receive(boost::asio::buffer(data, sizeof(data)),
115 boost::asio::socket_base::message_peek,
116 ec));
117
118 // Revert the original non_blocking flag on the socket.
119 socket_.non_blocking(non_blocking_orig);
120
121 // If the connection is alive we'd typically get would_block status code.
122 // If there are any data that haven't been read we may also get success
123 // status. We're guessing that try_again may also be returned by some
124 // implementations in some situations. Any other error code indicates a
125 // problem with the connection so we assume that the connection has been
126 // closed.
127 return (!ec || (ec.value() == boost::asio::error::try_again) ||
128 (ec.value() == boost::asio::error::would_block));
129 }
130
131 return (false);
132 }
133
141 virtual void open(const IOEndpoint* endpoint, C& callback);
142
155 virtual void asyncSend(const void* data, size_t length,
156 const IOEndpoint* endpoint, C& callback);
157
170 void asyncSend(const void* data, size_t length, C& callback);
171
183 virtual void asyncReceive(void* data, size_t length, size_t offset,
184 IOEndpoint* endpoint, C& callback);
185
201 virtual bool processReceivedData(const void* staging, size_t length,
202 size_t& cumulative, size_t& offset,
203 size_t& expected,
205
207 virtual void cancel();
208
210 virtual void close();
211
215 virtual boost::asio::ip::tcp::socket& getASIOSocket() const {
216 return (socket_);
217 }
218
219private:
220
222 IOServicePtr io_service_;
223
227
229 std::unique_ptr<boost::asio::ip::tcp::socket> socket_ptr_;
230
232 boost::asio::ip::tcp::socket& socket_;
233
247
249 isc::util::OutputBufferPtr send_buffer_;
250};
251
252// Constructor - caller manages socket
253
254template <typename C>
255TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
256 socket_ptr_(), socket_(socket), send_buffer_() {
257}
258
259// Constructor - create socket on the fly
260
261template <typename C>
262TCPSocket<C>::TCPSocket(const IOServicePtr& io_service) : io_service_(io_service),
263 socket_ptr_(new boost::asio::ip::tcp::socket(io_service_->getInternalIOService())),
264 socket_(*socket_ptr_) {
265}
266
267// Destructor.
268
269template <typename C>
273
274// Open the socket.
275
276template <typename C> void
277TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
278 // If socket is open on this end but has been closed by the peer,
279 // we need to reconnect.
280 if (socket_.is_open() && !isUsable()) {
281 close();
282 }
283 // Ignore opens on already-open socket. Don't throw a failure because
284 // of uncertainties as to what precedes when using asynchronous I/O.
285 // Also allows us a treat a passed-in socket as a self-managed socket.
286 if (!socket_.is_open()) {
287 if (endpoint->getFamily() == AF_INET) {
288 socket_.open(boost::asio::ip::tcp::v4());
289 } else {
290 socket_.open(boost::asio::ip::tcp::v6());
291 }
292
293 // Set options on the socket:
294
295 // Reuse address - allow the socket to bind to a port even if the port
296 // is in the TIMED_WAIT state.
297 socket_.set_option(boost::asio::socket_base::reuse_address(true));
298 }
299
300 // Upconvert to a TCPEndpoint. We need to do this because although
301 // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
302 // contain a method for getting at the underlying endpoint type - that is in
304 isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
305 const TCPEndpoint* tcp_endpoint =
306 static_cast<const TCPEndpoint*>(endpoint);
307
308 // Connect to the remote endpoint. On success, the handler will be
309 // called (with one argument - the length argument will default to
310 // zero).
311 socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
312}
313
314// Send a message. Should never do this if the socket is not open, so throw
315// an exception if this is the case.
316
317template <typename C> void
318TCPSocket<C>::asyncSend(const void* data, size_t length, C& callback) {
319 if (socket_.is_open()) {
320
321 try {
322 send_buffer_.reset(new isc::util::OutputBuffer(length));
323 send_buffer_->writeData(data, length);
324
325 // Send the data.
326 socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
327 send_buffer_->getLength()),
328 callback);
329 } catch (const boost::numeric::bad_numeric_cast&) {
331 "attempt to send buffer larger than 64kB");
332 }
333
334 } else {
336 "attempt to send on a TCP socket that is not open");
337 }
338}
339
340template <typename C> void
341TCPSocket<C>::asyncSend(const void* data, size_t length,
342 const IOEndpoint*, C& callback) {
343 if (socket_.is_open()) {
344
348 try {
350 uint16_t count = boost::numeric_cast<uint16_t>(length);
351
353 send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
354 send_buffer_->writeUint16(count);
355 send_buffer_->writeData(data, length);
356
358 socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
359 send_buffer_->getLength()), callback);
360 } catch (const boost::numeric::bad_numeric_cast&) {
362 "attempt to send buffer larger than 64kB");
363 }
364
365 } else {
367 "attempt to send on a TCP socket that is not open");
368 }
369}
370
371// Receive a message. Note that the "offset" argument is used as an index
372// into the buffer in order to decide where to put the data. It is up to the
373// caller to initialize the data to zero
374template <typename C> void
375TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
376 IOEndpoint* endpoint, C& callback) {
377 if (socket_.is_open()) {
378 // Upconvert to a TCPEndpoint. We need to do this because although
379 // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
380 // does not contain a method for getting at the underlying endpoint
381 // type - that is in the derived class and the two classes differ on
382 // return type.
383 isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
384 TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
385
386 // Write the endpoint details from the communications link. Ideally
387 // we should make IOEndpoint assignable, but this runs in to all sorts
388 // of problems concerning the management of the underlying Boost
389 // endpoint (e.g. if it is not self-managed, is the copied one
390 // self-managed?) The most pragmatic solution is to let Boost take care
391 // of everything and copy details of the underlying endpoint.
392 tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
393
394 // Ensure we can write into the buffer and if so, set the pointer to
395 // where the data will be written.
396 if (offset >= length) {
397 isc_throw(BufferOverflow, "attempt to read into area beyond end of "
398 "TCP receive buffer");
399 }
400 void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
401
402 // ... and kick off the read.
403 socket_.async_receive(boost::asio::buffer(buffer_start, length - offset), callback);
404
405 } else {
407 "attempt to receive from a TCP socket that is not open");
408 }
409}
410
411// Is the receive complete?
412
413template <typename C> bool
414TCPSocket<C>::processReceivedData(const void* staging, size_t length,
415 size_t& cumulative, size_t& offset,
416 size_t& expected,
418 // Point to the data in the staging buffer and note how much there is.
419 const uint8_t* data = static_cast<const uint8_t*>(staging);
420 size_t data_length = length;
421
422 // Is the number is "expected" valid? It won't be unless we have received
423 // at least two bytes of data in total for this set of receives.
424 if (cumulative < 2) {
425
426 // "expected" is not valid. Did this read give us enough data to
427 // work it out?
428 cumulative += length;
429 if (cumulative < 2) {
430
431 // Nope, still not valid. This must have been the first packet and
432 // was only one byte long. Tell the fetch code to read the next
433 // packet into the staging buffer beyond the data that is already
434 // there so that the next time we are called we have a complete
435 // TCP count.
436 offset = cumulative;
437 return (false);
438 }
439
440 // Have enough data to interpret the packet count, so do so now.
441 expected = isc::util::readUint16(data, cumulative);
442
443 // We have two bytes less of data to process. Point to the start of the
444 // data and adjust the packet size. Note that at this point,
445 // "cumulative" is the true amount of data in the staging buffer, not
446 // "length".
447 data += 2;
448 data_length = cumulative - 2;
449 } else {
450
451 // Update total amount of data received.
452 cumulative += length;
453 }
454
455 // Regardless of anything else, the next read goes into the start of the
456 // staging buffer.
457 offset = 0;
458
459 // Work out how much data we still have to put in the output buffer. (This
460 // could be zero if we have just interpreted the TCP count and that was
461 // set to zero.)
462 if (expected >= outbuff->getLength()) {
463
464 // Still need data in the output packet. Copy what we can from the
465 // staging buffer to the output buffer.
466 size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
467 outbuff->writeData(data, copy_amount);
468 }
469
470 // We can now say if we have all the data.
471 return (expected == outbuff->getLength());
472}
473
474// Cancel I/O on the socket. No-op if the socket is not open.
475
476template <typename C> void
478 if (socket_.is_open()) {
479 socket_.cancel();
480 }
481}
482
483// Close the socket down. Can only do this if the socket is open and we are
484// managing it ourself.
485
486template <typename C> void
488 if (socket_.is_open() && socket_ptr_) {
489 socket_.close();
490 }
491}
492
493} // namespace asiolink
494} // namespace isc
495
496#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:346
#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:574
Defines the logger used by the top-level component of kea-lfc.