Kea 2.7.0
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 isc {
24namespace tcp {
25
26void
27TcpResponse::consumeWireData(const size_t length) {
28 send_in_progress_ = true;
29 wire_data_.erase(wire_data_.begin(), wire_data_.begin() + length);
30}
31
32void
33TcpConnection::
34SocketCallback::operator()(boost::system::error_code ec, size_t length) {
35 if (ec.value() == boost::asio::error::operation_aborted) {
36 return;
37 }
38 callback_(ec, length);
39}
40
42 const TcpConnectionAcceptorPtr& acceptor,
43 const TlsContextPtr& tls_context,
44 TcpConnectionPool& connection_pool,
45 const TcpConnectionAcceptorCallback& acceptor_callback,
46 const TcpConnectionFilterCallback& connection_filter,
47 const long idle_timeout,
48 const size_t read_max /* = 32768 */)
49 : io_service_(io_service),
50 tls_context_(tls_context),
51 idle_timeout_(idle_timeout),
52 idle_timer_(io_service),
53 tcp_socket_(),
54 tls_socket_(),
55 acceptor_(acceptor),
56 connection_pool_(connection_pool),
57 acceptor_callback_(acceptor_callback),
58 connection_filter_(connection_filter),
59 read_max_(read_max),
60 input_buf_(read_max) {
61 if (!tls_context) {
63 } else {
65 tls_context));
66 }
67}
68
72
73void
74TcpConnection::shutdownCallback(const boost::system::error_code&) {
75 tls_socket_->close();
76}
77
78void
81 if (tcp_socket_) {
82 tcp_socket_->close();
83 return;
84 }
85
86 if (tls_socket_) {
87 // Create instance of the callback to close the socket.
88 SocketCallback cb(std::bind(&TcpConnection::shutdownCallback,
89 shared_from_this(),
90 ph::_1)); // error_code
91 tls_socket_->shutdown(cb);
92 return;
93 }
94
95 // Not reachable?
96 isc_throw(Unexpected, "internal error: unable to shutdown the socket");
97}
98
99void
102 if (tcp_socket_) {
103 tcp_socket_->close();
104 return;
105 }
106
107 if (tls_socket_) {
108 tls_socket_->close();
109 return;
110 }
111
112 // Not reachable?
113 isc_throw(Unexpected, "internal error: unable to close the socket");
114}
115
116void
127
128void
139
140void
142 // Create instance of the callback. It is safe to pass the local instance
143 // of the callback, because the underlying boost functions make copies
144 // as needed.
146 shared_from_this(),
147 ph::_1);
148 try {
149 TlsConnectionAcceptorPtr tls_acceptor =
150 boost::dynamic_pointer_cast<TlsConnectionAcceptor>(acceptor_);
151 if (!tls_acceptor) {
152 if (!tcp_socket_) {
153 isc_throw(Unexpected, "internal error: TCP socket is null");
154 }
155 acceptor_->asyncAccept(*tcp_socket_, cb);
156 } else {
157 if (!tls_socket_) {
158 isc_throw(Unexpected, "internal error: TLS socket is null");
159 }
160 tls_acceptor->asyncAccept(*tls_socket_, cb);
161 }
162 } catch (const std::exception& ex) {
163 isc_throw(TcpConnectionError, "unable to start accepting TCP "
164 "connections: " << ex.what());
165 }
166}
167
168void
170 // Skip the handshake if the socket is not a TLS one.
171 if (!tls_socket_) {
172 doRead();
173 return;
174 }
175
177
178 // Create instance of the callback. It is safe to pass the local instance
179 // of the callback, because the underlying boost functions make copies
180 // as needed.
181 SocketCallback cb(std::bind(&TcpConnection::handshakeCallback,
182 shared_from_this(),
183 ph::_1)); // error
184 try {
185 tls_socket_->handshake(cb);
186
187 } catch (const std::exception& ex) {
188 isc_throw(TcpConnectionError, "unable to perform TLS handshake: "
189 << ex.what());
190 }
191}
192
193void
195 try {
196 TCPEndpoint endpoint;
197
199
200 // Request hasn't been created if we are starting to read the
201 // new request.
202 if (!request) {
203 request = createRequest();
204 }
205
206 // Create instance of the callback. It is safe to pass the local instance
207 // of the callback, because the underlying std functions make copies
208 // as needed.
209 SocketCallback cb(std::bind(&TcpConnection::socketReadCallback,
210 shared_from_this(),
211 request,
212 ph::_1, // error
213 ph::_2)); // bytes_transferred
214 if (tcp_socket_) {
215 tcp_socket_->asyncReceive(static_cast<void*>(getInputBufData()),
216 getInputBufSize(), 0, &endpoint, cb);
217 return;
218 }
219
220 if (tls_socket_) {
221 tls_socket_->asyncReceive(static_cast<void*>(getInputBufData()),
222 getInputBufSize(), 0, &endpoint, cb);
223 return;
224 }
225 } catch (...) {
227 }
228}
229
230void
232 try {
233 if (response->wireDataAvail()) {
234 // Create instance of the callback. It is safe to pass the
235 // local instance of the callback, because the underlying
236 // std functions make copies as needed.
237 SocketCallback cb(std::bind(&TcpConnection::socketWriteCallback,
238 shared_from_this(),
239 response,
240 ph::_1, // error
241 ph::_2)); // bytes_transferred
242 if (tcp_socket_) {
246 tcp_socket_->asyncSend(response->getWireData(),
247 response->getWireDataSize(),
248 cb);
249 return;
250 }
251 if (tls_socket_) {
255 tls_socket_->asyncSend(response->getWireData(),
256 response->getWireDataSize(),
257 cb);
258 return;
259 }
260 } else {
261 // The connection remains open and we are done sending the response.
262 // If the response sent handler returns true then we should start the
263 // idle timer.
264 if (responseSent(response)) {
266 }
267 }
268 } catch (...) {
269 // The connection is dead and there can't be a pending write as
270 // they are in sequence.
272 }
273}
274
275void
279
280
281void
282TcpConnection::acceptorCallback(const boost::system::error_code& ec) {
283 if (!acceptor_->isOpen()) {
284 return;
285 }
286
287 if (ec) {
289 }
290
291 // Stage a new connection to listen for next client.
293
294 if (!ec) {
295 try {
296 if (tcp_socket_ && tcp_socket_->getASIOSocket().is_open()) {
298 tcp_socket_->getASIOSocket().remote_endpoint();
299 } else if (tls_socket_ && tls_socket_->getASIOSocket().is_open()) {
301 tls_socket_->getASIOSocket().remote_endpoint();
302 }
303 } catch (...) {
304 // Let's it to fail later.
305 }
306
307 // In theory, we should not get here with an unopened socket
308 // but just in case, we'll check for NO_ENDPOINT.
309 if ((remote_endpoint_ == NO_ENDPOINT()) ||
316 return;
317 }
318
319 if (!tls_context_) {
323 .arg(static_cast<unsigned>(idle_timeout_/1000));
324 } else {
328 .arg(static_cast<unsigned>(idle_timeout_/1000));
329 }
330
331 doHandshake();
332 }
333}
334
335void
336TcpConnection::handshakeCallback(const boost::system::error_code& ec) {
337 if (ec) {
340 .arg(ec.message());
342 } else {
346 .arg(static_cast<unsigned>(idle_timeout_/1000));
347 doRead();
348 }
349}
350
351void
353 boost::system::error_code ec, size_t length) {
354 if (ec) {
355 // IO service has been stopped and the connection is probably
356 // going to be shutting down.
357 if (ec.value() == boost::asio::error::operation_aborted) {
358 return;
359
360 // EWOULDBLOCK and EAGAIN are special cases. Everything else is
361 // treated as fatal error.
362 } else if ((ec.value() != boost::asio::error::try_again) &&
363 (ec.value() != boost::asio::error::would_block)) {
365 return;
366
367 // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
368 // read something from the socket on the next attempt. Just make sure
369 // we don't try to read anything now in case there is any garbage
370 // passed in length.
371 } else {
372 length = 0;
373 }
374 }
375
376 // Data received, Restart the request timer.
378
379 TcpRequestPtr next_request = request;
380 if (length) {
383 .arg(length)
385 WireData input_data(input_buf_.begin(), input_buf_.begin() + length);
386 next_request = postData(request, input_data);
387 }
388
389 // Start next read.
390 doRead(next_request);
391}
392
395 size_t bytes_left = 0;
396 size_t length = input_data.size();
397 if (length) {
398 // Add data to the current request.
399 size_t bytes_used = request->postBuffer(static_cast<void*>(input_data.data()), length);
400 // Remove bytes used.
401 bytes_left = length - bytes_used;
402 input_data.erase(input_data.begin(), input_data.begin() + length);
403 }
404
405 if (request->needData()) {
406 // Current request is incomplete and we're out of data
407 // return the incomplete request and we'll read again.
408 return (request);
409 }
410
411 try {
415
416 // Request complete, stop the timer.
418
419 // Process the completed request.
420 requestReceived(request);
421 } catch (const std::exception& ex) {
424 .arg(ex.what());
425 }
426
427 // Create a new, empty request.
428 request = createRequest();
429 if (bytes_left) {
430 // The input buffer spanned messages. Recurse to post the remainder to the
431 // new request.
432 request = postData(request, input_data);
433 }
434
435 return (request);
436}
437
438void
440 boost::system::error_code ec, size_t length) {
441 if (ec) {
442 // IO service has been stopped and the connection is probably
443 // going to be shutting down.
444 if (ec.value() == boost::asio::error::operation_aborted) {
445 return;
446
447 // EWOULDBLOCK and EAGAIN are special cases. Everything else is
448 // treated as fatal error.
449 } else if ((ec.value() != boost::asio::error::try_again) &&
450 (ec.value() != boost::asio::error::would_block)) {
451 // The connection is dead and there can't be a pending write as
452 // they are in sequence.
454 return;
455
456 // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
457 // read something from the socket on the next attempt.
458 } else {
459 doWrite(response);
460 }
461 }
462
464 .arg(length)
466
467 // Since each response has its own wire data, it is not really
468 // possible that the number of bytes written is larger than the size
469 // of the buffer. But, let's be safe and set the length to the size
470 // of the buffer if that unexpected condition occurs.
471 if (length > response->getWireDataSize()) {
472 length = response->getWireDataSize();
473 }
474
475 // Eat the 'length' number of bytes from the output buffer and only
476 // leave the part of the response that hasn't been sent.
477 response->consumeWireData(length);
478
479 // Schedule the write of the unsent data.
480 doWrite(response);
481}
482
483void
488
489void
494 // In theory we should shutdown first and stop/close after but
495 // it is better to put the connection management responsibility
496 // on the client... so simply drop idle connections.
498}
499
500std::string
502 if (remote_endpoint_ != NO_ENDPOINT()) {
503 return (remote_endpoint_.address().to_string());
504 }
505
506 return ("(unknown address)");
507}
508
509void
510TcpConnection::setReadMax(const size_t read_max) {
511 if (!read_max) {
512 isc_throw(BadValue, "TcpConnection read_max must be > 0");
513 }
514
515 read_max_ = read_max;
516 input_buf_.resize(read_max);
517}
518
519} // end of namespace isc::tcp
520} // 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.
const int DBGLVL_TRACE_DETAIL_DATA
Trace data associated with detailed operations.
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
boost::shared_ptr< TlsConnectionAcceptor > TlsConnectionAcceptorPtr
Type of shared pointer to TLS acceptors.
const isc::log::MessageID TCP_CONNECTION_STOP_FAILED
const isc::log::MessageID TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED
boost::shared_ptr< TcpConnectionAcceptor > TcpConnectionAcceptorPtr
Type of shared pointer to TCP acceptors.
const isc::log::MessageID TLS_REQUEST_RECEIVE_START
const isc::log::MessageID TLS_CONNECTION_HANDSHAKE_FAILED
const isc::log::MessageID TCP_CONNECTION_STOP
const isc::log::MessageID TCP_DATA_SENT
boost::shared_ptr< TcpRequest > TcpRequestPtr
Defines a smart pointer to a TcpRequest.
std::function< bool(const boost::asio::ip::tcp::endpoint &) TcpConnectionFilterCallback)
Type of the callback for filtering new connections by ip address.
const isc::log::MessageID TCP_CONNECTION_REJECTED_BY_FILTER
const isc::log::MessageID TLS_CONNECTION_HANDSHAKE_START
std::function< void(const boost::system::error_code &) TcpConnectionAcceptorCallback)
Type of the callback for the TCP acceptor used in this library.
boost::shared_ptr< TcpResponse > TcpResponsePtr
const isc::log::MessageID TLS_SERVER_RESPONSE_SEND
const isc::log::MessageID TCP_DATA_RECEIVED
const isc::log::MessageID TCP_REQUEST_RECEIVE_START
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
const isc::log::MessageID TCP_REQUEST_RECEIVED_FAILED
const isc::log::MessageID TCP_CONNECTION_SHUTDOWN
const isc::log::MessageID TCP_CONNECTION_SHUTDOWN_FAILED
const isc::log::MessageID TCP_CLIENT_REQUEST_RECEIVED
Defines the logger used by the top-level component of kea-lfc.