Kea  1.9.9-git
tls_socket.h
Go to the documentation of this file.
1 // Copyright (C) 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 TLS_SOCKET_H
8 #define TLS_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 
14 #include <asiolink/crypto_tls.h>
15 #include <asiolink/tcp_socket.h>
16 
17 #include <boost/noncopyable.hpp>
18 
19 namespace isc {
20 namespace asiolink {
21 
26 template <typename C>
27 class TLSSocket : public IOAsioSocket<C>, private boost::noncopyable {
28 public:
29 
36  TLSSocket(TlsStream<C>& stream);
37 
45  TLSSocket(IOService& service, TlsContextPtr context);
46 
48  virtual ~TLSSocket() { }
49 
51  virtual int getNative() const {
52 #if BOOST_VERSION < 106600
53  return (socket_.native());
54 #else
55  return (socket_.native_handle());
56 #endif
57  }
58 
60  virtual int getProtocol() const {
61  return (IPPROTO_TCP);
62  }
63 
67  virtual bool isOpenSynchronous() const {
68  return (false);
69  }
70 
77  bool isUsable() const {
78  // If the socket is open it doesn't mean that it is still
79  // usable. The connection could have been closed on the other
80  // end. We have to check if we can still use this socket.
81  if (socket_.is_open()) {
82  // Remember the current non blocking setting.
83  const bool non_blocking_orig = socket_.non_blocking();
84 
85  // Set the socket to non blocking mode. We're going to
86  // test if the socket returns would_block status on the
87  // attempt to read from it.
88  socket_.non_blocking(true);
89 
90  // Use receive with message peek flag to avoid removing
91  // the data awaiting to be read.
92  char data[2];
93  int err = 0;
94  int cc = recv(getNative(), data, sizeof(data), MSG_PEEK);
95  if (cc < 0) {
96  // Error case.
97  err = errno;
98  } else if (cc == 0) {
99  // End of file.
100  err = -1;
101  }
102 
103  // Revert the original non_blocking flag on the socket.
104  socket_.non_blocking(non_blocking_orig);
105 
106  // If the connection is alive we'd typically get
107  // would_block status code. If there are any data that
108  // haven't been read we may also get success status. We're
109  // guessing that try_again may also be returned by some
110  // implementations in some situations. Any other error
111  // code indicates a problem with the connection so we
112  // assume that the connection has been closed.
113  return ((err == 0) || (err == EAGAIN) || (err == EWOULDBLOCK));
114  }
115 
116  return (false);
117  }
118 
126  virtual void open(const IOEndpoint* endpoint, C& callback);
127 
135  virtual void handshake(C& callback);
136 
149  virtual void asyncSend(const void* data, size_t length,
150  const IOEndpoint* endpoint, C& callback);
151 
164  void asyncSend(const void* data, size_t length, C& callback);
165 
177  virtual void asyncReceive(void* data, size_t length, size_t offset,
178  IOEndpoint* endpoint, C& callback);
179 
195  virtual bool processReceivedData(const void* staging, size_t length,
196  size_t& cumulative, size_t& offset,
197  size_t& expected,
198  isc::util::OutputBufferPtr& outbuff);
199 
201  virtual void cancel();
202 
204  virtual void close();
205 
210  virtual void shutdown(C& callback);
211 
215  virtual typename TlsStream<C>::lowest_layer_type& getASIOSocket() const {
216  return (socket_);
217  }
218 
222  virtual TlsStream<C>& getTlsStream() const {
223  return (stream_);
224  }
225 
226 private:
230 
232  std::unique_ptr<TlsStream<C>> stream_ptr_;
233 
235  TlsStream<C>& stream_;
236 
238  typename TlsStream<C>::lowest_layer_type& socket_;
239 
253 
255  isc::util::OutputBufferPtr send_buffer_;
256 };
257 
258 // Constructor - caller manages socket.
259 
260 template <typename C>
261 TLSSocket<C>::TLSSocket(TlsStream<C>& stream) :
262  stream_ptr_(), stream_(stream),
263  socket_(stream_.lowest_layer()), send_buffer_() {
264 }
265 
266 // Constructor - create socket on the fly.
267 
268 template <typename C>
270  stream_ptr_(new TlsStream<C>(service, context)),
271  stream_(*stream_ptr_), socket_(stream_.lowest_layer()), send_buffer_()
272 {
273 }
274 
275 // Open the socket.
276 
277 template <typename C> void
278 TLSSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
279  // Ignore opens on already-open socket. Don't throw a failure because
280  // of uncertainties as to what precedes when using asynchronous I/O.
281  // Also allows us a treat a passed-in socket as a self-managed socket.
282  if (!socket_.is_open()) {
283  if (endpoint->getFamily() == AF_INET) {
284  socket_.open(boost::asio::ip::tcp::v4());
285  } else {
286  socket_.open(boost::asio::ip::tcp::v6());
287  }
288 
289  // Set options on the socket:
290 
291  // Reuse address - allow the socket to bind to a port even if the port
292  // is in the TIMED_WAIT state.
293  socket_.set_option(boost::asio::socket_base::reuse_address(true));
294  }
295 
296  // Upconvert to a TCPEndpoint. We need to do this because although
297  // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
298  // contain a method for getting at the underlying endpoint type - that is in
300  isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
301  const TCPEndpoint* tcp_endpoint =
302  static_cast<const TCPEndpoint*>(endpoint);
303 
304  // Connect to the remote endpoint. On success, the handler will be
305  // called (with one argument - the length argument will default to
306  // zero).
307  socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
308 }
309 
310 // Perform the handshake.
311 
312 template <typename C> void
314  if (!socket_.is_open()) {
315  isc_throw(SocketNotOpen, "attempt to perform handshake on "
316  "a TLS socket that is not open");
317  }
318  stream_.handshake(callback);
319 }
320 
321 // Send a message. Should never do this if the socket is not open, so throw
322 // an exception if this is the case.
323 
324 template <typename C> void
325 TLSSocket<C>::asyncSend(const void* data, size_t length, C& callback)
326 {
327  if (!socket_.is_open()) {
329  "attempt to send on a TLS socket that is not open");
330  }
331 
332  try {
333  send_buffer_.reset(new isc::util::OutputBuffer(length));
334  send_buffer_->writeData(data, length);
335 
336  // Send the data.
337  boost::asio::async_write(stream_,
338  boost::asio::buffer(send_buffer_->getData(),
339  send_buffer_->getLength()),
340  callback);
341  } catch (const boost::numeric::bad_numeric_cast&) {
343  "attempt to send buffer larger than 64kB");
344  }
345 }
346 
347 template <typename C> void
348 TLSSocket<C>::asyncSend(const void* data, size_t length,
349  const IOEndpoint*, C& callback)
350 {
351  if (!socket_.is_open()) {
353  "attempt to send on a TLS socket that is not open");
354  }
355 
359  try {
360  // Ensure it fits into 16 bits
361  uint16_t count = boost::numeric_cast<uint16_t>(length);
362 
363  // Copy data into a buffer preceded by the count field.
364  send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
365  send_buffer_->writeUint16(count);
366  send_buffer_->writeData(data, length);
367 
368  // ... and send it
369  boost::asio::async_write(stream_,
370  boost::asio::buffer(send_buffer_->getData(),
371  send_buffer_->getLength()),
372  callback);
373  } catch (const boost::numeric::bad_numeric_cast&) {
375  "attempt to send buffer larger than 64kB");
376  }
377 }
378 
379 // Receive a message. Note that the "offset" argument is used as an index
380 // into the buffer in order to decide where to put the data. It is up to the
381 // caller to initialize the data to zero
382 template <typename C> void
383 TLSSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
384  IOEndpoint* endpoint, C& callback)
385 {
386  if (!socket_.is_open()) {
388  "attempt to receive from a TLS socket that is not open");
389  }
390 
391  // Upconvert to a TCPEndpoint. We need to do this because although
392  // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
393  // does not contain a method for getting at the underlying endpoint
394  // type - that is in the derived class and the two classes differ on
395  // return type.
396  isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
397  TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
398 
399  // Write the endpoint details from the communications link. Ideally
400  // we should make IOEndpoint assignable, but this runs in to all sorts
401  // of problems concerning the management of the underlying Boost
402  // endpoint (e.g. if it is not self-managed, is the copied one
403  // self-managed?) The most pragmatic solution is to let Boost take care
404  // of everything and copy details of the underlying endpoint.
405  tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
406 
407  // Ensure we can write into the buffer and if so, set the pointer to
408  // where the data will be written.
409  if (offset >= length) {
410  isc_throw(BufferOverflow, "attempt to read into area beyond end of "
411  "TCP receive buffer");
412  }
413  void* buffer_start =
414  static_cast<void*>(static_cast<uint8_t*>(data) + offset);
415 
416  // ... and kick off the read.
417  stream_.async_read_some(boost::asio::buffer(buffer_start, length - offset),
418  callback);
419 }
420 
421 // Is the receive complete?
422 
423 template <typename C> bool
424 TLSSocket<C>::processReceivedData(const void* staging, size_t length,
425  size_t& cumulative, size_t& offset,
426  size_t& expected,
428 {
429  // Point to the data in the staging buffer and note how much there is.
430  const uint8_t* data = static_cast<const uint8_t*>(staging);
431  size_t data_length = length;
432 
433  // Is the number is "expected" valid? It won't be unless we have received
434  // at least two bytes of data in total for this set of receives.
435  if (cumulative < 2) {
436 
437  // "expected" is not valid. Did this read give us enough data to
438  // work it out?
439  cumulative += length;
440  if (cumulative < 2) {
441 
442  // Nope, still not valid. This must have been the first packet and
443  // was only one byte long. Tell the fetch code to read the next
444  // packet into the staging buffer beyond the data that is already
445  // there so that the next time we are called we have a complete
446  // TCP count.
447  offset = cumulative;
448  return (false);
449  }
450 
451  // Have enough data to interpret the packet count, so do so now.
452  expected = isc::util::readUint16(data, cumulative);
453 
454  // We have two bytes less of data to process. Point to the start of the
455  // data and adjust the packet size. Note that at this point,
456  // "cumulative" is the true amount of data in the staging buffer, not
457  // "length".
458  data += 2;
459  data_length = cumulative - 2;
460  } else {
461 
462  // Update total amount of data received.
463  cumulative += length;
464  }
465 
466  // Regardless of anything else, the next read goes into the start of the
467  // staging buffer.
468  offset = 0;
469 
470  // Work out how much data we still have to put in the output buffer. (This
471  // could be zero if we have just interpreted the TCP count and that was
472  // set to zero.)
473  if (expected >= outbuff->getLength()) {
474 
475  // Still need data in the output packet. Copy what we can from the
476  // staging buffer to the output buffer.
477  size_t copy_amount = std::min(expected - outbuff->getLength(),
478  data_length);
479  outbuff->writeData(data, copy_amount);
480  }
481 
482  // We can now say if we have all the data.
483  return (expected == outbuff->getLength());
484 }
485 
486 // Cancel I/O on the socket. No-op if the socket is not open.
487 
488 template <typename C> void
490  if (socket_.is_open()) {
491  socket_.cancel();
492  }
493 }
494 
495 // TLS shutdown. Can be used for orderly close.
496 
497 template <typename C> void
499  if (!socket_.is_open()) {
500  isc_throw(SocketNotOpen, "attempt to perform shutdown on "
501  "a TLS socket that is not open");
502  }
503  stream_.shutdown(callback);
504 }
505 
506 // Close the socket down. Can only do this if the socket is open and we are
507 // managing it ourself.
508 
509 template <typename C> void
511  if (socket_.is_open() && stream_ptr_) {
512  socket_.close();
513  }
514 }
515 
516 } // namespace asiolink
517 } // namespace isc
518 
519 #endif // TLS_SOCKET_H
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition: isc_assert.h:18
#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.
TLS API.
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:599