Kea 2.5.8
tcp_connection.cc
Go to the documentation of this file.
1// Copyright (C) 2022-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#include <config.h>
8
10#include <tcp/tcp_connection.h>
12#include <tcp/tcp_log.h>
13#include <tcp/tcp_messages.h>
14#include <boost/make_shared.hpp>
15
16#include <iomanip>
17#include <sstream>
18#include <functional>
19
20using namespace isc::asiolink;
21namespace ph = std::placeholders;
22
23namespace {
24
28const size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
29
30}
31
32namespace isc {
33namespace tcp {
34
35void
36TcpResponse::consumeWireData(const size_t length) {
37 send_in_progress_ = true;
38 wire_data_.erase(wire_data_.begin(), wire_data_.begin() + length);
39}
40
41void
42TcpConnection::
43SocketCallback::operator()(boost::system::error_code ec, size_t length) {
44 if (ec.value() == boost::asio::error::operation_aborted) {
45 return;
46 }
47 callback_(ec, length);
48}
49
51 const TcpConnectionAcceptorPtr& acceptor,
52 const TlsContextPtr& tls_context,
53 TcpConnectionPool& connection_pool,
54 const TcpConnectionAcceptorCallback& acceptor_callback,
55 const TcpConnectionFilterCallback& connection_filter,
56 const long idle_timeout,
57 const size_t read_max /* = 32768 */)
58 : io_service_(io_service),
59 tls_context_(tls_context),
60 idle_timeout_(idle_timeout),
61 idle_timer_(io_service),
62 tcp_socket_(),
63 tls_socket_(),
64 acceptor_(acceptor),
65 connection_pool_(connection_pool),
66 acceptor_callback_(acceptor_callback),
67 connection_filter_(connection_filter),
68 read_max_(read_max),
69 input_buf_(read_max) {
70 if (!tls_context) {
72 } else {
74 tls_context));
75 }
76}
77
79 close();
80}
81
82void
83TcpConnection::shutdownCallback(const boost::system::error_code&) {
84 tls_socket_->close();
85}
86
87void
90 if (tcp_socket_) {
91 tcp_socket_->close();
92 return;
93 }
94
95 if (tls_socket_) {
96 // Create instance of the callback to close the socket.
97 SocketCallback cb(std::bind(&TcpConnection::shutdownCallback,
98 shared_from_this(),
99 ph::_1)); // error_code
100 tls_socket_->shutdown(cb);
101 return;
102 }
103
104 // Not reachable?
105 isc_throw(Unexpected, "internal error: unable to shutdown the socket");
106}
107
108void
111 if (tcp_socket_) {
112 tcp_socket_->close();
113 return;
114 }
115
116 if (tls_socket_) {
117 tls_socket_->close();
118 return;
119 }
120
121 // Not reachable?
122 isc_throw(Unexpected, "internal error: unable to close the socket");
123}
124
125void
127 try {
131 connection_pool_.shutdown(shared_from_this());
132 } catch (...) {
134 }
135}
136
137void
139 try {
143 connection_pool_.stop(shared_from_this());
144 } catch (...) {
146 }
147}
148
149void
151 // Create instance of the callback. It is safe to pass the local instance
152 // of the callback, because the underlying boost functions make copies
153 // as needed.
155 shared_from_this(),
156 ph::_1);
157 try {
158 TlsConnectionAcceptorPtr tls_acceptor =
159 boost::dynamic_pointer_cast<TlsConnectionAcceptor>(acceptor_);
160 if (!tls_acceptor) {
161 if (!tcp_socket_) {
162 isc_throw(Unexpected, "internal error: TCP socket is null");
163 }
164 acceptor_->asyncAccept(*tcp_socket_, cb);
165 } else {
166 if (!tls_socket_) {
167 isc_throw(Unexpected, "internal error: TLS socket is null");
168 }
169 tls_acceptor->asyncAccept(*tls_socket_, cb);
170 }
171 } catch (const std::exception& ex) {
172 isc_throw(TcpConnectionError, "unable to start accepting TCP "
173 "connections: " << ex.what());
174 }
175}
176
177void
179 // Skip the handshake if the socket is not a TLS one.
180 if (!tls_socket_) {
181 doRead();
182 return;
183 }
184
186
187 // Create instance of the callback. It is safe to pass the local instance
188 // of the callback, because the underlying boost functions make copies
189 // as needed.
190 SocketCallback cb(std::bind(&TcpConnection::handshakeCallback,
191 shared_from_this(),
192 ph::_1)); // error
193 try {
194 tls_socket_->handshake(cb);
195
196 } catch (const std::exception& ex) {
197 isc_throw(TcpConnectionError, "unable to perform TLS handshake: "
198 << ex.what());
199 }
200}
201
202void
204 try {
205 TCPEndpoint endpoint;
206
208
209 // Request hasn't been created if we are starting to read the
210 // new request.
211 if (!request) {
212 request = createRequest();
213 }
214
215 // Create instance of the callback. It is safe to pass the local instance
216 // of the callback, because the underlying std functions make copies
217 // as needed.
218 SocketCallback cb(std::bind(&TcpConnection::socketReadCallback,
219 shared_from_this(),
220 request,
221 ph::_1, // error
222 ph::_2)); // bytes_transferred
223 if (tcp_socket_) {
224 tcp_socket_->asyncReceive(static_cast<void*>(getInputBufData()),
225 getInputBufSize(), 0, &endpoint, cb);
226 return;
227 }
228
229 if (tls_socket_) {
230 tls_socket_->asyncReceive(static_cast<void*>(getInputBufData()),
231 getInputBufSize(), 0, &endpoint, cb);
232 return;
233 }
234 } catch (...) {
236 }
237}
238
239void
241 try {
242 if (response->wireDataAvail()) {
243 // Create instance of the callback. It is safe to pass the
244 // local instance of the callback, because the underlying
245 // std functions make copies as needed.
246 SocketCallback cb(std::bind(&TcpConnection::socketWriteCallback,
247 shared_from_this(),
248 response,
249 ph::_1, // error
250 ph::_2)); // bytes_transferred
251 if (tcp_socket_) {
255 tcp_socket_->asyncSend(response->getWireData(),
256 response->getWireDataSize(),
257 cb);
258 return;
259 }
260 if (tls_socket_) {
264 tls_socket_->asyncSend(response->getWireData(),
265 response->getWireDataSize(),
266 cb);
267 return;
268 }
269 } else {
270 // The connection remains open and we are done sending the response.
271 // If the response sent handler returns true then we should start the
272 // idle timer.
273 if (responseSent(response)) {
275 }
276 }
277 } catch (...) {
278 // The connection is dead and there can't be a pending write as
279 // they are in sequence.
281 }
282}
283
284void
286 doWrite(response);
287}
288
289
290void
291TcpConnection::acceptorCallback(const boost::system::error_code& ec) {
292 if (!acceptor_->isOpen()) {
293 return;
294 }
295
296 if (ec) {
298 }
299
300 // Stage a new connection to listen for next client.
302
303 if (!ec) {
304 try {
305 if (tcp_socket_ && tcp_socket_->getASIOSocket().is_open()) {
307 tcp_socket_->getASIOSocket().remote_endpoint();
308 } else if (tls_socket_ && tls_socket_->getASIOSocket().is_open()) {
310 tls_socket_->getASIOSocket().remote_endpoint();
311 }
312 } catch (...) {
313 // Let's it to fail later.
314 }
315
316 // In theory, we should not get here with an unopened socket
317 // but just in case, we'll check for NO_ENDPOINT.
318 if ((remote_endpoint_ == NO_ENDPOINT()) ||
325 return;
326 }
327
328 if (!tls_context_) {
332 .arg(static_cast<unsigned>(idle_timeout_/1000));
333 } else {
337 .arg(static_cast<unsigned>(idle_timeout_/1000));
338 }
339
340 doHandshake();
341 }
342}
343
344void
345TcpConnection::handshakeCallback(const boost::system::error_code& ec) {
346 if (ec) {
349 .arg(ec.message());
351 } else {
355 .arg(static_cast<unsigned>(idle_timeout_/1000));
356 doRead();
357 }
358}
359
360void
362 boost::system::error_code ec, size_t length) {
363 if (ec) {
364 // IO service has been stopped and the connection is probably
365 // going to be shutting down.
366 if (ec.value() == boost::asio::error::operation_aborted) {
367 return;
368
369 // EWOULDBLOCK and EAGAIN are special cases. Everything else is
370 // treated as fatal error.
371 } else if ((ec.value() != boost::asio::error::try_again) &&
372 (ec.value() != boost::asio::error::would_block)) {
374 return;
375
376 // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
377 // read something from the socket on the next attempt. Just make sure
378 // we don't try to read anything now in case there is any garbage
379 // passed in length.
380 } else {
381 length = 0;
382 }
383 }
384
385 // Data received, Restart the request timer.
387
388 TcpRequestPtr next_request = request;
389 if (length) {
392 .arg(length)
394 WireData input_data(input_buf_.begin(), input_buf_.begin() + length);
395 next_request = postData(request, input_data);
396 }
397
398 // Start next read.
399 doRead(next_request);
400}
401
404 size_t bytes_left = 0;
405 size_t length = input_data.size();
406 if (length) {
407 // Add data to the current request.
408 size_t bytes_used = request->postBuffer(static_cast<void*>(input_data.data()), length);
409 // Remove bytes used.
410 bytes_left = length - bytes_used;
411 input_data.erase(input_data.begin(), input_data.begin() + length);
412 }
413
414 if (request->needData()) {
415 // Current request is incomplete and we're out of data
416 // return the incomplete request and we'll read again.
417 return (request);
418 }
419
420 try {
424
425 // Request complete, stop the timer.
427
428 // Process the completed request.
429 requestReceived(request);
430 } catch (const std::exception& ex) {
433 .arg(ex.what());
434 }
435
436 // Create a new, empty request.
437 request = createRequest();
438 if (bytes_left) {
439 // The input buffer spanned messages. Recurse to post the remainder to the
440 // new request.
441 request = postData(request, input_data);
442 }
443
444 return (request);
445}
446
447void
449 boost::system::error_code ec, size_t length) {
450 if (ec) {
451 // IO service has been stopped and the connection is probably
452 // going to be shutting down.
453 if (ec.value() == boost::asio::error::operation_aborted) {
454 return;
455
456 // EWOULDBLOCK and EAGAIN are special cases. Everything else is
457 // treated as fatal error.
458 } else if ((ec.value() != boost::asio::error::try_again) &&
459 (ec.value() != boost::asio::error::would_block)) {
460 // The connection is dead and there can't be a pending write as
461 // they are in sequence.
463 return;
464
465 // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
466 // read something from the socket on the next attempt.
467 } else {
468 doWrite(response);
469 }
470 }
471
473 .arg(length)
475
476 // Since each response has its own wire data, it is not really
477 // possible that the number of bytes written is larger than the size
478 // of the buffer. But, let's be safe and set the length to the size
479 // of the buffer if that unexpected condition occurs.
480 if (length > response->getWireDataSize()) {
481 length = response->getWireDataSize();
482 }
483
484 // Eat the 'length' number of bytes from the output buffer and only
485 // leave the part of the response that hasn't been sent.
486 response->consumeWireData(length);
487
488 // Schedule the write of the unsent data.
489 doWrite(response);
490}
491
492void
496}
497
498void
503 // In theory we should shutdown first and stop/close after but
504 // it is better to put the connection management responsibility
505 // on the client... so simply drop idle connections.
507}
508
509std::string
511 if (remote_endpoint_ != NO_ENDPOINT()) {
512 return (remote_endpoint_.address().to_string());
513 }
514
515 return ("(unknown address)");
516}
517
518void
519TcpConnection::setReadMax(const size_t read_max) {
520 if (!read_max) {
521 isc_throw(BadValue, "TcpConnection read_max must be > 0");
522 }
523
524 read_max_ = read_max;
525 input_buf_.resize(read_max);
526}
527
528} // end of namespace isc::tcp
529} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown when an unexpected error condition occurs.
Generic error reported within TcpConnection class.
Pool of active TCP connections.
void stop(const TcpConnectionPtr &connection)
Removes a connection from the pool and stops it.
static std::atomic< uint64_t > rejected_counter_
Class/static rejected (by the accept filter) connection counter.
void shutdown(const TcpConnectionPtr &connection)
Removes a connection from the pool and shutdown it.
void doWrite(TcpResponsePtr response)
Starts asynchronous write to the socket.
virtual TcpRequestPtr createRequest()=0
Creates a new, empty request.
void shutdownCallback(const boost::system::error_code &ec)
Callback invoked when TLS shutdown is performed.
unsigned char * getInputBufData()
Returns pointer to the first byte of the input buffer.
void acceptorCallback(const boost::system::error_code &ec)
Local callback invoked when new connection is accepted.
void asyncAccept()
Asynchronously accepts new connection.
size_t getInputBufSize() const
Returns input buffer size.
virtual void shutdown()
Shutdown the socket.
boost::asio::ip::tcp::endpoint remote_endpoint_
Remote endpoint.
virtual void requestReceived(TcpRequestPtr request)=0
Processes a request once it has been completely received.
virtual void shutdownConnection()
Shuts down current connection.
virtual void stopThisConnection()
Stops current connection.
void setReadMax(const size_t read_max)
Sets the maximum number of bytes read during single socket read.
TcpConnectionAcceptorPtr acceptor_
Pointer to the TCP acceptor used to accept new connections.
virtual void close()
Closes the socket.
asiolink::TlsContextPtr tls_context_
TLS context.
WireData input_buf_
Buffer for a single socket read.
void socketReadCallback(TcpRequestPtr request, boost::system::error_code ec, size_t length)
Callback invoked when new data is received over the socket.
void setupIdleTimer()
Reset timer for detecting idle timeout in connections.
virtual void socketWriteCallback(TcpResponsePtr request, boost::system::error_code ec, size_t length)
Callback invoked when data is sent over the socket.
void asyncSendResponse(TcpResponsePtr response)
Sends TCP response asynchronously.
std::string getRemoteEndpointAddressAsText() const
returns remote address in textual form
TcpConnectionFilterCallback connection_filter_
External callback for filtering connections by IP address.
std::unique_ptr< asiolink::TCPSocket< SocketCallback > > tcp_socket_
TCP socket used by this connection.
void doRead(TcpRequestPtr request=TcpRequestPtr())
Starts asynchronous read from the socket.
static const boost::asio::ip::tcp::endpoint & NO_ENDPOINT()
Returns an empty end point.
virtual ~TcpConnection()
Destructor.
asiolink::IntervalTimer idle_timer_
Timer used to detect idle Timeout.
virtual bool responseSent(TcpResponsePtr response)=0
Determines behavior after a response has been sent.
TcpConnectionAcceptorCallback acceptor_callback_
External TCP acceptor callback.
size_t read_max_
Maximum bytes to read in a single socket read.
TcpConnectionPool & connection_pool_
Connection pool holding this connection.
void doHandshake()
Asynchronously performs TLS handshake.
TcpRequestPtr postData(TcpRequestPtr request, WireData &input_data)
Appends newly received raw data to the given request.
std::unique_ptr< asiolink::TLSSocket< SocketCallback > > tls_socket_
TLS socket used by this connection.
void handshakeCallback(const boost::system::error_code &ec)
Local callback invoked when TLS handshake is performed.
void idleTimeoutCallback()
Callback invoked when the client has been idle.
TcpConnection(const asiolink::IOServicePtr &io_service, const TcpConnectionAcceptorPtr &acceptor, const asiolink::TlsContextPtr &tls_context, TcpConnectionPool &connection_pool, const TcpConnectionAcceptorCallback &acceptor_callback, const TcpConnectionFilterCallback &connection_filter, const long idle_timeout, const size_t read_max=32768)
Constructor.
long idle_timeout_
Timeout after which the a TCP connection is shut down by the server.
WireData wire_data_
Buffer used for data in wire format data.
virtual void consumeWireData(const size_t length)
Erases n bytes from the beginning of the wire data.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
const int DBGLVL_TRACE_DETAIL_DATA
Trace data associated with detailed operations.
Definition: log_dbglevels.h:78
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:75
std::function< bool(const boost::asio::ip::tcp::endpoint &)> TcpConnectionFilterCallback
Type of the callback for filtering new connections by ip address.
boost::shared_ptr< TlsConnectionAcceptor > TlsConnectionAcceptorPtr
Type of shared pointer to TLS acceptors.
const isc::log::MessageID TCP_CONNECTION_STOP_FAILED
Definition: tcp_messages.h:20
const isc::log::MessageID TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED
Definition: tcp_messages.h:23
boost::shared_ptr< TcpConnectionAcceptor > TcpConnectionAcceptorPtr
Type of shared pointer to TCP acceptors.
const isc::log::MessageID TLS_REQUEST_RECEIVE_START
Definition: tcp_messages.h:31
const isc::log::MessageID TLS_CONNECTION_HANDSHAKE_FAILED
Definition: tcp_messages.h:29
const isc::log::MessageID TCP_CONNECTION_STOP
Definition: tcp_messages.h:19
const isc::log::MessageID TCP_DATA_SENT
Definition: tcp_messages.h:22
boost::shared_ptr< TcpRequest > TcpRequestPtr
Defines a smart pointer to a TcpRequest.
const isc::log::MessageID TCP_CONNECTION_REJECTED_BY_FILTER
Definition: tcp_messages.h:16
const isc::log::MessageID TLS_CONNECTION_HANDSHAKE_START
Definition: tcp_messages.h:30
boost::shared_ptr< TcpResponse > TcpResponsePtr
const isc::log::MessageID TLS_SERVER_RESPONSE_SEND
Definition: tcp_messages.h:32
const isc::log::MessageID TCP_DATA_RECEIVED
Definition: tcp_messages.h:21
const isc::log::MessageID TCP_REQUEST_RECEIVE_START
Definition: tcp_messages.h:26
std::vector< uint8_t > WireData
Defines a data structure for storing raw bytes of data on the wire.
isc::log::Logger tcp_logger("tcp")
Defines the logger used within libkea-tcp library.
Definition: tcp_log.h:18
const isc::log::MessageID TCP_SERVER_RESPONSE_SEND
Definition: tcp_messages.h:27
const isc::log::MessageID TCP_REQUEST_RECEIVED_FAILED
Definition: tcp_messages.h:25
const isc::log::MessageID TCP_CONNECTION_SHUTDOWN
Definition: tcp_messages.h:17
const isc::log::MessageID TCP_CONNECTION_SHUTDOWN_FAILED
Definition: tcp_messages.h:18
const isc::log::MessageID TCP_CLIENT_REQUEST_RECEIVED
Definition: tcp_messages.h:14
std::function< void(const boost::system::error_code &)> TcpConnectionAcceptorCallback
Type of the callback for the TCP acceptor used in this library.
Defines the logger used by the top-level component of kea-lfc.