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