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