Kea  1.5.0
tcp_socket.h
Go to the documentation of this file.
1 // Copyright (C) 2011-2018 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 <cassert>
20 #include <cstddef>
21 
22 #include <boost/bind.hpp>
23 #include <boost/numeric/conversion/cast.hpp>
24 
25 #include <util/buffer.h>
26 #include <util/io_utilities.h>
27 
29 #include <asiolink/io_endpoint.h>
30 #include <asiolink/io_service.h>
31 #include <asiolink/tcp_endpoint.h>
32 
33 namespace isc {
34 namespace asiolink {
35 
39 class BufferTooLarge : public IOError {
40 public:
41  BufferTooLarge(const char* file, size_t line, const char* what) :
42  IOError(file, line, what) {}
43 };
44 
49 template <typename C>
50 class TCPSocket : public IOAsioSocket<C> {
51 private:
53  TCPSocket(const TCPSocket&);
54  TCPSocket& operator=(const TCPSocket&);
55 
56 public:
57 
63  TCPSocket(boost::asio::ip::tcp::socket& socket);
64 
71  TCPSocket(IOService& service);
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,
209  isc::util::OutputBufferPtr& outbuff);
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 
224 private:
225  // Two variables to hold the socket - a socket and a pointer to it. This
226  // handles the case where a socket is passed to the TCPSocket on
227  // construction, or where it is asked to manage its own socket.
228  boost::asio::ip::tcp::socket* socket_ptr_;
229  boost::asio::ip::tcp::socket& socket_;
230 
231  // TODO: Remove temporary buffer
232  // The current implementation copies the buffer passed to asyncSend() into
233  // a temporary buffer and precedes it with a two-byte count field. As
234  // ASIO should really be just about sending and receiving data, the TCP
235  // code should not do this. If the protocol using this requires a two-byte
236  // count, it should add it before calling this code. (This may be best
237  // achieved by altering isc::dns::buffer to have pairs of methods:
238  // getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
239  // methods taking into account a two-byte count field.)
240  //
241  // The option of sending the data in two operations, the count followed by
242  // the data was discounted as that would lead to two callbacks which would
243  // cause problems with the stackless coroutine code.
244  isc::util::OutputBufferPtr send_buffer_;
245 };
246 
247 // Constructor - caller manages socket
248 
249 template <typename C>
250 TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
251  socket_ptr_(NULL), socket_(socket), send_buffer_()
252 {
253 }
254 
255 // Constructor - create socket on the fly
256 
257 template <typename C>
259  socket_ptr_(new boost::asio::ip::tcp::socket(service.get_io_service())),
260  socket_(*socket_ptr_)
261 {
262 }
263 
264 // Destructor. Only delete the socket if we are managing it.
265 
266 template <typename C>
268 {
269  delete socket_ptr_;
270 }
271 
272 // Open the socket.
273 
274 template <typename C> void
275 TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
276  // If socket is open on this end but has been closed by the peer,
277  // we need to reconnect.
278  if (socket_.is_open() && !isUsable()) {
279  close();
280  }
281  // Ignore opens on already-open socket. Don't throw a failure because
282  // of uncertainties as to what precedes whan when using asynchronous I/O.
283  // At also allows us a treat a passed-in socket as a self-managed socket.
284  if (!socket_.is_open()) {
285  if (endpoint->getFamily() == AF_INET) {
286  socket_.open(boost::asio::ip::tcp::v4());
287  }
288  else {
289  socket_.open(boost::asio::ip::tcp::v6());
290  }
291 
292  // Set options on the socket:
293 
294  // Reuse address - allow the socket to bind to a port even if the port
295  // is in the TIMED_WAIT state.
296  socket_.set_option(boost::asio::socket_base::reuse_address(true));
297  }
298 
299  // Upconvert to a TCPEndpoint. We need to do this because although
300  // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
301  // contain a method for getting at the underlying endpoint type - that is in
303  assert(endpoint->getProtocol() == IPPROTO_TCP);
304  const TCPEndpoint* tcp_endpoint =
305  static_cast<const TCPEndpoint*>(endpoint);
306 
307  // Connect to the remote endpoint. On success, the handler will be
308  // called (with one argument - the length argument will default to
309  // zero).
310  socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
311 }
312 
313 // Send a message. Should never do this if the socket is not open, so throw
314 // an exception if this is the case.
315 
316 template <typename C> void
317 TCPSocket<C>::asyncSend(const void* data, size_t length, C& callback)
318 {
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 (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 
340 template <typename C> void
341 TCPSocket<C>::asyncSend(const void* data, size_t length,
342  const IOEndpoint*, C& callback)
343 {
344  if (socket_.is_open()) {
345 
346  // Need to copy the data into a temporary buffer and precede it with
347  // a two-byte count field.
348  // TODO: arrange for the buffer passed to be preceded by the count
349  try {
350  // Ensure it fits into 16 bits
351  uint16_t count = boost::numeric_cast<uint16_t>(length);
352 
353  // Copy data into a buffer preceded by the count field.
354  send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
355  send_buffer_->writeUint16(count);
356  send_buffer_->writeData(data, length);
357 
358  // ... and send it
359  socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
360  send_buffer_->getLength()), callback);
361  } catch (boost::numeric::bad_numeric_cast&) {
363  "attempt to send buffer larger than 64kB");
364  }
365 
366  } else {
368  "attempt to send on a TCP socket that is not open");
369  }
370 }
371 
372 // Receive a message. Note that the "offset" argument is used as an index
373 // into the buffer in order to decide where to put the data. It is up to the
374 // caller to initialize the data to zero
375 template <typename C> void
376 TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
377  IOEndpoint* endpoint, C& callback)
378 {
379  if (socket_.is_open()) {
380  // Upconvert to a TCPEndpoint. We need to do this because although
381  // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
382  // does not contain a method for getting at the underlying endpoint
383  // type - that is in the derived class and the two classes differ on
384  // return type.
385  assert(endpoint->getProtocol() == IPPROTO_TCP);
386  TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
387 
388  // Write the endpoint details from the communications link. Ideally
389  // we should make IOEndpoint assignable, but this runs in to all sorts
390  // of problems concerning the management of the underlying Boost
391  // endpoint (e.g. if it is not self-managed, is the copied one
392  // self-managed?) The most pragmatic solution is to let Boost take care
393  // of everything and copy details of the underlying endpoint.
394  tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
395 
396  // Ensure we can write into the buffer and if so, set the pointer to
397  // where the data will be written.
398  if (offset >= length) {
399  isc_throw(BufferOverflow, "attempt to read into area beyond end of "
400  "TCP receive buffer");
401  }
402  void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
403 
404  // ... and kick off the read.
405  socket_.async_receive(boost::asio::buffer(buffer_start, length - offset), callback);
406 
407  } else {
409  "attempt to receive from a TCP socket that is not open");
410  }
411 }
412 
413 // Is the receive complete?
414 
415 template <typename C> bool
416 TCPSocket<C>::processReceivedData(const void* staging, size_t length,
417  size_t& cumulative, size_t& offset,
418  size_t& expected,
420 {
421  // Point to the data in the staging buffer and note how much there is.
422  const uint8_t* data = static_cast<const uint8_t*>(staging);
423  size_t data_length = length;
424 
425  // Is the number is "expected" valid? It won't be unless we have received
426  // at least two bytes of data in total for this set of receives.
427  if (cumulative < 2) {
428 
429  // "expected" is not valid. Did this read give us enough data to
430  // work it out?
431  cumulative += length;
432  if (cumulative < 2) {
433 
434  // Nope, still not valid. This must have been the first packet and
435  // was only one byte long. Tell the fetch code to read the next
436  // packet into the staging buffer beyond the data that is already
437  // there so that the next time we are called we have a complete
438  // TCP count.
439  offset = cumulative;
440  return (false);
441  }
442 
443  // Have enough data to interpret the packet count, so do so now.
444  expected = isc::util::readUint16(data, cumulative);
445 
446  // We have two bytes less of data to process. Point to the start of the
447  // data and adjust the packet size. Note that at this point,
448  // "cumulative" is the true amount of data in the staging buffer, not
449  // "length".
450  data += 2;
451  data_length = cumulative - 2;
452  } else {
453 
454  // Update total amount of data received.
455  cumulative += length;
456  }
457 
458  // Regardless of anything else, the next read goes into the start of the
459  // staging buffer.
460  offset = 0;
461 
462  // Work out how much data we still have to put in the output buffer. (This
463  // could be zero if we have just interpreted the TCP count and that was
464  // set to zero.)
465  if (expected >= outbuff->getLength()) {
466 
467  // Still need data in the output packet. Copy what we can from the
468  // staging buffer to the output buffer.
469  size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
470  outbuff->writeData(data, copy_amount);
471  }
472 
473  // We can now say if we have all the data.
474  return (expected == outbuff->getLength());
475 }
476 
477 // Cancel I/O on the socket. No-op if the socket is not open.
478 
479 template <typename C> void
481  if (socket_.is_open()) {
482  socket_.cancel();
483  }
484 }
485 
486 // Close the socket down. Can only do this if the socket is open and we are
487 // managing it ourself.
488 
489 template <typename C> void
491  if (socket_.is_open() && socket_ptr_) {
492  socket_.close();
493  }
494 }
495 
496 } // namespace asiolink
497 } // namespace isc
498 
499 #endif // TCP_SOCKET_H
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
Defines the logger used by the top-level component of kea-dhcp-ddns.
uint16_t readUint16(const void *buffer, size_t length)
Read Unsigned 16-Bit Integer from Buffer.
Definition: io_utilities.h:28
boost::shared_ptr< OutputBuffer > OutputBufferPtr
Definition: buffer.h:596