Kea  1.9.9-git
connection.cc
Go to the documentation of this file.
1 // Copyright (C) 2017-2021 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 <http/connection.h>
11 #include <http/connection_pool.h>
12 #include <http/http_log.h>
13 #include <http/http_messages.h>
14 #include <boost/make_shared.hpp>
15 #include <functional>
16 
17 using namespace isc::asiolink;
18 namespace ph = std::placeholders;
19 
20 namespace {
21 
25 constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
26 
27 }
28 
29 namespace isc {
30 namespace http {
31 
32 HttpConnection::Transaction::Transaction(const HttpResponseCreatorPtr& response_creator,
33  const HttpRequestPtr& request)
34  : request_(request ? request : response_creator->createNewHttpRequest()),
35  parser_(new HttpRequestParser(*request_)),
36  input_buf_(),
37  output_buf_() {
38  parser_->initModel();
39 }
40 
43  return (boost::make_shared<Transaction>(response_creator));
44 }
45 
48  const TransactionPtr& transaction) {
49  if (transaction) {
50  return (boost::make_shared<Transaction>(response_creator,
51  transaction->getRequest()));
52  }
53  return (create(response_creator));
54 }
55 
56 void
57 HttpConnection::
58 SocketCallback::operator()(boost::system::error_code ec, size_t length) {
59  if (ec.value() == boost::asio::error::operation_aborted) {
60  return;
61  }
62  callback_(ec, length);
63 }
64 
66  const HttpAcceptorPtr& acceptor,
67  const TlsContextPtr& tls_context,
68  HttpConnectionPool& connection_pool,
69  const HttpResponseCreatorPtr& response_creator,
70  const HttpAcceptorCallback& callback,
71  const long request_timeout,
72  const long idle_timeout)
73  : request_timer_(io_service),
74  request_timeout_(request_timeout),
75  tls_context_(tls_context),
76  idle_timeout_(idle_timeout),
77  tcp_socket_(),
78  tls_socket_(),
79  acceptor_(acceptor),
80  connection_pool_(connection_pool),
81  response_creator_(response_creator),
82  acceptor_callback_(callback) {
83  if (!tls_context) {
84  tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
85  } else {
87  tls_context));
88  }
89 }
90 
92  close();
93 }
94 
95 void
96 HttpConnection::shutdownCallback(const boost::system::error_code&) {
97  tls_socket_->close();
98 }
99 
100 void
103  if (tcp_socket_) {
104  tcp_socket_->close();
105  return;
106  }
107  if (tls_socket_) {
108  // Create instance of the callback to close the socket.
109  SocketCallback cb(std::bind(&HttpConnection::shutdownCallback,
110  shared_from_this(),
111  ph::_1)); // error_code
112  tls_socket_->shutdown(cb);
113  return;
114  }
115  // Not reachable?
116  isc_throw(Unexpected, "internal error: unable to shutdown the socket");
117 }
118 
119 void
122  if (tcp_socket_) {
123  tcp_socket_->close();
124  return;
125  }
126  if (tls_socket_) {
127  tls_socket_->close();
128  return;
129  }
130  // Not reachable?
131  isc_throw(Unexpected, "internal error: unable to close the socket");
132 }
133 
134 void
136  try {
140  connection_pool_.shutdown(shared_from_this());
141  } catch (...) {
143  }
144 }
145 
146 void
148  try {
152  connection_pool_.stop(shared_from_this());
153  } catch (...) {
155  }
156 }
157 
158 void
160  // Create instance of the callback. It is safe to pass the local instance
161  // of the callback, because the underlying boost functions make copies
162  // as needed.
164  shared_from_this(),
165  ph::_1); // error
166  try {
167  HttpsAcceptorPtr tls_acceptor =
168  boost::dynamic_pointer_cast<HttpsAcceptor>(acceptor_);
169  if (!tls_acceptor) {
170  if (!tcp_socket_) {
171  isc_throw(Unexpected, "internal error: TCP socket is null");
172  }
173  acceptor_->asyncAccept(*tcp_socket_, cb);
174  } else {
175  if (!tls_socket_) {
176  isc_throw(Unexpected, "internal error: TLS socket is null");
177  }
178  tls_acceptor->asyncAccept(*tls_socket_, cb);
179  }
180  } catch (const std::exception& ex) {
181  isc_throw(HttpConnectionError, "unable to start accepting TCP "
182  "connections: " << ex.what());
183  }
184 }
185 
186 void
188  // Skip the handshake if the socket is not a TLS one.
189  if (!tls_socket_) {
190  doRead();
191  return;
192  }
193 
194  // Create instance of the callback. It is safe to pass the local instance
195  // of the callback, because the underlying boost functions make copies
196  // as needed.
197  SocketCallback cb(std::bind(&HttpConnection::handshakeCallback,
198  shared_from_this(),
199  ph::_1)); // error
200  try {
201  tls_socket_->handshake(cb);
202 
203  } catch (const std::exception& ex) {
204  isc_throw(HttpConnectionError, "unable to perform TLS handshake: "
205  << ex.what());
206  }
207 }
208 
209 void
211  try {
212  TCPEndpoint endpoint;
213 
214  // Transaction hasn't been created if we are starting to read the
215  // new request.
216  if (!transaction) {
217  transaction = Transaction::create(response_creator_);
218  }
219 
220  // Create instance of the callback. It is safe to pass the local instance
221  // of the callback, because the underlying std functions make copies
222  // as needed.
223  SocketCallback cb(std::bind(&HttpConnection::socketReadCallback,
224  shared_from_this(),
225  transaction,
226  ph::_1, // error
227  ph::_2)); //bytes_transferred
228  if (tcp_socket_) {
229  tcp_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
230  transaction->getInputBufSize(),
231  0, &endpoint, cb);
232  return;
233  }
234  if (tls_socket_) {
235  tls_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
236  transaction->getInputBufSize(),
237  0, &endpoint, cb);
238  return;
239  }
240  } catch (...) {
242  }
243 }
244 
245 void
247  try {
248  if (transaction->outputDataAvail()) {
249  // Create instance of the callback. It is safe to pass the local instance
250  // of the callback, because the underlying std functions make copies
251  // as needed.
252  SocketCallback cb(std::bind(&HttpConnection::socketWriteCallback,
253  shared_from_this(),
254  transaction,
255  ph::_1, // error
256  ph::_2)); // bytes_transferred
257  if (tcp_socket_) {
258  tcp_socket_->asyncSend(transaction->getOutputBufData(),
259  transaction->getOutputBufSize(),
260  cb);
261  return;
262  }
263  if (tls_socket_) {
264  tls_socket_->asyncSend(transaction->getOutputBufData(),
265  transaction->getOutputBufSize(),
266  cb);
267  return;
268  }
269  } else {
270  // The isPersistent() function may throw if the request hasn't
271  // been created, i.e. the HTTP headers weren't parsed. We catch
272  // this exception below and close the connection since we're
273  // unable to tell if the connection should remain persistent
274  // or not. The default is to close it.
275  if (!transaction->getRequest()->isPersistent()) {
277 
278  } else {
279  // The connection is persistent and we are done sending
280  // the previous response. Start listening for the next
281  // requests.
282  setupIdleTimer();
283  doRead();
284  }
285  }
286  } catch (...) {
288  }
289 }
290 
291 void
293  TransactionPtr transaction) {
294  transaction->setOutputBuf(response->toString());
295  doWrite(transaction);
296 }
297 
298 
299 void
300 HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
301  if (!acceptor_->isOpen()) {
302  return;
303  }
304 
305  if (ec) {
307  }
308 
309  acceptor_callback_(ec);
310 
311  if (!ec) {
312  if (!tls_context_) {
316  .arg(static_cast<unsigned>(request_timeout_/1000));
317  } else {
321  .arg(static_cast<unsigned>(request_timeout_/1000));
322  }
323 
325  doHandshake();
326  }
327 }
328 
329 void
330 HttpConnection::handshakeCallback(const boost::system::error_code& ec) {
331  if (ec) {
334  .arg(ec.message());
336  } else {
340 
341  doRead();
342  }
343 }
344 
345 void
347  boost::system::error_code ec, size_t length) {
348  if (ec) {
349  // IO service has been stopped and the connection is probably
350  // going to be shutting down.
351  if (ec.value() == boost::asio::error::operation_aborted) {
352  return;
353 
354  // EWOULDBLOCK and EAGAIN are special cases. Everything else is
355  // treated as fatal error.
356  } else if ((ec.value() != boost::asio::error::try_again) &&
357  (ec.value() != boost::asio::error::would_block)) {
359 
360  // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
361  // read something from the socket on the next attempt. Just make sure
362  // we don't try to read anything now in case there is any garbage
363  // passed in length.
364  } else {
365  length = 0;
366  }
367  }
368 
369  // Receiving is in progress, so push back the timeout.
370  setupRequestTimer(transaction);
371 
372  if (length != 0) {
375  .arg(length)
377 
378  transaction->getParser()->postBuffer(static_cast<void*>(transaction->getInputBufData()),
379  length);
380  transaction->getParser()->poll();
381  }
382 
383  if (transaction->getParser()->needData()) {
384  // The parser indicates that the some part of the message being
385  // received is still missing, so continue to read.
386  doRead(transaction);
387 
388  } else {
389  try {
390  // The whole message has been received, so let's finalize it.
391  transaction->getRequest()->finalize();
392 
396 
400  .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
401 
402  } catch (const std::exception& ex) {
406  .arg(ex.what());
407 
411  .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
412  }
413 
414  // Don't want to timeout if creation of the response takes long.
416 
417  // Create the response from the received request using the custom
418  // response creator.
419  HttpResponsePtr response = response_creator_->createHttpResponse(transaction->getRequest());
422  .arg(response->toBriefString())
424 
428  .arg(HttpMessageParserBase::logFormatHttpMessage(response->toString(),
429  MAX_LOGGED_MESSAGE_SIZE));
430 
431  // Response created. Activate the timer again.
432  setupRequestTimer(transaction);
433 
434  // Start sending the response.
435  asyncSendResponse(response, transaction);
436  }
437 }
438 
439 void
441  boost::system::error_code ec, size_t length) {
442  if (ec) {
443  // IO service has been stopped and the connection is probably
444  // going to be shutting down.
445  if (ec.value() == boost::asio::error::operation_aborted) {
446  return;
447 
448  // EWOULDBLOCK and EAGAIN are special cases. Everything else is
449  // treated as fatal error.
450  } else if ((ec.value() != boost::asio::error::try_again) &&
451  (ec.value() != boost::asio::error::would_block)) {
453 
454  // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
455  // read something from the socket on the next attempt.
456  } else {
457  // Sending is in progress, so push back the timeout.
458  setupRequestTimer(transaction);
459 
460  doWrite(transaction);
461  }
462  }
463 
464  // Since each transaction has its own output buffer, it is not really
465  // possible that the number of bytes written is larger than the size
466  // of the buffer. But, let's be safe and set the length to the size
467  // of the buffer if that unexpected condition occurs.
468  if (length > transaction->getOutputBufSize()) {
469  length = transaction->getOutputBufSize();
470  }
471 
472  if (length <= transaction->getOutputBufSize()) {
473  // Sending is in progress, so push back the timeout.
474  setupRequestTimer(transaction);
475  }
476 
477  // Eat the 'length' number of bytes from the output buffer and only
478  // leave the part of the response that hasn't been sent.
479  transaction->consumeOutputBuf(length);
480 
481  // Schedule the write of the unsent data.
482  doWrite(transaction);
483 }
484 
485 void
487  // Pass raw pointer rather than shared_ptr to this object,
488  // because IntervalTimer already passes shared pointer to the
489  // IntervalTimerImpl to make sure that the callback remains
490  // valid.
492  this, transaction),
493  request_timeout_, IntervalTimer::ONE_SHOT);
494 }
495 
496 void
499  this),
500  idle_timeout_, IntervalTimer::ONE_SHOT);
501 }
502 
503 void
508 
509  // We need to differentiate the transactions between a normal response and the
510  // timeout. We create new transaction from the current transaction. It is
511  // to preserve the request we're responding to.
512  auto spawned_transaction = Transaction::spawn(response_creator_, transaction);
513 
514  // The new transaction inherits the request from the original transaction
515  // if such transaction exists.
516  auto request = spawned_transaction->getRequest();
517 
518  // Depending on when the timeout occurred, the HTTP version of the request
519  // may or may not be available. Therefore we check if the HTTP version is
520  // set in the request. If it is not available, we need to create a dummy
521  // request with the default HTTP/1.0 version. This version will be used
522  // in the response.
523  if (request->context()->http_version_major_ == 0) {
524  request.reset(new HttpRequest(HttpRequest::Method::HTTP_POST, "/",
526  HostHttpHeader("dummy")));
527  request->finalize();
528  }
529 
530  // Create the timeout response.
531  HttpResponsePtr response =
532  response_creator_->createStockHttpResponse(request,
534 
535  // Send the HTTP 408 status.
536  asyncSendResponse(response, spawned_transaction);
537 }
538 
539 void
544  // In theory we should shutdown first and stop/close after but
545  // it is better to put the connection management responsibility
546  // on the client... so simply drop idle connections.
548 }
549 
550 std::string
552  try {
553  if (tcp_socket_) {
554  if (tcp_socket_->getASIOSocket().is_open()) {
555  return (tcp_socket_->getASIOSocket().remote_endpoint().address().to_string());
556  }
557  } else if (tls_socket_) {
558  if (tls_socket_->getASIOSocket().is_open()) {
559  return (tls_socket_->getASIOSocket().remote_endpoint().address().to_string());
560  }
561  }
562  } catch (...) {
563  }
564  return ("(unknown address)");
565 }
566 
567 } // end of namespace isc::http
568 } // end of namespace isc
void asyncSendResponse(const ConstHttpResponsePtr &response, TransactionPtr transaction)
Sends HTTP response asynchronously.
Definition: connection.cc:292
boost::shared_ptr< const HttpResponse > ConstHttpResponsePtr
Pointer to the const HttpResponse object.
Definition: response.h:84
const isc::log::MessageID HTTP_CONNECTION_STOP
Definition: http_messages.h:27
Represents HTTP Host header.
Definition: http_header.h:68
std::unique_ptr< asiolink::TCPSocket< SocketCallback > > tcp_socket_
TCP socket used by this connection.
Definition: connection.h:406
void handshakeCallback(const boost::system::error_code &ec)
Local callback invoked when TLS handshake is performed.
Definition: connection.cc:330
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:65
static std::string logFormatHttpMessage(const std::string &message, const size_t limit=0)
Formats provided HTTP message for logging.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
void doRead(TransactionPtr transaction=TransactionPtr())
Starts asynchronous read from the socket.
Definition: connection.cc:210
std::function< void()> callback_
The callback function.
Definition: io_service.cc:38
HttpAcceptorCallback acceptor_callback_
External TCP acceptor callback.
Definition: connection.h:422
boost::shared_ptr< Transaction > TransactionPtr
Shared pointer to the Transaction.
Definition: connection.h:83
void doWrite(TransactionPtr transaction)
Starts asynchronous write to the socket.
Definition: connection.cc:246
boost::shared_ptr< HttpAcceptor > HttpAcceptorPtr
Type of shared pointer to TCP acceptors.
Definition: http_acceptor.h:31
const isc::log::MessageID HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED
Definition: http_messages.h:21
HttpAcceptorPtr acceptor_
Pointer to the TCP acceptor used to accept new connections.
Definition: connection.h:412
void shutdownCallback(const boost::system::error_code &ec)
Callback invoked when TLS shutdown is performed.
Definition: connection.cc:96
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
static TransactionPtr spawn(const HttpResponseCreatorPtr &response_creator, const TransactionPtr &transaction)
Creates new transaction from the current transaction.
Definition: connection.cc:47
void doHandshake()
Asynchronously performs TLS handshake.
Definition: connection.cc:187
const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN_FAILED
Definition: http_messages.h:26
A generic parser for HTTP requests.
const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_START
Definition: http_messages.h:24
void shutdown()
Shutdown the socket.
Definition: connection.cc:101
const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED_DETAILS
Definition: http_messages.h:18
std::function< void(const boost::system::error_code &)> HttpAcceptorCallback
Type of the callback for the TCP acceptor used in this library.
Definition: http_acceptor.h:20
virtual ~HttpConnection()
Destructor.
Definition: connection.cc:91
asiolink::TlsContextPtr tls_context_
TLS context.
Definition: connection.h:399
virtual void socketWriteCallback(TransactionPtr transaction, boost::system::error_code ec, size_t length)
Callback invoked when data is sent over the socket.
Definition: connection.cc:440
void stopThisConnection()
Stops current connection.
Definition: connection.cc:147
Generic error reported within HttpConnection class.
Definition: connection.h:26
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS
Definition: http_messages.h:13
void shutdownConnection()
Shuts down current connection.
Definition: connection.cc:135
void setupIdleTimer()
Reset timer for detecting idle timeout in persistent connections.
Definition: connection.cc:497
void socketReadCallback(TransactionPtr transaction, boost::system::error_code ec, size_t length)
Callback invoked when new data is received over the socket.
Definition: connection.cc:346
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition: response.h:78
std::unique_ptr< asiolink::TLSSocket< SocketCallback > > tls_socket_
TLS socket used by this connection.
Definition: connection.h:409
A generic exception that is thrown when an unexpected error condition occurs.
const int DBGLVL_TRACE_DETAIL_DATA
Trace data associated with detailed operations.
Definition: log_dbglevels.h:74
boost::shared_ptr< HttpsAcceptor > HttpsAcceptorPtr
Type of shared pointer to TLS acceptors.
Definition: http_acceptor.h:34
HttpConnectionPool & connection_pool_
Connection pool holding this connection.
Definition: connection.h:415
void stop(const HttpConnectionPtr &connection)
Removes a connection from the pool and stops it.
HttpResponseCreatorPtr response_creator_
Pointer to the HttpResponseCreator object used to create HTTP responses.
Definition: connection.h:419
const isc::log::MessageID HTTP_DATA_RECEIVED
Definition: http_messages.h:29
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
static TransactionPtr create(const HttpResponseCreatorPtr &response_creator)
Creates new transaction instance.
Definition: connection.cc:42
void setupRequestTimer(TransactionPtr transaction=TransactionPtr())
Reset timer for detecting request timeouts.
Definition: connection.cc:486
HttpConnection(asiolink::IOService &io_service, const HttpAcceptorPtr &acceptor, const asiolink::TlsContextPtr &tls_context, HttpConnectionPool &connection_pool, const HttpResponseCreatorPtr &response_creator, const HttpAcceptorCallback &callback, const long request_timeout, const long idle_timeout)
Constructor.
Definition: connection.cc:65
boost::shared_ptr< HttpResponseCreator > HttpResponseCreatorPtr
Pointer to the HttpResponseCreator object.
Defines the logger used by the top-level component of kea-dhcp-ddns.
const isc::log::MessageID HTTPS_REQUEST_RECEIVE_START
Definition: http_messages.h:11
const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED
Definition: http_messages.h:17
asiolink::IntervalTimer request_timer_
Timer used to detect Request Timeout.
Definition: connection.h:393
std::string getRemoteEndpointAddressAsText() const
returns remote address in textual form
Definition: connection.cc:551
void acceptorCallback(const boost::system::error_code &ec)
Local callback invoked when new connection is accepted.
Definition: connection.cc:300
void close()
Closes the socket.
Definition: connection.cc:120
const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND_DETAILS
Definition: http_messages.h:36
void asyncAccept()
Asynchronously accepts new connection.
Definition: connection.cc:159
static const HttpVersion & HTTP_10()
HTTP version 1.0.
Definition: http_types.h:53
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_FAILED
Definition: http_messages.h:23
Represents HTTP request message.
Definition: request.h:49
const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED
Definition: http_messages.h:12
const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN
Definition: http_messages.h:25
isc::log::Logger http_logger("http")
Defines the logger used within libkea-http library.
Definition: http_log.h:18
void shutdown(const HttpConnectionPtr &connection)
Removes a connection from the pool and shutdown it.
boost::shared_ptr< HttpRequest > HttpRequestPtr
Pointer to the HttpRequest object.
Definition: request.h:27
const isc::log::MessageID HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED
Definition: http_messages.h:30
const isc::log::MessageID HTTP_REQUEST_RECEIVE_START
Definition: http_messages.h:32
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:71
const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND
Definition: http_messages.h:35
void requestTimeoutCallback(TransactionPtr transaction)
Callback invoked when the HTTP Request Timeout occurs.
Definition: connection.cc:504
const isc::log::MessageID HTTP_CONNECTION_STOP_FAILED
Definition: http_messages.h:28
const int DBGLVL_TRACE_BASIC_DATA
Trace data associated with the basic operations.
Definition: log_dbglevels.h:68
Pool of active HTTP connections.
long idle_timeout_
Timeout after which the persistent HTTP connection is shut down by the server.
Definition: connection.h:403
long request_timeout_
Configured Request Timeout in milliseconds.
Definition: connection.h:396