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