Kea 2.7.7
tls_socket.h
Go to the documentation of this file.
1// Copyright (C) 2021-2025 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 return (socket_.native_handle());
54 }
55
57 virtual int getProtocol() const {
58 return (IPPROTO_TCP);
59 }
60
64 virtual bool isOpenSynchronous() const {
65 return (false);
66 }
67
74 bool isUsable() const {
75 // If the socket is open it doesn't mean that it is still
76 // usable. The connection could have been closed on the other
77 // end. We have to check if we can still use this socket.
78 if (socket_.is_open()) {
79 // Remember the current non blocking setting.
80 const bool non_blocking_orig = socket_.non_blocking();
81
82 // Set the socket to non blocking mode. We're going to
83 // test if the socket returns would_block status on the
84 // attempt to read from it.
85 socket_.non_blocking(true);
86
87 // Use receive with message peek flag to avoid removing
88 // the data awaiting to be read.
89 char data[2];
90 int err = 0;
91 int cc = recv(getNative(), data, sizeof(data), MSG_PEEK);
92 if (cc < 0) {
93 // Error case.
94 err = errno;
95 } else if (cc == 0) {
96 // End of file.
97 err = -1;
98 }
99
100 // Revert the original non_blocking flag on the socket.
101 socket_.non_blocking(non_blocking_orig);
102
103 // If the connection is alive we'd typically get
104 // would_block status code. If there are any data that
105 // haven't been read we may also get success status. We're
106 // guessing that try_again may also be returned by some
107 // implementations in some situations. Any other error
108 // code indicates a problem with the connection so we
109 // assume that the connection has been closed.
110 return ((err == 0) || (err == EAGAIN) || (err == EWOULDBLOCK));
111 }
112
113 return (false);
114 }
115
123 virtual void open(const IOEndpoint* endpoint, C& callback);
124
132 virtual void handshake(C& callback);
133
146 virtual void asyncSend(const void* data, size_t length,
147 const IOEndpoint* endpoint, C& callback);
148
161 void asyncSend(const void* data, size_t length, C& callback);
162
174 virtual void asyncReceive(void* data, size_t length, size_t offset,
175 IOEndpoint* endpoint, C& callback);
176
192 virtual bool processReceivedData(const void* staging, size_t length,
193 size_t& cumulative, size_t& offset,
194 size_t& expected,
196
198 virtual void cancel();
199
201 virtual void close();
202
207 virtual void shutdown(C& callback);
208
212 virtual typename TlsStream<C>::lowest_layer_type& getASIOSocket() const {
213 return (socket_);
214 }
215
219 virtual TlsStream<C>& getTlsStream() const {
220 return (stream_);
221 }
222
223private:
225 IOServicePtr io_service_;
226
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 : io_service_(io_service),
271 stream_ptr_(new TlsStream<C>(io_service, context)),
272 stream_(*stream_ptr_), socket_(stream_.lowest_layer()), send_buffer_() {
273}
274
275// Destructor.
276
277template <typename C>
279 close();
280}
281
282// Open the socket.
283
284template <typename C> void
285TLSSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
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 } else {
293 socket_.open(boost::asio::ip::tcp::v6());
294 }
295
296 // Set options on the socket:
297
298 // Reuse address - allow the socket to bind to a port even if the port
299 // is in the TIMED_WAIT state.
300 socket_.set_option(boost::asio::socket_base::reuse_address(true));
301 }
302
303 // Upconvert to a TCPEndpoint. We need to do this because although
304 // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
305 // contain a method for getting at the underlying endpoint type - that is in
307 isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
308 const TCPEndpoint* tcp_endpoint =
309 static_cast<const TCPEndpoint*>(endpoint);
310
311 // Connect to the remote endpoint. On success, the handler will be
312 // called (with one argument - the length argument will default to
313 // zero).
314 socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
315}
316
317// Perform the handshake.
318
319template <typename C> void
321 if (!socket_.is_open()) {
322 isc_throw(SocketNotOpen, "attempt to perform handshake on "
323 "a TLS socket that is not open");
324 }
325 stream_.handshake(callback);
326}
327
328// Send a message. Should never do this if the socket is not open, so throw
329// an exception if this is the case.
330
331template <typename C> void
332TLSSocket<C>::asyncSend(const void* data, size_t length, C& callback) {
333 if (!socket_.is_open()) {
335 "attempt to send on a TLS socket that is not open");
336 }
337
338 try {
339 send_buffer_.reset(new isc::util::OutputBuffer(length));
340 send_buffer_->writeData(data, length);
341
342 // Send the data.
343 boost::asio::async_write(stream_,
344 boost::asio::buffer(send_buffer_->getData(),
345 send_buffer_->getLength()),
346 callback);
347 } catch (const boost::numeric::bad_numeric_cast&) {
349 "attempt to send buffer larger than 64kB");
350 }
351}
352
353template <typename C> void
354TLSSocket<C>::asyncSend(const void* data, size_t length,
355 const IOEndpoint*, C& callback) {
356 if (!socket_.is_open()) {
358 "attempt to send on a TLS socket that is not open");
359 }
360
364 try {
365 // Ensure it fits into 16 bits
366 uint16_t count = boost::numeric_cast<uint16_t>(length);
367
368 // Copy data into a buffer preceded by the count field.
369 send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
370 send_buffer_->writeUint16(count);
371 send_buffer_->writeData(data, length);
372
373 // ... and send it
374 boost::asio::async_write(stream_,
375 boost::asio::buffer(send_buffer_->getData(),
376 send_buffer_->getLength()),
377 callback);
378 } catch (const boost::numeric::bad_numeric_cast&) {
380 "attempt to send buffer larger than 64kB");
381 }
382}
383
384// Receive a message. Note that the "offset" argument is used as an index
385// into the buffer in order to decide where to put the data. It is up to the
386// caller to initialize the data to zero
387template <typename C> void
388TLSSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
389 IOEndpoint* endpoint, C& callback) {
390 if (!socket_.is_open()) {
392 "attempt to receive from a TLS socket that is not open");
393 }
394
395 // Upconvert to a TCPEndpoint. We need to do this because although
396 // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
397 // does not contain a method for getting at the underlying endpoint
398 // type - that is in the derived class and the two classes differ on
399 // return type.
400 isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
401 TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
402
403 // Write the endpoint details from the communications link. Ideally
404 // we should make IOEndpoint assignable, but this runs in to all sorts
405 // of problems concerning the management of the underlying Boost
406 // endpoint (e.g. if it is not self-managed, is the copied one
407 // self-managed?) The most pragmatic solution is to let Boost take care
408 // of everything and copy details of the underlying endpoint.
409 tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
410
411 // Ensure we can write into the buffer and if so, set the pointer to
412 // where the data will be written.
413 if (offset >= length) {
414 isc_throw(BufferOverflow, "attempt to read into area beyond end of "
415 "TCP receive buffer");
416 }
417 void* buffer_start =
418 static_cast<void*>(static_cast<uint8_t*>(data) + offset);
419
420 // ... and kick off the read.
421 stream_.async_read_some(boost::asio::buffer(buffer_start, length - offset),
422 callback);
423}
424
425// Is the receive complete?
426
427template <typename C> bool
428TLSSocket<C>::processReceivedData(const void* staging, size_t length,
429 size_t& cumulative, size_t& offset,
430 size_t& expected,
432 // Point to the data in the staging buffer and note how much there is.
433 const uint8_t* data = static_cast<const uint8_t*>(staging);
434 size_t data_length = length;
435
436 // Is the number is "expected" valid? It won't be unless we have received
437 // at least two bytes of data in total for this set of receives.
438 if (cumulative < 2) {
439
440 // "expected" is not valid. Did this read give us enough data to
441 // work it out?
442 cumulative += length;
443 if (cumulative < 2) {
444
445 // Nope, still not valid. This must have been the first packet and
446 // was only one byte long. Tell the fetch code to read the next
447 // packet into the staging buffer beyond the data that is already
448 // there so that the next time we are called we have a complete
449 // TCP count.
450 offset = cumulative;
451 return (false);
452 }
453
454 // Have enough data to interpret the packet count, so do so now.
455 expected = isc::util::readUint16(data, cumulative);
456
457 // We have two bytes less of data to process. Point to the start of the
458 // data and adjust the packet size. Note that at this point,
459 // "cumulative" is the true amount of data in the staging buffer, not
460 // "length".
461 data += 2;
462 data_length = cumulative - 2;
463 } else {
464
465 // Update total amount of data received.
466 cumulative += length;
467 }
468
469 // Regardless of anything else, the next read goes into the start of the
470 // staging buffer.
471 offset = 0;
472
473 // Work out how much data we still have to put in the output buffer. (This
474 // could be zero if we have just interpreted the TCP count and that was
475 // set to zero.)
476 if (expected >= outbuff->getLength()) {
477
478 // Still need data in the output packet. Copy what we can from the
479 // staging buffer to the output buffer.
480 size_t copy_amount = std::min(expected - outbuff->getLength(),
481 data_length);
482 outbuff->writeData(data, copy_amount);
483 }
484
485 // We can now say if we have all the data.
486 return (expected == outbuff->getLength());
487}
488
489// Cancel I/O on the socket. No-op if the socket is not open.
490
491template <typename C> void
493 if (socket_.is_open()) {
494 socket_.cancel();
495 }
496}
497
498// TLS shutdown. Can be used for orderly close.
499
500template <typename C> void
502 if (!socket_.is_open()) {
503 isc_throw(SocketNotOpen, "attempt to perform shutdown on "
504 "a TLS socket that is not open");
505 }
506 stream_.shutdown(callback);
507}
508
509// Close the socket down. Can only do this if the socket is open and we are
510// managing it ourself.
511
512template <typename C> void
514 if (socket_.is_open() && stream_ptr_) {
515 socket_.close();
516 }
517}
518
519} // namespace asiolink
520} // namespace isc
521
522#endif // TLS_SOCKET_H
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition buffer.h:346
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:574
Defines the logger used by the top-level component of kea-lfc.