Kea 2.5.8
connection.cc
Go to the documentation of this file.
1// Copyright (C) 2017-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 <http/connection.h>
12#include <http/http_log.h>
13#include <http/http_messages.h>
14#include <boost/make_shared.hpp>
15#include <functional>
16
17using namespace isc::asiolink;
18namespace ph = std::placeholders;
19
20namespace {
21
25constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
26
27}
28
29namespace isc {
30namespace http {
31
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
56void
57HttpConnection::
58SocketCallback::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),
79 acceptor_(acceptor),
80 connection_pool_(connection_pool),
81 response_creator_(response_creator),
82 acceptor_callback_(callback) {
83 if (!tls_context) {
85 } else {
87 tls_context));
88 }
89}
90
92 close();
93}
94
95void
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
126void
127HttpConnection::shutdownCallback(const boost::system::error_code&) {
128 tls_socket_->close();
129}
130
131void
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
150void
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
165void
167 try {
171 connection_pool_.shutdown(shared_from_this());
172 } catch (...) {
174 }
175}
176
177void
179 try {
183 connection_pool_.stop(shared_from_this());
184 } catch (...) {
186 }
187}
188
189void
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
217void
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
240void
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) {
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
277void
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.
315 doRead();
316 }
317 }
318 } catch (...) {
320 }
321}
322
323void
325 TransactionPtr transaction) {
326 transaction->setOutputBuf(response->toString());
327 doWrite(transaction);
328}
329
330
331void
332HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
333 if (!acceptor_->isOpen()) {
334 return;
335 }
336
337 if (ec) {
339 }
340
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
361void
362HttpConnection::handshakeCallback(const boost::system::error_code& ec) {
363 if (ec) {
366 .arg(ec.message());
368 } else {
372
373 doRead();
374 }
375}
376
377void
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
471void
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
517void
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),
526}
527
528void
531 this),
533}
534
535void
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
571void
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
582std::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
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.
Represents HTTP Host header.
Definition: http_header.h:68
Generic error reported within HttpConnection class.
Definition: connection.h:26
Pool of active HTTP connections.
void stop(const HttpConnectionPtr &connection)
Removes a connection from the pool and stops it.
void shutdown(const HttpConnectionPtr &connection)
Removes a connection from the pool and shutdown it.
static TransactionPtr create(const HttpResponseCreatorPtr &response_creator)
Creates new transaction instance.
Definition: connection.cc:42
Transaction(const HttpResponseCreatorPtr &response_creator, const HttpRequestPtr &request=HttpRequestPtr())
Constructor.
Definition: connection.cc:32
static TransactionPtr spawn(const HttpResponseCreatorPtr &response_creator, const TransactionPtr &transaction)
Creates new transaction from the current transaction.
Definition: connection.cc:47
HttpConnectionPool & connection_pool_
Connection pool holding this connection.
Definition: connection.h:419
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
asiolink::TlsContextPtr tls_context_
TLS context.
Definition: connection.h:403
void recordParameters(const HttpRequestPtr &request) const
Records connection parameters into the HTTP request.
Definition: connection.cc:96
void acceptorCallback(const boost::system::error_code &ec)
Local callback invoked when new connection is accepted.
Definition: connection.cc:332
boost::shared_ptr< Transaction > TransactionPtr
Shared pointer to the Transaction.
Definition: connection.h:86
void setupIdleTimer()
Reset timer for detecting idle timeout in persistent connections.
Definition: connection.cc:529
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 doHandshake()
Asynchronously performs TLS handshake.
Definition: connection.cc:218
void asyncSendResponse(const ConstHttpResponsePtr &response, TransactionPtr transaction)
Sends HTTP response asynchronously.
Definition: connection.cc:324
void doRead(TransactionPtr transaction=TransactionPtr())
Starts asynchronous read from the socket.
Definition: connection.cc:241
HttpAcceptorPtr acceptor_
Pointer to the TCP acceptor used to accept new connections.
Definition: connection.h:416
std::unique_ptr< asiolink::TLSSocket< SocketCallback > > tls_socket_
TLS socket used by this connection.
Definition: connection.h:413
void doWrite(TransactionPtr transaction)
Starts asynchronous write to the socket.
Definition: connection.cc:278
void setupRequestTimer(TransactionPtr transaction=TransactionPtr())
Reset timer for detecting request timeouts.
Definition: connection.cc:518
HttpConnection(const asiolink::IOServicePtr &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
void shutdown()
Shutdown the socket.
Definition: connection.cc:132
void shutdownCallback(const boost::system::error_code &ec)
Callback invoked when TLS shutdown is performed.
Definition: connection.cc:127
void close()
Closes the socket.
Definition: connection.cc:151
void stopThisConnection()
Stops current connection.
Definition: connection.cc:178
std::string getRemoteEndpointAddressAsText() const
returns remote address in textual form
Definition: connection.cc:583
void handshakeCallback(const boost::system::error_code &ec)
Local callback invoked when TLS handshake is performed.
Definition: connection.cc:362
HttpResponseCreatorPtr response_creator_
Pointer to the HttpResponseCreator object used to create HTTP responses.
Definition: connection.h:423
long idle_timeout_
Timeout after which the persistent HTTP connection is shut down by the server.
Definition: connection.h:407
void asyncAccept()
Asynchronously accepts new connection.
Definition: connection.cc:190
std::unique_ptr< asiolink::TCPSocket< SocketCallback > > tcp_socket_
TCP socket used by this connection.
Definition: connection.h:410
void requestTimeoutCallback(TransactionPtr transaction)
Callback invoked when the HTTP Request Timeout occurs.
Definition: connection.cc:536
asiolink::IntervalTimer request_timer_
Timer used to detect Request Timeout.
Definition: connection.h:397
HttpAcceptorCallback acceptor_callback_
External TCP acceptor callback.
Definition: connection.h:426
long request_timeout_
Configured Request Timeout in milliseconds.
Definition: connection.h:400
void shutdownConnection()
Shuts down current connection.
Definition: connection.cc:166
virtual ~HttpConnection()
Destructor.
Definition: connection.cc:91
static std::string logFormatHttpMessage(const std::string &message, const size_t limit=0)
Formats provided HTTP message for logging.
A generic parser for HTTP requests.
Represents HTTP request message.
Definition: request.h:57
static bool recordIssuer_
Record issuer name.
Definition: request.h:255
static bool recordSubject_
Access control parameters: Flags which indicate what information to record.
Definition: request.h:252
#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 isc::log::MessageID HTTP_CONNECTION_SHUTDOWN
Definition: http_messages.h:26
const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_START
Definition: http_messages.h:25
const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN_FAILED
Definition: http_messages.h:27
boost::shared_ptr< const HttpResponse > ConstHttpResponsePtr
Pointer to the const HttpResponse object.
Definition: response.h:84
const isc::log::MessageID HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED
Definition: http_messages.h:31
const isc::log::MessageID HTTP_DATA_RECEIVED
Definition: http_messages.h:30
const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS
Definition: http_messages.h:13
const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND_DETAILS
Definition: http_messages.h:37
const isc::log::MessageID HTTP_CONNECTION_STOP_FAILED
Definition: http_messages.h:29
isc::log::Logger http_logger("http")
Defines the logger used within libkea-http library.
Definition: http_log.h:18
const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED
Definition: http_messages.h:18
const isc::log::MessageID HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED
Definition: http_messages.h:22
const isc::log::MessageID HTTPS_REQUEST_RECEIVE_START
Definition: http_messages.h:11
const isc::log::MessageID HTTP_CONNECTION_STOP
Definition: http_messages.h:28
const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_FAILED
Definition: http_messages.h:24
boost::shared_ptr< HttpResponseCreator > HttpResponseCreatorPtr
Pointer to the HttpResponseCreator object.
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition: response.h:81
const isc::log::MessageID HTTP_REQUEST_RECEIVE_START
Definition: http_messages.h:33
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
boost::shared_ptr< HttpRequest > HttpRequestPtr
Pointer to the HttpRequest object.
Definition: request.h:30
boost::shared_ptr< HttpAcceptor > HttpAcceptorPtr
Type of shared pointer to TCP acceptors.
Definition: http_acceptor.h:31
boost::shared_ptr< HttpsAcceptor > HttpsAcceptorPtr
Type of shared pointer to TLS acceptors.
Definition: http_acceptor.h:34
const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED
Definition: http_messages.h:12
const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND
Definition: http_messages.h:36
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_BASIC_DATA
Trace data associated with the basic operations.
Definition: log_dbglevels.h:72
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:75
Defines the logger used by the top-level component of kea-lfc.
static const HttpVersion & HTTP_10()
HTTP version 1.0.
Definition: http_types.h:53