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
9 #include <asiolink/asio_wrapper.h>
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
22 /// @brief Maximum size of the HTTP message that can be logged.
23 ///
24 /// The part of the HTTP message beyond this value is truncated.
25 constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
26
27 }
28
29 namespace isc {
30 namespace http {
31
Transaction(const HttpResponseCreatorPtr & response_creator,const HttpRequestPtr & request)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
41 HttpConnection::TransactionPtr
create(const HttpResponseCreatorPtr & response_creator)42 HttpConnection::Transaction::create(const HttpResponseCreatorPtr& response_creator) {
43 return (boost::make_shared<Transaction>(response_creator));
44 }
45
46 HttpConnection::TransactionPtr
spawn(const HttpResponseCreatorPtr & response_creator,const TransactionPtr & transaction)47 HttpConnection::Transaction::spawn(const HttpResponseCreatorPtr& response_creator,
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::
operator ()(boost::system::error_code ec,size_t length)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
HttpConnection(asiolink::IOService & io_service,const HttpAcceptorPtr & acceptor,const TlsContextPtr & tls_context,HttpConnectionPool & connection_pool,const HttpResponseCreatorPtr & response_creator,const HttpAcceptorCallback & callback,const long request_timeout,const long idle_timeout)65 HttpConnection::HttpConnection(asiolink::IOService& io_service,
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 {
86 tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
87 tls_context));
88 }
89 }
90
~HttpConnection()91 HttpConnection::~HttpConnection() {
92 close();
93 }
94
95 void
shutdownCallback(const boost::system::error_code &)96 HttpConnection::shutdownCallback(const boost::system::error_code&) {
97 tls_socket_->close();
98 }
99
100 void
shutdown()101 HttpConnection::shutdown() {
102 request_timer_.cancel();
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
close()120 HttpConnection::close() {
121 request_timer_.cancel();
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
shutdownConnection()135 HttpConnection::shutdownConnection() {
136 try {
137 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
138 HTTP_CONNECTION_SHUTDOWN)
139 .arg(getRemoteEndpointAddressAsText());
140 connection_pool_.shutdown(shared_from_this());
141 } catch (...) {
142 LOG_ERROR(http_logger, HTTP_CONNECTION_SHUTDOWN_FAILED);
143 }
144 }
145
146 void
stopThisConnection()147 HttpConnection::stopThisConnection() {
148 try {
149 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
150 HTTP_CONNECTION_STOP)
151 .arg(getRemoteEndpointAddressAsText());
152 connection_pool_.stop(shared_from_this());
153 } catch (...) {
154 LOG_ERROR(http_logger, HTTP_CONNECTION_STOP_FAILED);
155 }
156 }
157
158 void
asyncAccept()159 HttpConnection::asyncAccept() {
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.
163 HttpAcceptorCallback cb = std::bind(&HttpConnection::acceptorCallback,
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
doHandshake()187 HttpConnection::doHandshake() {
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
doRead(TransactionPtr transaction)210 HttpConnection::doRead(TransactionPtr transaction) {
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 (...) {
241 stopThisConnection();
242 }
243 }
244
245 void
doWrite(HttpConnection::TransactionPtr transaction)246 HttpConnection::doWrite(HttpConnection::TransactionPtr transaction) {
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()) {
276 stopThisConnection();
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 (...) {
287 stopThisConnection();
288 }
289 }
290
291 void
asyncSendResponse(const ConstHttpResponsePtr & response,TransactionPtr transaction)292 HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response,
293 TransactionPtr transaction) {
294 transaction->setOutputBuf(response->toString());
295 doWrite(transaction);
296 }
297
298
299 void
acceptorCallback(const boost::system::error_code & ec)300 HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
301 if (!acceptor_->isOpen()) {
302 return;
303 }
304
305 if (ec) {
306 stopThisConnection();
307 }
308
309 acceptor_callback_(ec);
310
311 if (!ec) {
312 if (!tls_context_) {
313 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
314 HTTP_REQUEST_RECEIVE_START)
315 .arg(getRemoteEndpointAddressAsText())
316 .arg(static_cast<unsigned>(request_timeout_/1000));
317 } else {
318 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
319 HTTP_CONNECTION_HANDSHAKE_START)
320 .arg(getRemoteEndpointAddressAsText())
321 .arg(static_cast<unsigned>(request_timeout_/1000));
322 }
323
324 setupRequestTimer();
325 doHandshake();
326 }
327 }
328
329 void
handshakeCallback(const boost::system::error_code & ec)330 HttpConnection::handshakeCallback(const boost::system::error_code& ec) {
331 if (ec) {
332 LOG_INFO(http_logger, HTTP_CONNECTION_HANDSHAKE_FAILED)
333 .arg(getRemoteEndpointAddressAsText())
334 .arg(ec.message());
335 stopThisConnection();
336 } else {
337 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
338 HTTPS_REQUEST_RECEIVE_START)
339 .arg(getRemoteEndpointAddressAsText());
340
341 doRead();
342 }
343 }
344
345 void
socketReadCallback(HttpConnection::TransactionPtr transaction,boost::system::error_code ec,size_t length)346 HttpConnection::socketReadCallback(HttpConnection::TransactionPtr transaction,
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)) {
358 stopThisConnection();
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) {
373 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
374 HTTP_DATA_RECEIVED)
375 .arg(length)
376 .arg(getRemoteEndpointAddressAsText());
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
393 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
394 HTTP_CLIENT_REQUEST_RECEIVED)
395 .arg(getRemoteEndpointAddressAsText());
396
397 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
398 HTTP_CLIENT_REQUEST_RECEIVED_DETAILS)
399 .arg(getRemoteEndpointAddressAsText())
400 .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
401
402 } catch (const std::exception& ex) {
403 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
404 HTTP_BAD_CLIENT_REQUEST_RECEIVED)
405 .arg(getRemoteEndpointAddressAsText())
406 .arg(ex.what());
407
408 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
409 HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS)
410 .arg(getRemoteEndpointAddressAsText())
411 .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
412 }
413
414 // Don't want to timeout if creation of the response takes long.
415 request_timer_.cancel();
416
417 // Create the response from the received request using the custom
418 // response creator.
419 HttpResponsePtr response = response_creator_->createHttpResponse(transaction->getRequest());
420 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
421 HTTP_SERVER_RESPONSE_SEND)
422 .arg(response->toBriefString())
423 .arg(getRemoteEndpointAddressAsText());
424
425 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
426 HTTP_SERVER_RESPONSE_SEND_DETAILS)
427 .arg(getRemoteEndpointAddressAsText())
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
socketWriteCallback(HttpConnection::TransactionPtr transaction,boost::system::error_code ec,size_t length)440 HttpConnection::socketWriteCallback(HttpConnection::TransactionPtr transaction,
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)) {
452 stopThisConnection();
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
setupRequestTimer(TransactionPtr transaction)486 HttpConnection::setupRequestTimer(TransactionPtr transaction) {
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.
491 request_timer_.setup(std::bind(&HttpConnection::requestTimeoutCallback,
492 this, transaction),
493 request_timeout_, IntervalTimer::ONE_SHOT);
494 }
495
496 void
setupIdleTimer()497 HttpConnection::setupIdleTimer() {
498 request_timer_.setup(std::bind(&HttpConnection::idleTimeoutCallback,
499 this),
500 idle_timeout_, IntervalTimer::ONE_SHOT);
501 }
502
503 void
requestTimeoutCallback(TransactionPtr transaction)504 HttpConnection::requestTimeoutCallback(TransactionPtr transaction) {
505 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
506 HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED)
507 .arg(getRemoteEndpointAddressAsText());
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, "/",
525 HttpVersion::HTTP_10(),
526 HostHttpHeader("dummy")));
527 request->finalize();
528 }
529
530 // Create the timeout response.
531 HttpResponsePtr response =
532 response_creator_->createStockHttpResponse(request,
533 HttpStatusCode::REQUEST_TIMEOUT);
534
535 // Send the HTTP 408 status.
536 asyncSendResponse(response, spawned_transaction);
537 }
538
539 void
idleTimeoutCallback()540 HttpConnection::idleTimeoutCallback() {
541 LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
542 HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED)
543 .arg(getRemoteEndpointAddressAsText());
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.
547 stopThisConnection();
548 }
549
550 std::string
getRemoteEndpointAddressAsText() const551 HttpConnection::getRemoteEndpointAddressAsText() const {
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
569