blob: d34fb2e582a450db1935ad60b2ec934498adbf06 [file] [log] [blame]
AppaRao Pulibd030d02020-03-20 03:34:29 +05301/*
2// Copyright (c) 2020 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
Ed Tanousbb49eb52022-06-28 12:02:42 -070017#include <boost/asio/io_context.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053018#include <boost/asio/ip/address.hpp>
19#include <boost/asio/ip/basic_endpoint.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070020#include <boost/asio/ip/tcp.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000021#include <boost/asio/ssl/context.hpp>
22#include <boost/asio/ssl/error.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070023#include <boost/asio/steady_timer.hpp>
24#include <boost/beast/core/flat_buffer.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070025#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070026#include <boost/beast/core/tcp_stream.hpp>
27#include <boost/beast/http/message.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070028#include <boost/beast/http/parser.hpp>
29#include <boost/beast/http/read.hpp>
30#include <boost/beast/http/string_body.hpp>
31#include <boost/beast/http/write.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000032#include <boost/beast/ssl/ssl_stream.hpp>
AppaRao Pulibd030d02020-03-20 03:34:29 +053033#include <boost/beast/version.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000034#include <boost/container/devector.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070035#include <boost/system/error_code.hpp>
36#include <http/http_response.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053037#include <include/async_resolve.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070038#include <logging.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000039#include <ssl_key_handler.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050040
AppaRao Pulibd030d02020-03-20 03:34:29 +053041#include <cstdlib>
42#include <functional>
43#include <iostream>
44#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053045#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053046#include <string>
47
48namespace crow
49{
50
Carson Labradof52c03c2022-03-23 18:50:15 +000051// It is assumed that the BMC should be able to handle 4 parallel connections
52constexpr uint8_t maxPoolSize = 4;
53constexpr uint8_t maxRequestQueueSize = 50;
Carson Labrado17dcc312022-07-28 22:17:28 +000054constexpr unsigned int httpReadBodyLimit = 131072;
Carson Labrado4d942722022-06-22 22:16:10 +000055constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053056
AppaRao Pulibd030d02020-03-20 03:34:29 +053057enum class ConnState
58{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053059 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053060 resolveInProgress,
61 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053062 connectInProgress,
63 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053064 connected,
AppaRao Pulie38778a2022-06-27 23:09:03 +000065 handshakeInProgress,
66 handshakeFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053067 sendInProgress,
68 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053069 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053070 recvFailed,
71 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053072 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053073 suspended,
74 terminated,
75 abortConnection,
AppaRao Pulie38778a2022-06-27 23:09:03 +000076 sslInitFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053077 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053078};
79
Carson Labradoa7a80292022-06-01 16:01:52 +000080static inline boost::system::error_code
81 defaultRetryHandler(unsigned int respCode)
82{
83 // As a default, assume 200X is alright
84 BMCWEB_LOG_DEBUG << "Using default check for response code validity";
85 if ((respCode < 200) || (respCode >= 300))
86 {
87 return boost::system::errc::make_error_code(
88 boost::system::errc::result_out_of_range);
89 }
90
91 // Return 0 if the response code is valid
92 return boost::system::errc::make_error_code(boost::system::errc::success);
93};
94
Carson Labradof52c03c2022-03-23 18:50:15 +000095// We need to allow retry information to be set before a message has been sent
96// and a connection pool has been created
97struct RetryPolicyData
98{
99 uint32_t maxRetryAttempts = 5;
100 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
101 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradoa7a80292022-06-01 16:01:52 +0000102 std::function<boost::system::error_code(unsigned int respCode)>
103 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000104};
105
106struct PendingRequest
107{
Carson Labrado244256c2022-04-27 17:16:32 +0000108 boost::beast::http::request<boost::beast::http::string_body> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000109 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000110 RetryPolicyData retryPolicy;
Carson Labrado039a47e2022-04-05 16:03:20 +0000111 PendingRequest(
Ed Tanous8a592812022-06-04 09:06:59 -0700112 boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
113 const std::function<void(bool, uint32_t, Response&)>& callbackIn,
114 const RetryPolicyData& retryPolicyIn) :
115 req(std::move(reqIn)),
116 callback(callbackIn), retryPolicy(retryPolicyIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000117 {}
118};
119
120class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530121{
122 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000123 ConnState state = ConnState::initialized;
124 uint32_t retryCount = 0;
125 bool runningTimer = false;
126 std::string subId;
127 std::string host;
128 uint16_t port;
129 uint32_t connId;
130
131 // Retry policy information
132 // This should be updated before each message is sent
133 RetryPolicyData retryPolicy;
134
135 // Data buffers
AppaRao Pulibd030d02020-03-20 03:34:29 +0530136 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530137 std::optional<
138 boost::beast::http::response_parser<boost::beast::http::string_body>>
139 parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000140 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000141 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530142
Carson Labradof52c03c2022-03-23 18:50:15 +0000143 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000144 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000145 crow::async_resolve::Resolver resolver;
146 boost::beast::tcp_stream conn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000147 std::optional<boost::beast::ssl_stream<boost::beast::tcp_stream&>> sslConn;
148
Carson Labradof52c03c2022-03-23 18:50:15 +0000149 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700150
Carson Labradof52c03c2022-03-23 18:50:15 +0000151 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530152
Sunitha Harish29a82b02021-02-18 15:54:16 +0530153 void doResolve()
154 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530155 state = ConnState::resolveInProgress;
Carson Labradof52c03c2022-03-23 18:50:15 +0000156 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
157 << std::to_string(port)
158 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530159
160 auto respHandler =
161 [self(shared_from_this())](
162 const boost::beast::error_code ec,
163 const std::vector<boost::asio::ip::tcp::endpoint>&
164 endpointList) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700165 if (ec || (endpointList.empty()))
166 {
167 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
168 self->state = ConnState::resolveFailed;
169 self->waitAndRetry();
170 return;
171 }
172 BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
173 << std::to_string(self->port)
174 << ", id: " << std::to_string(self->connId);
175 self->doConnect(endpointList);
176 };
Carson Labradof52c03c2022-03-23 18:50:15 +0000177
Sunitha Harish29a82b02021-02-18 15:54:16 +0530178 resolver.asyncResolve(host, port, std::move(respHandler));
179 }
180
181 void doConnect(
182 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530183 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530184 state = ConnState::connectInProgress;
185
Carson Labradof52c03c2022-03-23 18:50:15 +0000186 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
187 << std::to_string(port)
188 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530189
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530190 conn.expires_after(std::chrono::seconds(30));
Ed Tanous002d39b2022-05-31 08:59:27 -0700191 conn.async_connect(endpointList,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000192 std::bind_front(&ConnectionInfo::afterConnect, this,
193 shared_from_this()));
194 }
195
196 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
197 boost::beast::error_code ec,
198 const boost::asio::ip::tcp::endpoint& endpoint)
199 {
200
201 if (ec)
202 {
203 BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
204 << ":" << std::to_string(endpoint.port())
205 << ", id: " << std::to_string(connId)
206 << " failed: " << ec.message();
207 state = ConnState::connectFailed;
208 waitAndRetry();
209 return;
210 }
211 BMCWEB_LOG_DEBUG << "Connected to: " << endpoint.address().to_string()
212 << ":" << std::to_string(endpoint.port())
213 << ", id: " << std::to_string(connId);
214 if (sslConn)
215 {
216 doSSLHandshake();
217 return;
218 }
219 state = ConnState::connected;
220 sendMessage();
221 }
222
223 void doSSLHandshake()
224 {
225 if (!sslConn)
226 {
227 return;
228 }
229 state = ConnState::handshakeInProgress;
230 sslConn->async_handshake(
231 boost::asio::ssl::stream_base::client,
232 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
233 shared_from_this()));
234 }
235
236 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
237 boost::beast::error_code ec)
238 {
239 if (ec)
240 {
241 BMCWEB_LOG_ERROR << "SSL Handshake failed -"
242 << " id: " << std::to_string(connId)
243 << " error: " << ec.message();
244 state = ConnState::handshakeFailed;
245 waitAndRetry();
246 return;
247 }
248 BMCWEB_LOG_DEBUG << "SSL Handshake successful -"
249 << " id: " << std::to_string(connId);
250 state = ConnState::connected;
251 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530252 }
253
Carson Labradof52c03c2022-03-23 18:50:15 +0000254 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530255 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530256 state = ConnState::sendInProgress;
257
AppaRao Pulibd030d02020-03-20 03:34:29 +0530258 // Set a timeout on the operation
259 conn.expires_after(std::chrono::seconds(30));
260
261 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000262 if (sslConn)
263 {
264 boost::beast::http::async_write(
265 *sslConn, req,
266 std::bind_front(&ConnectionInfo::afterWrite, this,
267 shared_from_this()));
268 }
269 else
270 {
271 boost::beast::http::async_write(
272 conn, req,
273 std::bind_front(&ConnectionInfo::afterWrite, this,
274 shared_from_this()));
275 }
276 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530277
AppaRao Pulie38778a2022-06-27 23:09:03 +0000278 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
279 const boost::beast::error_code& ec, size_t bytesTransferred)
280 {
281 if (ec)
282 {
283 BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
284 state = ConnState::sendFailed;
285 waitAndRetry();
286 return;
287 }
288 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
289 << bytesTransferred;
290
291 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530292 }
293
294 void recvMessage()
295 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530296 state = ConnState::recvInProgress;
297
298 parser.emplace(std::piecewise_construct, std::make_tuple());
299 parser->body_limit(httpReadBodyLimit);
300
AppaRao Pulibd030d02020-03-20 03:34:29 +0530301 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000302 if (sslConn)
303 {
304 boost::beast::http::async_read(
305 *sslConn, buffer, *parser,
306 std::bind_front(&ConnectionInfo::afterRead, this,
307 shared_from_this()));
308 }
309 else
310 {
311 boost::beast::http::async_read(
312 conn, buffer, *parser,
313 std::bind_front(&ConnectionInfo::afterRead, this,
314 shared_from_this()));
315 }
316 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530317
AppaRao Pulie38778a2022-06-27 23:09:03 +0000318 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
319 const boost::beast::error_code& ec,
320 const std::size_t& bytesTransferred)
321 {
322 if (ec && ec != boost::asio::ssl::error::stream_truncated)
323 {
324 BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
325 state = ConnState::recvFailed;
326 waitAndRetry();
327 return;
328 }
329 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
330 << bytesTransferred;
331 BMCWEB_LOG_DEBUG << "recvMessage() data: " << parser->get().body();
332
333 unsigned int respCode = parser->get().result_int();
334 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " << respCode;
335
336 // Make sure the received response code is valid as defined by
337 // the associated retry policy
338 if (retryPolicy.invalidResp(respCode))
339 {
340 // The listener failed to receive the Sent-Event
341 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
342 "receive Sent-Event. Header Response Code: "
Ed Tanous002d39b2022-05-31 08:59:27 -0700343 << respCode;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000344 state = ConnState::recvFailed;
345 waitAndRetry();
346 return;
347 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700348
AppaRao Pulie38778a2022-06-27 23:09:03 +0000349 // Send is successful
350 // Reset the counter just in case this was after retrying
351 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530352
AppaRao Pulie38778a2022-06-27 23:09:03 +0000353 // Keep the connection alive if server supports it
354 // Else close the connection
355 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
356 << parser->keep_alive();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530357
AppaRao Pulie38778a2022-06-27 23:09:03 +0000358 // Copy the response into a Response object so that it can be
359 // processed by the callback function.
360 res.clear();
361 res.stringResponse = parser->release();
362 callback(parser->keep_alive(), connId, res);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530363 }
364
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530365 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530366 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000367 if ((retryCount >= retryPolicy.maxRetryAttempts) ||
368 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530369 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530370 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
Carson Labradof52c03c2022-03-23 18:50:15 +0000371 BMCWEB_LOG_DEBUG << "Retry policy: "
372 << retryPolicy.retryPolicyAction;
Carson Labrado039a47e2022-04-05 16:03:20 +0000373
374 // We want to return a 502 to indicate there was an error with the
375 // external server
376 res.clear();
Ed Tanous40d799e2022-06-28 12:07:22 -0700377 res.result(boost::beast::http::status::bad_gateway);
Carson Labrado039a47e2022-04-05 16:03:20 +0000378
Carson Labradof52c03c2022-03-23 18:50:15 +0000379 if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530380 {
381 // TODO: delete subscription
382 state = ConnState::terminated;
Carson Labrado039a47e2022-04-05 16:03:20 +0000383 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530384 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000385 if (retryPolicy.retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530386 {
387 state = ConnState::suspended;
Carson Labrado039a47e2022-04-05 16:03:20 +0000388 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530389 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530390 // Reset the retrycount to zero so that client can try connecting
391 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700392 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530393 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530394 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530395
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530396 if (runningTimer)
397 {
398 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
399 return;
400 }
401 runningTimer = true;
402
403 retryCount++;
404
Carson Labradof52c03c2022-03-23 18:50:15 +0000405 BMCWEB_LOG_DEBUG << "Attempt retry after "
406 << std::to_string(
407 retryPolicy.retryIntervalSecs.count())
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530408 << " seconds. RetryCount = " << retryCount;
Carson Labradof52c03c2022-03-23 18:50:15 +0000409 timer.expires_after(retryPolicy.retryIntervalSecs);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530410 timer.async_wait(
Carson Labradof52c03c2022-03-23 18:50:15 +0000411 [self(shared_from_this())](const boost::system::error_code ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700412 if (ec == boost::asio::error::operation_aborted)
413 {
414 BMCWEB_LOG_DEBUG
415 << "async_wait failed since the operation is aborted"
416 << ec.message();
417 }
418 else if (ec)
419 {
420 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
421 // Ignore the error and continue the retry loop to attempt
422 // sending the event as per the retry policy
423 }
424 self->runningTimer = false;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530425
Ed Tanous002d39b2022-05-31 08:59:27 -0700426 // Let's close the connection and restart from resolve.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000427 self->doClose(true);
Ed Tanous002d39b2022-05-31 08:59:27 -0700428 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530429 }
430
AppaRao Pulie38778a2022-06-27 23:09:03 +0000431 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530432 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000433 boost::beast::error_code ec;
434 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
435 conn.close();
436
437 // not_connected happens sometimes so don't bother reporting it.
438 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530439 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000440 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
441 << ", id: " << std::to_string(connId)
442 << "shutdown failed: " << ec.message();
Carson Labradof52c03c2022-03-23 18:50:15 +0000443 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000444 else
445 {
446 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
447 << ", id: " << std::to_string(connId)
448 << " closed gracefully";
449 }
Ed Tanousca723762022-06-28 19:40:39 -0700450
AppaRao Pulie38778a2022-06-27 23:09:03 +0000451 if ((state != ConnState::suspended) && (state != ConnState::terminated))
452 {
453 if (retry)
454 {
455 // Now let's try to resend the data
456 state = ConnState::retry;
457 this->doResolve();
458 }
459 else
460 {
461 state = ConnState::closed;
462 }
463 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000464 }
465
AppaRao Pulie38778a2022-06-27 23:09:03 +0000466 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000467 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000468 if (!sslConn)
469 {
470 shutdownConn(retry);
471 return;
472 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000473
AppaRao Pulie38778a2022-06-27 23:09:03 +0000474 sslConn->async_shutdown(
475 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
476 shared_from_this(), retry));
477 }
478
479 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
480 bool retry, const boost::system::error_code& ec)
481 {
482
483 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000484 {
485 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
486 << ", id: " << std::to_string(connId)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000487 << " shutdown failed: " << ec.message();
Carson Labradof52c03c2022-03-23 18:50:15 +0000488 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000489 else
490 {
491 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
492 << ", id: " << std::to_string(connId)
493 << " closed gracefully";
494 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000495 shutdownConn(retry);
496 }
Ed Tanousca723762022-06-28 19:40:39 -0700497
AppaRao Pulie38778a2022-06-27 23:09:03 +0000498 void setCipherSuiteTLSext()
499 {
500 if (!sslConn)
501 {
502 return;
503 }
504 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
505 // file but its having old style casting (name is cast to void*).
506 // Since bmcweb compiler treats all old-style-cast as error, its
507 // causing the build failure. So replaced the same macro inline and
508 // did corrected the code by doing static_cast to viod*. This has to
509 // be fixed in openssl library in long run. Set SNI Hostname (many
510 // hosts need this to handshake successfully)
511 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
512 TLSEXT_NAMETYPE_host_name,
513 static_cast<void*>(&host.front())) == 0)
514
515 {
516 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
517 boost::asio::error::get_ssl_category()};
518
519 BMCWEB_LOG_ERROR << "SSL_set_tlsext_host_name " << host << ":"
520 << port << ", id: " << std::to_string(connId)
521 << " failed: " << ec.message();
522 // Set state as sslInit failed so that we close the connection
523 // and take appropriate action as per retry configuration.
524 state = ConnState::sslInitFailed;
525 waitAndRetry();
526 return;
527 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530528 }
529
530 public:
AppaRao Pulie38778a2022-06-27 23:09:03 +0000531 explicit ConnectionInfo(boost::asio::io_context& iocIn,
532 const std::string& idIn,
533 const std::string& destIPIn, uint16_t destPortIn,
534 bool useSSL, unsigned int connIdIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700535 subId(idIn),
AppaRao Pulie38778a2022-06-27 23:09:03 +0000536 host(destIPIn), port(destPortIn), connId(connIdIn), conn(iocIn),
537 timer(iocIn)
538 {
539 if (useSSL)
540 {
541 std::optional<boost::asio::ssl::context> sslCtx =
542 ensuressl::getSSLClientContext();
543
544 if (!sslCtx)
545 {
546 BMCWEB_LOG_ERROR << "prepareSSLContext failed - " << host << ":"
547 << port << ", id: " << std::to_string(connId);
548 // Don't retry if failure occurs while preparing SSL context
549 // such as certificate is invalid or set cipher failure or set
550 // host name failure etc... Setting conn state to sslInitFailed
551 // and connection state will be transitioned to next state
552 // depending on retry policy set by subscription.
553 state = ConnState::sslInitFailed;
554 waitAndRetry();
555 return;
556 }
557 sslConn.emplace(conn, *sslCtx);
558 setCipherSuiteTLSext();
559 }
560 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000561};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530562
Carson Labradof52c03c2022-03-23 18:50:15 +0000563class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
564{
565 private:
566 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000567 std::string id;
568 std::string destIP;
569 uint16_t destPort;
570 bool useSSL;
Carson Labradof52c03c2022-03-23 18:50:15 +0000571 std::vector<std::shared_ptr<ConnectionInfo>> connections;
572 boost::container::devector<PendingRequest> requestQueue;
573
574 friend class HttpClient;
575
Carson Labrado244256c2022-04-27 17:16:32 +0000576 // Configure a connections's request, callback, and retry info in
577 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000578 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530579 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000580 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530581 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000582 BMCWEB_LOG_ERROR
583 << "setConnProps() should not have been called when requestQueue is empty";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530584 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530585 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530586
Carson Labrado244256c2022-04-27 17:16:32 +0000587 auto nextReq = requestQueue.front();
588 conn.retryPolicy = std::move(nextReq.retryPolicy);
589 conn.req = std::move(nextReq.req);
590 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000591
592 BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
593 << ":" << std::to_string(conn.port)
Carson Labradoa7a80292022-06-01 16:01:52 +0000594 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000595
596 // We can remove the request from the queue at this point
597 requestQueue.pop_front();
598 }
599
600 // Configures a connection to use the specific retry policy.
601 inline void setConnRetryPolicy(ConnectionInfo& conn,
602 const RetryPolicyData& retryPolicy)
603 {
604 BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
Carson Labradoa7a80292022-06-01 16:01:52 +0000605 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000606
607 conn.retryPolicy = retryPolicy;
608 }
609
610 // Gets called as part of callback after request is sent
611 // Reuses the connection if there are any requests waiting to be sent
612 // Otherwise closes the connection if it is not a keep-alive
613 void sendNext(bool keepAlive, uint32_t connId)
614 {
615 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000616
617 // Allow the connection's handler to be deleted
618 // This is needed because of Redfish Aggregation passing an
619 // AsyncResponse shared_ptr to this callback
620 conn->callback = nullptr;
621
Carson Labradof52c03c2022-03-23 18:50:15 +0000622 // Reuse the connection to send the next request in the queue
623 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530624 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000625 BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
626 << " requests remaining in queue for " << destIP
627 << ":" << std::to_string(destPort)
628 << ", reusing connnection "
629 << std::to_string(connId);
630
631 setConnProps(*conn);
632
633 if (keepAlive)
634 {
635 conn->sendMessage();
636 }
637 else
638 {
639 // Server is not keep-alive enabled so we need to close the
640 // connection and then start over from resolve
641 conn->doClose();
642 conn->doResolve();
643 }
644 return;
645 }
646
647 // No more messages to send so close the connection if necessary
648 if (keepAlive)
649 {
650 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530651 }
652 else
653 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000654 // Abort the connection since server is not keep-alive enabled
655 conn->state = ConnState::abortConnection;
656 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530657 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530658 }
659
Carson Labrado244256c2022-04-27 17:16:32 +0000660 void sendData(std::string& data, const std::string& destUri,
661 const boost::beast::http::fields& httpHeader,
662 const boost::beast::http::verb verb,
663 const RetryPolicyData& retryPolicy,
Ed Tanous6b3db602022-06-28 19:41:44 -0700664 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530665 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000666 std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
667
668 // Callback to be called once the request has been sent
Carson Labrado039a47e2022-04-05 16:03:20 +0000669 auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
670 Response& res) {
671 // Allow provided callback to perform additional processing of the
672 // request
673 resHandler(res);
674
Carson Labradof52c03c2022-03-23 18:50:15 +0000675 // If requests remain in the queue then we want to reuse this
676 // connection to send the next request
677 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
678 if (!self)
679 {
680 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
681 return;
682 }
683
684 self->sendNext(keepAlive, connId);
685 };
686
Carson Labrado244256c2022-04-27 17:16:32 +0000687 // Construct the request to be sent
688 boost::beast::http::request<boost::beast::http::string_body> thisReq(
689 verb, destUri, 11, "", httpHeader);
690 thisReq.set(boost::beast::http::field::host, destIP);
691 thisReq.keep_alive(true);
692 thisReq.body() = std::move(data);
693 thisReq.prepare_payload();
694
Carson Labradof52c03c2022-03-23 18:50:15 +0000695 // Reuse an existing connection if one is available
696 for (unsigned int i = 0; i < connections.size(); i++)
697 {
698 auto conn = connections[i];
699 if ((conn->state == ConnState::idle) ||
700 (conn->state == ConnState::initialized) ||
701 (conn->state == ConnState::closed))
702 {
Carson Labrado244256c2022-04-27 17:16:32 +0000703 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000704 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000705 setConnRetryPolicy(*conn, retryPolicy);
706 std::string commonMsg = std::to_string(i) + " from pool " +
707 destIP + ":" + std::to_string(destPort);
708
709 if (conn->state == ConnState::idle)
710 {
711 BMCWEB_LOG_DEBUG << "Grabbing idle connection "
712 << commonMsg;
713 conn->sendMessage();
714 }
715 else
716 {
717 BMCWEB_LOG_DEBUG << "Reusing existing connection "
718 << commonMsg;
719 conn->doResolve();
720 }
721 return;
722 }
723 }
724
725 // All connections in use so create a new connection or add request to
726 // the queue
727 if (connections.size() < maxPoolSize)
728 {
729 BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
730 << ":" << std::to_string(destPort);
731 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000732 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000733 conn->callback = std::move(cb);
734 setConnRetryPolicy(*conn, retryPolicy);
735 conn->doResolve();
736 }
737 else if (requestQueue.size() < maxRequestQueueSize)
738 {
739 BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
Carson Labrado244256c2022-04-27 17:16:32 +0000740 requestQueue.emplace_back(std::move(thisReq), std::move(cb),
Carson Labradof52c03c2022-03-23 18:50:15 +0000741 retryPolicy);
742 }
743 else
744 {
745 BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
746 << " request queue full. Dropping request.";
747 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530748 }
749
Carson Labradof52c03c2022-03-23 18:50:15 +0000750 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530751 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000752 unsigned int newId = static_cast<unsigned int>(connections.size());
753
AppaRao Pulie38778a2022-06-27 23:09:03 +0000754 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
755 ioc, id, destIP, destPort, useSSL, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000756
757 BMCWEB_LOG_DEBUG << "Added connection "
758 << std::to_string(connections.size() - 1)
759 << " to pool " << destIP << ":"
760 << std::to_string(destPort);
761
762 return ret;
763 }
764
765 public:
Ed Tanous8a592812022-06-04 09:06:59 -0700766 explicit ConnectionPool(boost::asio::io_context& iocIn,
767 const std::string& idIn,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000768 const std::string& destIPIn, uint16_t destPortIn,
769 bool useSSLIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700770 ioc(iocIn),
AppaRao Pulie38778a2022-06-27 23:09:03 +0000771 id(idIn), destIP(destIPIn), destPort(destPortIn), useSSL(useSSLIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000772 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000773 BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
774 << std::to_string(destPort);
775
776 // Initialize the pool with a single connection
777 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530778 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530779};
780
Carson Labradof52c03c2022-03-23 18:50:15 +0000781class HttpClient
782{
783 private:
784 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
785 connectionPools;
786 boost::asio::io_context& ioc =
787 crow::connections::systemBus->get_io_context();
788 std::unordered_map<std::string, RetryPolicyData> retryInfo;
789 HttpClient() = default;
790
Carson Labrado039a47e2022-04-05 16:03:20 +0000791 // Used as a dummy callback by sendData() in order to call
792 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700793 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000794 {
795 BMCWEB_LOG_DEBUG << "Response handled with return code: "
796 << std::to_string(res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700797 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000798
Carson Labradof52c03c2022-03-23 18:50:15 +0000799 public:
800 HttpClient(const HttpClient&) = delete;
801 HttpClient& operator=(const HttpClient&) = delete;
802 HttpClient(HttpClient&&) = delete;
803 HttpClient& operator=(HttpClient&&) = delete;
804 ~HttpClient() = default;
805
806 static HttpClient& getInstance()
807 {
808 static HttpClient handler;
809 return handler;
810 }
811
Carson Labrado039a47e2022-04-05 16:03:20 +0000812 // Send a request to destIP:destPort where additional processing of the
813 // result is not required
Carson Labradof52c03c2022-03-23 18:50:15 +0000814 void sendData(std::string& data, const std::string& id,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000815 const std::string& destIP, uint16_t destPort,
816 const std::string& destUri, bool useSSL,
Carson Labradof52c03c2022-03-23 18:50:15 +0000817 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000818 const boost::beast::http::verb verb,
819 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000820 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000821 const std::function<void(Response&)> cb = genericResHandler;
822 sendDataWithCallback(data, id, destIP, destPort, destUri, useSSL,
823 httpHeader, verb, retryPolicyName, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000824 }
825
826 // Send request to destIP:destPort and use the provided callback to
827 // handle the response
828 void sendDataWithCallback(std::string& data, const std::string& id,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000829 const std::string& destIP, uint16_t destPort,
830 const std::string& destUri, bool useSSL,
Carson Labrado039a47e2022-04-05 16:03:20 +0000831 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000832 const boost::beast::http::verb verb,
833 const std::string& retryPolicyName,
Ed Tanous6b3db602022-06-28 19:41:44 -0700834 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000835 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000836 std::string clientKey = useSSL ? "https" : "http";
837 clientKey += destIP;
838 clientKey += ":";
839 clientKey += std::to_string(destPort);
Carson Labradof52c03c2022-03-23 18:50:15 +0000840 // Use nullptr to avoid creating a ConnectionPool each time
AppaRao Pulie38778a2022-06-27 23:09:03 +0000841 std::shared_ptr<ConnectionPool>& conn = connectionPools[clientKey];
842 if (conn == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000843 {
844 // Now actually create the ConnectionPool shared_ptr since it does
845 // not already exist
AppaRao Pulie38778a2022-06-27 23:09:03 +0000846 conn = std::make_shared<ConnectionPool>(ioc, id, destIP, destPort,
847 useSSL);
Carson Labradof52c03c2022-03-23 18:50:15 +0000848 BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
849 }
850 else
851 {
852 BMCWEB_LOG_DEBUG << "Using existing connection pool for "
853 << clientKey;
854 }
855
856 // Get the associated retry policy
857 auto policy = retryInfo.try_emplace(retryPolicyName);
858 if (policy.second)
859 {
860 BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
861 << "\" with default values";
Carson Labradof52c03c2022-03-23 18:50:15 +0000862 }
863
864 // Send the data using either the existing connection pool or the newly
865 // created connection pool
AppaRao Pulie38778a2022-06-27 23:09:03 +0000866 conn->sendData(data, destUri, httpHeader, verb, policy.first->second,
867 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000868 }
869
Carson Labradoa7a80292022-06-01 16:01:52 +0000870 void setRetryConfig(
871 const uint32_t retryAttempts, const uint32_t retryTimeoutInterval,
872 const std::function<boost::system::error_code(unsigned int respCode)>&
873 invalidResp,
874 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000875 {
876 // We need to create the retry policy if one does not already exist for
877 // the given retryPolicyName
878 auto result = retryInfo.try_emplace(retryPolicyName);
879 if (result.second)
880 {
881 BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
882 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000883 }
884 else
885 {
886 BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
887 << retryPolicyName << "\"";
888 }
889
890 result.first->second.maxRetryAttempts = retryAttempts;
891 result.first->second.retryIntervalSecs =
892 std::chrono::seconds(retryTimeoutInterval);
Carson Labradoa7a80292022-06-01 16:01:52 +0000893 result.first->second.invalidResp = invalidResp;
Carson Labradof52c03c2022-03-23 18:50:15 +0000894 }
895
896 void setRetryPolicy(const std::string& retryPolicy,
897 const std::string& retryPolicyName)
898 {
899 // We need to create the retry policy if one does not already exist for
900 // the given retryPolicyName
901 auto result = retryInfo.try_emplace(retryPolicyName);
902 if (result.second)
903 {
904 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
905 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000906 }
907 else
908 {
909 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
910 << retryPolicyName << "\"";
911 }
912
913 result.first->second.retryPolicyAction = retryPolicy;
914 }
915};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530916} // namespace crow