Kea  2.3.7
tcp_connection.cc
Go to the documentation of this file.
1 // Copyright (C) 2022-2023 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 <util/strutil.h>
15 #include <boost/make_shared.hpp>
16 
17 #include <iomanip>
18 #include <sstream>
19 #include <functional>
20 
21 using namespace isc::asiolink;
22 namespace ph = std::placeholders;
23 
24 namespace {
25 
29 const size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
30 
31 }
32 
33 namespace isc {
34 namespace tcp {
35 
36 void
37 TcpResponse::consumeWireData(const size_t length) {
38  send_in_progress_ = true;
39  wire_data_.erase(wire_data_.begin(), wire_data_.begin() + length);
40 }
41 
42 void
43 TcpConnection::
44 SocketCallback::operator()(boost::system::error_code ec, size_t length) {
45  if (ec.value() == boost::asio::error::operation_aborted) {
46  return;
47  }
48  callback_(ec, length);
49 }
50 
51 TcpConnection::TcpConnection(asiolink::IOService& io_service,
52  const TcpConnectionAcceptorPtr& acceptor,
53  const TlsContextPtr& tls_context,
54  TcpConnectionPool& connection_pool,
55  const TcpConnectionAcceptorCallback& acceptor_callback,
56  const TcpConnectionFilterCallback& connection_filter,
57  const long idle_timeout,
58  const size_t read_max /* = 32768 */)
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  input_buf_(read_max) {
69  if (!tls_context) {
70  tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
71  } else {
73  tls_context));
74  }
75 }
76 
78  close();
79 }
80 
81 void
82 TcpConnection::shutdownCallback(const boost::system::error_code&) {
83  tls_socket_->close();
84 }
85 
86 void
89  if (tcp_socket_) {
90  tcp_socket_->close();
91  return;
92  }
93 
94  if (tls_socket_) {
95  // Create instance of the callback to close the socket.
96  SocketCallback cb(std::bind(&TcpConnection::shutdownCallback,
97  shared_from_this(),
98  ph::_1)); // error_code
99  tls_socket_->shutdown(cb);
100  return;
101  }
102 
103  // Not reachable?
104  isc_throw(Unexpected, "internal error: unable to shutdown the socket");
105 }
106 
107 void
110  if (tcp_socket_) {
111  tcp_socket_->close();
112  return;
113  }
114 
115  if (tls_socket_) {
116  tls_socket_->close();
117  return;
118  }
119 
120  // Not reachable?
121  isc_throw(Unexpected, "internal error: unable to close the socket");
122 }
123 
124 void
126  try {
130  connection_pool_.shutdown(shared_from_this());
131  } catch (...) {
133  }
134 }
135 
136 void
138  try {
142  connection_pool_.stop(shared_from_this());
143  } catch (...) {
145  }
146 }
147 
148 void
150  // Create instance of the callback. It is safe to pass the local instance
151  // of the callback, because the underlying boost functions make copies
152  // as needed.
154  shared_from_this(),
155  ph::_1);
156  try {
157  TlsConnectionAcceptorPtr tls_acceptor =
158  boost::dynamic_pointer_cast<TlsConnectionAcceptor>(acceptor_);
159  if (!tls_acceptor) {
160  if (!tcp_socket_) {
161  isc_throw(Unexpected, "internal error: TCP socket is null");
162  }
163  acceptor_->asyncAccept(*tcp_socket_, cb);
164  } else {
165  if (!tls_socket_) {
166  isc_throw(Unexpected, "internal error: TLS socket is null");
167  }
168  tls_acceptor->asyncAccept(*tls_socket_, cb);
169  }
170  } catch (const std::exception& ex) {
171  isc_throw(TcpConnectionError, "unable to start accepting TCP "
172  "connections: " << ex.what());
173  }
174 }
175 
176 void
178  // Skip the handshake if the socket is not a TLS one.
179  if (!tls_socket_) {
180  doRead();
181  return;
182  }
183 
184  setupIdleTimer();
185 
186  // Create instance of the callback. It is safe to pass the local instance
187  // of the callback, because the underlying boost functions make copies
188  // as needed.
189  SocketCallback cb(std::bind(&TcpConnection::handshakeCallback,
190  shared_from_this(),
191  ph::_1)); // error
192  try {
193  tls_socket_->handshake(cb);
194 
195  } catch (const std::exception& ex) {
196  isc_throw(TcpConnectionError, "unable to perform TLS handshake: "
197  << ex.what());
198  }
199 }
200 
201 void
203  try {
204  TCPEndpoint endpoint;
205 
206  setupIdleTimer();
207 
208  // Request hasn't been created if we are starting to read the
209  // new request.
210  if (!request) {
211  request = createRequest();
212  }
213 
214  // Create instance of the callback. It is safe to pass the local instance
215  // of the callback, because the underlying std functions make copies
216  // as needed.
217  SocketCallback cb(std::bind(&TcpConnection::socketReadCallback,
218  shared_from_this(),
219  request,
220  ph::_1, // error
221  ph::_2)); // bytes_transferred
222  if (tcp_socket_) {
223  tcp_socket_->asyncReceive(static_cast<void*>(getInputBufData()),
224  getInputBufSize(), 0, &endpoint, cb);
225  return;
226  }
227 
228  if (tls_socket_) {
229  tls_socket_->asyncReceive(static_cast<void*>(getInputBufData()),
230  getInputBufSize(), 0, &endpoint, cb);
231  return;
232  }
233  } catch (...) {
235  }
236 }
237 
238 void
240  try {
241  if (response->wireDataAvail()) {
242  // Create instance of the callback. It is safe to pass the
243  // local instance of the callback, because the underlying
244  // std functions make copies as needed.
245  SocketCallback cb(std::bind(&TcpConnection::socketWriteCallback,
246  shared_from_this(),
247  response,
248  ph::_1, // error
249  ph::_2)); // bytes_transferred
250  if (tcp_socket_) {
254  tcp_socket_->asyncSend(response->getWireData(),
255  response->getWireDataSize(),
256  cb);
257  return;
258  }
259  if (tls_socket_) {
263  tls_socket_->asyncSend(response->getWireData(),
264  response->getWireDataSize(),
265  cb);
266  return;
267  }
268  } else {
269  // The connection remains open and we are done sending the response.
270  // If the response sent handler returns true then we should start the
271  // idle timer.
272  if (responseSent(response)) {
273  setupIdleTimer();
274  }
275  }
276  } catch (...) {
277  // The connection is dead and there can't be a pending write as
278  // they are in sequence.
280  }
281 }
282 
283 void
285  doWrite(response);
286 }
287 
288 
289 void
290 TcpConnection::acceptorCallback(const boost::system::error_code& ec) {
291  if (!acceptor_->isOpen()) {
292  return;
293  }
294 
295  if (ec) {
297  }
298 
299  // Stage a new connection to listen for next client.
300  acceptor_callback_(ec);
301 
302  if (!ec) {
303  try {
304  if (tcp_socket_ && tcp_socket_->getASIOSocket().is_open()) {
306  tcp_socket_->getASIOSocket().remote_endpoint();
307  } else if (tls_socket_ && tls_socket_->getASIOSocket().is_open()) {
309  tls_socket_->getASIOSocket().remote_endpoint();
310  }
311  } catch (...) {
312  // Let's it to fail later.
313  }
314 
315  // In theory, we should not get here with an unopened socket
316  // but just in case, we'll check for NO_ENDPOINT.
317  if ((remote_endpoint_ == NO_ENDPOINT()) ||
324  return;
325  }
326 
327  if (!tls_context_) {
331  .arg(static_cast<unsigned>(idle_timeout_/1000));
332  } else {
336  .arg(static_cast<unsigned>(idle_timeout_/1000));
337  }
338 
339  doHandshake();
340  }
341 }
342 
343 void
344 TcpConnection::handshakeCallback(const boost::system::error_code& ec) {
345  if (ec) {
348  .arg(ec.message());
350  } else {
354  .arg(static_cast<unsigned>(idle_timeout_/1000));
355  doRead();
356  }
357 }
358 
359 void
361  boost::system::error_code ec, size_t length) {
362  if (ec) {
363  // IO service has been stopped and the connection is probably
364  // going to be shutting down.
365  if (ec.value() == boost::asio::error::operation_aborted) {
366  return;
367 
368  // EWOULDBLOCK and EAGAIN are special cases. Everything else is
369  // treated as fatal error.
370  } else if ((ec.value() != boost::asio::error::try_again) &&
371  (ec.value() != boost::asio::error::would_block)) {
373  return;
374 
375  // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
376  // read something from the socket on the next attempt. Just make sure
377  // we don't try to read anything now in case there is any garbage
378  // passed in length.
379  } else {
380  length = 0;
381  }
382  }
383 
384  // Data received, Restart the request timer.
385  setupIdleTimer();
386 
387  TcpRequestPtr next_request = request;
388  if (length) {
391  .arg(length)
393  WireData input_data(input_buf_.begin(), input_buf_.begin() + length);
394  next_request = postData(request, input_data);
395  }
396 
397  // Start next read.
398  doRead(next_request);
399 }
400 
403  size_t bytes_left = 0;
404  size_t length = input_data.size();
405  if (length) {
406  // Add data to the current request.
407  size_t bytes_used = request->postBuffer(static_cast<void*>(input_data.data()), length);
408  // Remove bytes used.
409  bytes_left = length - bytes_used;
410  input_data.erase(input_data.begin(), input_data.begin() + length);
411  }
412 
413  if (request->needData()) {
414  // Current request is incomplete and we're out of data
415  // return the incomplete request and we'll read again.
416  return (request);
417  }
418 
419  try {
423 
424  // Request complete, stop the timer.
426 
427  // Process the completed request.
428  requestReceived(request);
429  } catch (const std::exception& ex) {
432  .arg(ex.what());
433  }
434 
435  // Create a new, empty request.
436  request = createRequest();
437  if (bytes_left) {
438  // The input buffer spanned messages. Recurse to post the remainder to the
439  // new request.
440  request = postData(request, input_data);
441  }
442 
443  return (request);
444 }
445 
446 void
448  boost::system::error_code ec, size_t length) {
449  if (ec) {
450  // IO service has been stopped and the connection is probably
451  // going to be shutting down.
452  if (ec.value() == boost::asio::error::operation_aborted) {
453  return;
454 
455  // EWOULDBLOCK and EAGAIN are special cases. Everything else is
456  // treated as fatal error.
457  } else if ((ec.value() != boost::asio::error::try_again) &&
458  (ec.value() != boost::asio::error::would_block)) {
459  // The connection is dead and there can't be a pending write as
460  // they are in sequence.
462  return;
463 
464  // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
465  // read something from the socket on the next attempt.
466  } else {
467  doWrite(response);
468  }
469  }
470 
472  .arg(length)
474 
475  // Since each response has its own wire data, it is not really
476  // possible that the number of bytes written is larger than the size
477  // of the buffer. But, let's be safe and set the length to the size
478  // of the buffer if that unexpected condition occurs.
479  if (length > response->getWireDataSize()) {
480  length = response->getWireDataSize();
481  }
482 
483  // Eat the 'length' number of bytes from the output buffer and only
484  // leave the part of the response that hasn't been sent.
485  response->consumeWireData(length);
486 
487  // Schedule the write of the unsent data.
488  doWrite(response);
489 }
490 
491 void
494  idle_timeout_, IntervalTimer::ONE_SHOT);
495 }
496 
497 void
502  // In theory we should shutdown first and stop/close after but
503  // it is better to put the connection management responsibility
504  // on the client... so simply drop idle connections.
506 }
507 
508 std::string
510  if (remote_endpoint_ != NO_ENDPOINT()) {
511  return (remote_endpoint_.address().to_string());
512  }
513 
514  return ("(unknown address)");
515 }
516 
517 void
518 TcpConnection::setReadMax(const size_t read_max) {
519  if (!read_max) {
520  isc_throw(BadValue, "TcpConnection read_max must be > 0");
521  }
522 
523  read_max_ = read_max;
524  input_buf_.resize(read_max);
525 }
526 
527 } // end of namespace isc::tcp
528 } // 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.
void acceptorCallback(const boost::system::error_code &ec)
Local callback invoked when new connection is accepted.
void asyncAccept()
Asynchronously accepts new connection.
static const boost::asio::ip::tcp::endpoint & NO_ENDPOINT()
Returns an empty end point.
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.
unsigned char * getInputBufData()
Returns pointer to the first byte of the input buffer.
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.
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.
long idle_timeout_
Timeout after which the a TCP connection is shut down by the server.
#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.