Kea 2.5.5
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
19namespace isc {
20namespace asiolink {
21
26template <typename C>
27class TLSSocket : public IOAsioSocket<C>, private boost::noncopyable {
28public:
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,
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
226private:
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
260template <typename C>
261TLSSocket<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
268template <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
277template <typename C> void
278TLSSocket<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
312template <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
324template <typename C> void
325TLSSocket<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
347template <typename C> void
348TLSSocket<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
382template <typename C> void
383TLSSocket<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
423template <typename C> bool
424TLSSocket<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
488template <typename C> void
490 if (socket_.is_open()) {
491 socket_.cancel();
492 }
493}
494
495// TLS shutdown. Can be used for orderly close.
496
497template <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
509template <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
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
TLS API.
#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:603
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.