Kea  2.3.3-git
connection.cc
Go to the documentation of this file.
1 // Copyright (C) 2017-2022 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
97  if (!request) {
98  // Should never happen.
99  return;
100  }
101 
102  // Record the remote address.
103  request->setRemote(getRemoteEndpointAddressAsText());
104 
105  // Record TLS parameters.
106  if (!tls_socket_) {
107  return;
108  }
109 
110  // The connection uses HTTPS aka HTTP over TLS.
111  request->setTls(true);
112 
113  // Record the first commonName of the subjectName of the client
114  // certificate when wanted.
116  request->setSubject(tls_socket_->getTlsStream().getSubject());
117  }
118 
119  // Record the first commonName of the issuerName of the client
120  // certificate when wanted.
122  request->setIssuer(tls_socket_->getTlsStream().getIssuer());
123  }
124 }
125 
126 void
127 HttpConnection::shutdownCallback(const boost::system::error_code&) {
128  tls_socket_->close();
129 }
130 
131 void
134  if (tcp_socket_) {
135  tcp_socket_->close();
136  return;
137  }
138  if (tls_socket_) {
139  // Create instance of the callback to close the socket.
140  SocketCallback cb(std::bind(&HttpConnection::shutdownCallback,
141  shared_from_this(),
142  ph::_1)); // error_code
143  tls_socket_->shutdown(cb);
144  return;
145  }
146  // Not reachable?
147  isc_throw(Unexpected, "internal error: unable to shutdown the socket");
148 }
149 
150 void
153  if (tcp_socket_) {
154  tcp_socket_->close();
155  return;
156  }
157  if (tls_socket_) {
158  tls_socket_->close();
159  return;
160  }
161  // Not reachable?
162  isc_throw(Unexpected, "internal error: unable to close the socket");
163 }
164 
165 void
167  try {
171  connection_pool_.shutdown(shared_from_this());
172  } catch (...) {
174  }
175 }
176 
177 void
179  try {
183  connection_pool_.stop(shared_from_this());
184  } catch (...) {
186  }
187 }
188 
189 void
191  // Create instance of the callback. It is safe to pass the local instance
192  // of the callback, because the underlying boost functions make copies
193  // as needed.
195  shared_from_this(),
196  ph::_1); // error
197  try {
198  HttpsAcceptorPtr tls_acceptor =
199  boost::dynamic_pointer_cast<HttpsAcceptor>(acceptor_);
200  if (!tls_acceptor) {
201  if (!tcp_socket_) {
202  isc_throw(Unexpected, "internal error: TCP socket is null");
203  }
204  acceptor_->asyncAccept(*tcp_socket_, cb);
205  } else {
206  if (!tls_socket_) {
207  isc_throw(Unexpected, "internal error: TLS socket is null");
208  }
209  tls_acceptor->asyncAccept(*tls_socket_, cb);
210  }
211  } catch (const std::exception& ex) {
212  isc_throw(HttpConnectionError, "unable to start accepting TCP "
213  "connections: " << ex.what());
214  }
215 }
216 
217 void
219  // Skip the handshake if the socket is not a TLS one.
220  if (!tls_socket_) {
221  doRead();
222  return;
223  }
224 
225  // Create instance of the callback. It is safe to pass the local instance
226  // of the callback, because the underlying boost functions make copies
227  // as needed.
228  SocketCallback cb(std::bind(&HttpConnection::handshakeCallback,
229  shared_from_this(),
230  ph::_1)); // error
231  try {
232  tls_socket_->handshake(cb);
233 
234  } catch (const std::exception& ex) {
235  isc_throw(HttpConnectionError, "unable to perform TLS handshake: "
236  << ex.what());
237  }
238 }
239 
240 void
242  try {
243  TCPEndpoint endpoint;
244 
245  // Transaction hasn't been created if we are starting to read the
246  // new request.
247  if (!transaction) {
248  transaction = Transaction::create(response_creator_);
249  recordParameters(transaction->getRequest());
250  }
251 
252  // Create instance of the callback. It is safe to pass the local instance
253  // of the callback, because the underlying std functions make copies
254  // as needed.
255  SocketCallback cb(std::bind(&HttpConnection::socketReadCallback,
256  shared_from_this(),
257  transaction,
258  ph::_1, // error
259  ph::_2)); //bytes_transferred
260  if (tcp_socket_) {
261  tcp_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
262  transaction->getInputBufSize(),
263  0, &endpoint, cb);
264  return;
265  }
266  if (tls_socket_) {
267  tls_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
268  transaction->getInputBufSize(),
269  0, &endpoint, cb);
270  return;
271  }
272  } catch (...) {
274  }
275 }
276 
277 void
279  try {
280  if (transaction->outputDataAvail()) {
281  // Create instance of the callback. It is safe to pass the local instance
282  // of the callback, because the underlying std functions make copies
283  // as needed.
284  SocketCallback cb(std::bind(&HttpConnection::socketWriteCallback,
285  shared_from_this(),
286  transaction,
287  ph::_1, // error
288  ph::_2)); // bytes_transferred
289  if (tcp_socket_) {
290  tcp_socket_->asyncSend(transaction->getOutputBufData(),
291  transaction->getOutputBufSize(),
292  cb);
293  return;
294  }
295  if (tls_socket_) {
296  tls_socket_->asyncSend(transaction->getOutputBufData(),
297  transaction->getOutputBufSize(),
298  cb);
299  return;
300  }
301  } else {
302  // The isPersistent() function may throw if the request hasn't
303  // been created, i.e. the HTTP headers weren't parsed. We catch
304  // this exception below and close the connection since we're
305  // unable to tell if the connection should remain persistent
306  // or not. The default is to close it.
307  if (!transaction->getRequest()->isPersistent()) {
309 
310  } else {
311  // The connection is persistent and we are done sending
312  // the previous response. Start listening for the next
313  // requests.
314  setupIdleTimer();
315  doRead();
316  }
317  }
318  } catch (...) {
320  }
321 }
322 
323 void
325  TransactionPtr transaction) {
326  transaction->setOutputBuf(response->toString());
327  doWrite(transaction);
328 }
329 
330 
331 void
332 HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
333  if (!acceptor_->isOpen()) {
334  return;
335  }
336 
337  if (ec) {
339  }
340 
341  acceptor_callback_(ec);
342 
343  if (!ec) {
344  if (!tls_context_) {
348  .arg(static_cast<unsigned>(request_timeout_/1000));
349  } else {
353  .arg(static_cast<unsigned>(request_timeout_/1000));
354  }
355 
357  doHandshake();
358  }
359 }
360 
361 void
362 HttpConnection::handshakeCallback(const boost::system::error_code& ec) {
363  if (ec) {
366  .arg(ec.message());
368  } else {
372 
373  doRead();
374  }
375 }
376 
377 void
379  boost::system::error_code ec, size_t length) {
380  if (ec) {
381  // IO service has been stopped and the connection is probably
382  // going to be shutting down.
383  if (ec.value() == boost::asio::error::operation_aborted) {
384  return;
385 
386  // EWOULDBLOCK and EAGAIN are special cases. Everything else is
387  // treated as fatal error.
388  } else if ((ec.value() != boost::asio::error::try_again) &&
389  (ec.value() != boost::asio::error::would_block)) {
391 
392  // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
393  // read something from the socket on the next attempt. Just make sure
394  // we don't try to read anything now in case there is any garbage
395  // passed in length.
396  } else {
397  length = 0;
398  }
399  }
400 
401  // Receiving is in progress, so push back the timeout.
402  setupRequestTimer(transaction);
403 
404  if (length != 0) {
407  .arg(length)
409 
410  transaction->getParser()->postBuffer(static_cast<void*>(transaction->getInputBufData()),
411  length);
412  transaction->getParser()->poll();
413  }
414 
415  if (transaction->getParser()->needData()) {
416  // The parser indicates that the some part of the message being
417  // received is still missing, so continue to read.
418  doRead(transaction);
419 
420  } else {
421  try {
422  // The whole message has been received, so let's finalize it.
423  transaction->getRequest()->finalize();
424 
428 
432  .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
433 
434  } catch (const std::exception& ex) {
438  .arg(ex.what());
439 
443  .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
444  }
445 
446  // Don't want to timeout if creation of the response takes long.
448 
449  // Create the response from the received request using the custom
450  // response creator.
451  HttpResponsePtr response = response_creator_->createHttpResponse(transaction->getRequest());
454  .arg(response->toBriefString())
456 
460  .arg(HttpMessageParserBase::logFormatHttpMessage(response->toString(),
461  MAX_LOGGED_MESSAGE_SIZE));
462 
463  // Response created. Activate the timer again.
464  setupRequestTimer(transaction);
465 
466  // Start sending the response.
467  asyncSendResponse(response, transaction);
468  }
469 }
470 
471 void
473  boost::system::error_code ec, size_t length) {
474  if (ec) {
475  // IO service has been stopped and the connection is probably
476  // going to be shutting down.
477  if (ec.value() == boost::asio::error::operation_aborted) {
478  return;
479 
480  // EWOULDBLOCK and EAGAIN are special cases. Everything else is
481  // treated as fatal error.
482  } else if ((ec.value() != boost::asio::error::try_again) &&
483  (ec.value() != boost::asio::error::would_block)) {
485 
486  // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
487  // read something from the socket on the next attempt.
488  } else {
489  // Sending is in progress, so push back the timeout.
490  setupRequestTimer(transaction);
491 
492  doWrite(transaction);
493  }
494  }
495 
496  // Since each transaction has its own output buffer, it is not really
497  // possible that the number of bytes written is larger than the size
498  // of the buffer. But, let's be safe and set the length to the size
499  // of the buffer if that unexpected condition occurs.
500  if (length > transaction->getOutputBufSize()) {
501  length = transaction->getOutputBufSize();
502  }
503 
504  if (length <= transaction->getOutputBufSize()) {
505  // Sending is in progress, so push back the timeout.
506  setupRequestTimer(transaction);
507  }
508 
509  // Eat the 'length' number of bytes from the output buffer and only
510  // leave the part of the response that hasn't been sent.
511  transaction->consumeOutputBuf(length);
512 
513  // Schedule the write of the unsent data.
514  doWrite(transaction);
515 }
516 
517 void
519  // Pass raw pointer rather than shared_ptr to this object,
520  // because IntervalTimer already passes shared pointer to the
521  // IntervalTimerImpl to make sure that the callback remains
522  // valid.
524  this, transaction),
525  request_timeout_, IntervalTimer::ONE_SHOT);
526 }
527 
528 void
531  this),
532  idle_timeout_, IntervalTimer::ONE_SHOT);
533 }
534 
535 void
540 
541  // We need to differentiate the transactions between a normal response and the
542  // timeout. We create new transaction from the current transaction. It is
543  // to preserve the request we're responding to.
544  auto spawned_transaction = Transaction::spawn(response_creator_, transaction);
545 
546  // The new transaction inherits the request from the original transaction
547  // if such transaction exists.
548  auto request = spawned_transaction->getRequest();
549 
550  // Depending on when the timeout occurred, the HTTP version of the request
551  // may or may not be available. Therefore we check if the HTTP version is
552  // set in the request. If it is not available, we need to create a dummy
553  // request with the default HTTP/1.0 version. This version will be used
554  // in the response.
555  if (request->context()->http_version_major_ == 0) {
556  request.reset(new HttpRequest(HttpRequest::Method::HTTP_POST, "/",
558  HostHttpHeader("dummy")));
559  request->finalize();
560  }
561 
562  // Create the timeout response.
563  HttpResponsePtr response =
564  response_creator_->createStockHttpResponse(request,
566 
567  // Send the HTTP 408 status.
568  asyncSendResponse(response, spawned_transaction);
569 }
570 
571 void
576  // In theory we should shutdown first and stop/close after but
577  // it is better to put the connection management responsibility
578  // on the client... so simply drop idle connections.
580 }
581 
582 std::string
584  try {
585  if (tcp_socket_) {
586  if (tcp_socket_->getASIOSocket().is_open()) {
587  return (tcp_socket_->getASIOSocket().remote_endpoint().address().to_string());
588  }
589  } else if (tls_socket_) {
590  if (tls_socket_->getASIOSocket().is_open()) {
591  return (tls_socket_->getASIOSocket().remote_endpoint().address().to_string());
592  }
593  }
594  } catch (...) {
595  }
596  return ("(unknown address)");
597 }
598 
599 } // end of namespace isc::http
600 } // end of namespace isc
void asyncSendResponse(const ConstHttpResponsePtr &response, TransactionPtr transaction)
Sends HTTP response asynchronously.
Definition: connection.cc:324
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:28
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:410
void handshakeCallback(const boost::system::error_code &ec)
Local callback invoked when TLS handshake is performed.
Definition: connection.cc:362
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
void recordParameters(const HttpRequestPtr &request) const
Records connection parameters into the HTTP request.
Definition: connection.cc:96
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:241
HttpAcceptorCallback acceptor_callback_
External TCP acceptor callback.
Definition: connection.h:426
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:278
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:22
HttpAcceptorPtr acceptor_
Pointer to the TCP acceptor used to accept new connections.
Definition: connection.h:416
void shutdownCallback(const boost::system::error_code &ec)
Callback invoked when TLS shutdown is performed.
Definition: connection.cc:127
#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:218
const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN_FAILED
Definition: http_messages.h:27
A generic parser for HTTP requests.
const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_START
Definition: http_messages.h:25
void shutdown()
Shutdown the socket.
Definition: connection.cc:132
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED_DETAILS
Definition: http_messages.h:19
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:403
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:472
void stopThisConnection()
Stops current connection.
Definition: connection.cc:178
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:166
void setupIdleTimer()
Reset timer for detecting idle timeout in persistent connections.
Definition: connection.cc:529
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:378
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:413
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:78
static bool recordSubject_
Access control parameters: Flags which indicate what information to record.
Definition: request.h:252
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:419
static bool recordIssuer_
Record issuer name.
Definition: request.h:255
void stop(const HttpConnectionPtr &connection)
Removes a connection from the pool and stops it.
std::string getRemoteEndpointAddressAsText() const
returns remote address in textual form
Definition: connection.cc:583
HttpResponseCreatorPtr response_creator_
Pointer to the HttpResponseCreator object used to create HTTP responses.
Definition: connection.h:423
const isc::log::MessageID HTTP_DATA_RECEIVED
Definition: http_messages.h:30
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:518
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-lfc.
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:18
asiolink::IntervalTimer request_timer_
Timer used to detect Request Timeout.
Definition: connection.h:397
void acceptorCallback(const boost::system::error_code &ec)
Local callback invoked when new connection is accepted.
Definition: connection.cc:332
void close()
Closes the socket.
Definition: connection.cc:151
const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND_DETAILS
Definition: http_messages.h:37
void asyncAccept()
Asynchronously accepts new connection.
Definition: connection.cc:190
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:24
Represents HTTP request message.
Definition: request.h:57
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:26
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:31
const isc::log::MessageID HTTP_REQUEST_RECEIVE_START
Definition: http_messages.h:33
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:75
const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND
Definition: http_messages.h:36
void requestTimeoutCallback(TransactionPtr transaction)
Callback invoked when the HTTP Request Timeout occurs.
Definition: connection.cc:536
const isc::log::MessageID HTTP_CONNECTION_STOP_FAILED
Definition: http_messages.h:29
const int DBGLVL_TRACE_BASIC_DATA
Trace data associated with the basic operations.
Definition: log_dbglevels.h:72
Pool of active HTTP connections.
long idle_timeout_
Timeout after which the persistent HTTP connection is shut down by the server.
Definition: connection.h:407
long request_timeout_
Configured Request Timeout in milliseconds.
Definition: connection.h:400