Kea 3.3.3
tls_socket.h
Go to the documentation of this file.
1// Copyright (C) 2021-2026 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
242};
243
244// Constructor - caller manages socket.
245
246template <typename C>
247TLSSocket<C>::TLSSocket(TlsStream<C>& stream) :
248 stream_ptr_(), stream_(stream),
249 socket_(stream_.lowest_layer()), header_() {
250}
251
252// Constructor - create socket on the fly.
253
254template <typename C>
256 : io_service_(io_service),
257 stream_ptr_(new TlsStream<C>(io_service, context)),
258 stream_(*stream_ptr_), socket_(stream_.lowest_layer()), header_() {
259}
260
261// Destructor.
262
263template <typename C>
267
268// Open the socket.
269
270template <typename C> void
271TLSSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
272 // Ignore opens on already-open socket. Don't throw a failure because
273 // of uncertainties as to what precedes when using asynchronous I/O.
274 // Also allows us a treat a passed-in socket as a self-managed socket.
275 if (!socket_.is_open()) {
276 if (endpoint->getFamily() == AF_INET) {
277 socket_.open(boost::asio::ip::tcp::v4());
278 } else {
279 socket_.open(boost::asio::ip::tcp::v6());
280 }
281
282 // Set options on the socket:
283
284 // Reuse address - allow the socket to bind to a port even if the port
285 // is in the TIMED_WAIT state.
286 socket_.set_option(boost::asio::socket_base::reuse_address(true));
287 }
288
289 // Upconvert to a TCPEndpoint. We need to do this because although
290 // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
291 // contain a method for getting at the underlying endpoint type - that is in
293 isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
294 const TCPEndpoint* tcp_endpoint =
295 static_cast<const TCPEndpoint*>(endpoint);
296
297 // Connect to the remote endpoint. On success, the handler will be
298 // called (with one argument - the length argument will default to
299 // zero).
300 socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
301}
302
303// Perform the handshake.
304
305template <typename C> void
307 if (!socket_.is_open()) {
308 isc_throw(SocketNotOpen, "attempt to perform handshake on "
309 "a TLS socket that is not open");
310 }
311 stream_.handshake(callback);
312}
313
314// Send a message. Should never do this if the socket is not open, so throw
315// an exception if this is the case.
316//
317// This uses boost asio buffers which do not own the data so it is
318// the responsibility of the caller to keep the data valid.
319// The header holding the count is a member of the socket object.
320// Boost asio buffers themselves can be released after boot asio calls.
321
322template <typename C> void
323TLSSocket<C>::asyncSend(const void* data, size_t length, C& callback) {
324 if (!socket_.is_open()) {
326 "attempt to send on a TLS socket that is not open");
327 }
328
329 // Send the data.
330 stream_.async_write_some(boost::asio::buffer(data, length), callback);
331}
332
333template <typename C> void
334TLSSocket<C>::asyncSend(const void* data, size_t length,
335 const IOEndpoint*, C& callback) {
336 if (!socket_.is_open()) {
338 "attempt to send on a TLS socket that is not open");
339 }
340
343 try {
345 uint16_t count = boost::numeric_cast<uint16_t>(length);
346 header_.reset(new isc::util::OutputBuffer(2));
347 header_->writeUint16(count);
348 std::vector<boost::asio::const_buffer> buffers;
349
350 // Prepare a buffer sequence.
351 buffers.push_back(boost::asio::buffer(header_->getData(),
352 header_->getLength()));
353 buffers.push_back(boost::asio::buffer(data, length));
354
355 // ... and send it
356 stream_.async_write_some(buffers, callback);
357 } catch (const boost::numeric::bad_numeric_cast&) {
359 "attempt to send buffer larger than 64kB");
360 }
361}
362
363// Receive a message. Note that the "offset" argument is used as an index
364// into the buffer in order to decide where to put the data. It is up to the
365// caller to initialize the data to zero
366template <typename C> void
367TLSSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
368 IOEndpoint* endpoint, C& callback) {
369 if (!socket_.is_open()) {
371 "attempt to receive from a TLS socket that is not open");
372 }
373
374 // Upconvert to a TCPEndpoint. We need to do this because although
375 // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
376 // does not contain a method for getting at the underlying endpoint
377 // type - that is in the derived class and the two classes differ on
378 // return type.
379 isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
380 TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
381
382 // Write the endpoint details from the communications link. Ideally
383 // we should make IOEndpoint assignable, but this runs in to all sorts
384 // of problems concerning the management of the underlying Boost
385 // endpoint (e.g. if it is not self-managed, is the copied one
386 // self-managed?) The most pragmatic solution is to let Boost take care
387 // of everything and copy details of the underlying endpoint.
388 tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
389
390 // Ensure we can write into the buffer and if so, set the pointer to
391 // where the data will be written.
392 if (offset >= length) {
393 isc_throw(BufferOverflow, "attempt to read into area beyond end of "
394 "TCP receive buffer");
395 }
396 void* buffer_start =
397 static_cast<void*>(static_cast<uint8_t*>(data) + offset);
398
399 // ... and kick off the read.
400 stream_.async_read_some(boost::asio::buffer(buffer_start, length - offset),
401 callback);
402}
403
404// Is the receive complete?
405
406template <typename C> bool
407TLSSocket<C>::processReceivedData(const void* staging, size_t length,
408 size_t& cumulative, size_t& offset,
409 size_t& expected,
411 // Point to the data in the staging buffer and note how much there is.
412 const uint8_t* data = static_cast<const uint8_t*>(staging);
413 size_t data_length = length;
414
415 // Is the number is "expected" valid? It won't be unless we have received
416 // at least two bytes of data in total for this set of receives.
417 if (cumulative < 2) {
418
419 // "expected" is not valid. Did this read give us enough data to
420 // work it out?
421 cumulative += length;
422 if (cumulative < 2) {
423
424 // Nope, still not valid. This must have been the first packet and
425 // was only one byte long. Tell the fetch code to read the next
426 // packet into the staging buffer beyond the data that is already
427 // there so that the next time we are called we have a complete
428 // TCP count.
429 offset = cumulative;
430 return (false);
431 }
432
433 // Have enough data to interpret the packet count, so do so now.
434 expected = isc::util::readUint16(data, cumulative);
435
436 // We have two bytes less of data to process. Point to the start of the
437 // data and adjust the packet size. Note that at this point,
438 // "cumulative" is the true amount of data in the staging buffer, not
439 // "length".
440 data += 2;
441 data_length = cumulative - 2;
442 } else {
443
444 // Update total amount of data received.
445 cumulative += length;
446 }
447
448 // Regardless of anything else, the next read goes into the start of the
449 // staging buffer.
450 offset = 0;
451
452 // Work out how much data we still have to put in the output buffer. (This
453 // could be zero if we have just interpreted the TCP count and that was
454 // set to zero.)
455 if (expected >= outbuff->getLength()) {
456
457 // Still need data in the output packet. Copy what we can from the
458 // staging buffer to the output buffer.
459 size_t copy_amount = std::min(expected - outbuff->getLength(),
460 data_length);
461 outbuff->writeData(data, copy_amount);
462 }
463
464 // We can now say if we have all the data.
465 return (expected == outbuff->getLength());
466}
467
468// Cancel I/O on the socket. No-op if the socket is not open.
469
470template <typename C> void
472 if (socket_.is_open()) {
473 socket_.cancel();
474 }
475}
476
477// TLS shutdown. Can be used for orderly close.
478
479template <typename C> void
481 if (!socket_.is_open()) {
482 isc_throw(SocketNotOpen, "attempt to perform shutdown on "
483 "a TLS socket that is not open");
484 }
485 stream_.shutdown(callback);
486}
487
488// Close the socket down. Can only do this if the socket is open and we are
489// managing it ourself.
490
491template <typename C> void
493 if (socket_.is_open() && stream_ptr_) {
494 socket_.close();
495 }
496}
497
498} // namespace asiolink
499} // namespace isc
500
501#endif // TLS_SOCKET_H
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition buffer.h:355
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:583
Defines the logger used by the top-level component of kea-lfc.