1 #pragma once 2 #include "bmcweb_config.h" 3 4 #include "async_resp.hpp" 5 #include "authentication.hpp" 6 #include "complete_response_fields.hpp" 7 #include "http_response.hpp" 8 #include "http_utility.hpp" 9 #include "logging.hpp" 10 #include "mutual_tls.hpp" 11 #include "nghttp2_adapters.hpp" 12 #include "ssl_key_handler.hpp" 13 #include "utility.hpp" 14 15 #include <boost/algorithm/string/predicate.hpp> 16 #include <boost/asio/io_context.hpp> 17 #include <boost/asio/ip/tcp.hpp> 18 #include <boost/asio/ssl/stream.hpp> 19 #include <boost/asio/steady_timer.hpp> 20 #include <boost/beast/core/multi_buffer.hpp> 21 #include <boost/beast/http/error.hpp> 22 #include <boost/beast/http/parser.hpp> 23 #include <boost/beast/http/read.hpp> 24 #include <boost/beast/http/serializer.hpp> 25 #include <boost/beast/http/string_body.hpp> 26 #include <boost/beast/http/write.hpp> 27 #include <boost/beast/ssl/ssl_stream.hpp> 28 #include <boost/beast/websocket.hpp> 29 30 #include <atomic> 31 #include <chrono> 32 #include <vector> 33 34 namespace crow 35 { 36 37 struct Http2StreamData 38 { 39 crow::Request req{}; 40 crow::Response res{}; 41 size_t sentSofar = 0; 42 }; 43 44 template <typename Adaptor, typename Handler> 45 class HTTP2Connection : 46 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>> 47 { 48 using self_type = HTTP2Connection<Adaptor, Handler>; 49 50 public: 51 HTTP2Connection(Adaptor&& adaptorIn, Handler* handlerIn, 52 std::function<std::string()>& getCachedDateStrF 53 54 ) : 55 adaptor(std::move(adaptorIn)), 56 57 ngSession(initializeNghttp2Session()), 58 59 handler(handlerIn), getCachedDateStr(getCachedDateStrF) 60 {} 61 62 void start() 63 { 64 // Create the control stream 65 streams[0]; 66 67 if (sendServerConnectionHeader() != 0) 68 { 69 BMCWEB_LOG_ERROR("send_server_connection_header failed"); 70 return; 71 } 72 doRead(); 73 } 74 75 int sendServerConnectionHeader() 76 { 77 BMCWEB_LOG_DEBUG("send_server_connection_header()"); 78 79 uint32_t maxStreams = 4; 80 std::array<nghttp2_settings_entry, 2> iv = { 81 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams}, 82 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}}; 83 int rv = ngSession.submitSettings(iv); 84 if (rv != 0) 85 { 86 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv)); 87 return -1; 88 } 89 return 0; 90 } 91 92 static ssize_t fileReadCallback(nghttp2_session* /* session */, 93 int32_t streamId, uint8_t* buf, 94 size_t length, uint32_t* dataFlags, 95 nghttp2_data_source* /*source*/, 96 void* userPtr) 97 { 98 self_type& self = userPtrToSelf(userPtr); 99 100 auto streamIt = self.streams.find(streamId); 101 if (streamIt == self.streams.end()) 102 { 103 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 104 } 105 Http2StreamData& stream = streamIt->second; 106 BMCWEB_LOG_DEBUG("File read callback length: {}", length); 107 crow::Response& res = stream.res; 108 109 BMCWEB_LOG_DEBUG("total: {} send_sofar: {}", res.body().size(), 110 stream.sentSofar); 111 112 size_t toSend = std::min(res.body().size() - stream.sentSofar, length); 113 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", toSend); 114 115 std::string::iterator bodyBegin = res.body().begin(); 116 std::advance(bodyBegin, stream.sentSofar); 117 118 memcpy(buf, &*bodyBegin, toSend); 119 stream.sentSofar += toSend; 120 121 if (stream.sentSofar >= res.body().size()) 122 { 123 BMCWEB_LOG_DEBUG("Setting OEF flag"); 124 *dataFlags |= NGHTTP2_DATA_FLAG_EOF; 125 //*dataFlags |= NGHTTP2_DATA_FLAG_NO_COPY; 126 } 127 return static_cast<ssize_t>(toSend); 128 } 129 130 nghttp2_nv headerFromStringViews(std::string_view name, 131 std::string_view value) 132 { 133 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data()); 134 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data()); 135 return {nameData, valueData, name.size(), value.size(), 136 NGHTTP2_NV_FLAG_NONE}; 137 } 138 139 int sendResponse(Response& completedRes, int32_t streamId) 140 { 141 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId); 142 143 auto it = streams.find(streamId); 144 if (it == streams.end()) 145 { 146 close(); 147 return -1; 148 } 149 Response& thisRes = it->second.res; 150 thisRes = std::move(completedRes); 151 crow::Request& thisReq = it->second.req; 152 std::vector<nghttp2_nv> hdr; 153 154 completeResponseFields(thisReq, thisRes); 155 thisRes.addHeader(boost::beast::http::field::date, getCachedDateStr()); 156 157 boost::beast::http::fields& fields = thisRes.stringResponse.base(); 158 std::string code = std::to_string(thisRes.stringResponse.result_int()); 159 hdr.emplace_back(headerFromStringViews(":status", code)); 160 for (const boost::beast::http::fields::value_type& header : fields) 161 { 162 hdr.emplace_back( 163 headerFromStringViews(header.name_string(), header.value())); 164 } 165 Http2StreamData& stream = it->second; 166 stream.sentSofar = 0; 167 168 nghttp2_data_provider dataPrd{ 169 .source = {.fd = 0}, 170 .read_callback = fileReadCallback, 171 }; 172 173 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd); 174 if (rv != 0) 175 { 176 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv)); 177 close(); 178 return -1; 179 } 180 ngSession.send(); 181 182 return 0; 183 } 184 185 nghttp2_session initializeNghttp2Session() 186 { 187 nghttp2_session_callbacks callbacks; 188 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic); 189 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic); 190 callbacks.setOnHeaderCallback(onHeaderCallbackStatic); 191 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic); 192 callbacks.setSendCallback(onSendCallbackStatic); 193 194 nghttp2_session session(callbacks); 195 session.setUserData(this); 196 197 return session; 198 } 199 200 int onRequestRecv(int32_t streamId) 201 { 202 BMCWEB_LOG_DEBUG("on_request_recv"); 203 204 auto it = streams.find(streamId); 205 if (it == streams.end()) 206 { 207 close(); 208 return -1; 209 } 210 211 crow::Request& thisReq = it->second.req; 212 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq), 213 thisReq.url().encoded_path()); 214 215 crow::Response& thisRes = it->second.res; 216 217 thisRes.setCompleteRequestHandler( 218 [this, streamId](Response& completeRes) { 219 BMCWEB_LOG_DEBUG("res.completeRequestHandler called"); 220 if (sendResponse(completeRes, streamId) != 0) 221 { 222 close(); 223 return; 224 } 225 }); 226 auto asyncResp = 227 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res)); 228 handler->handle(thisReq, asyncResp); 229 230 return 0; 231 } 232 233 int onFrameRecvCallback(const nghttp2_frame& frame) 234 { 235 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type)); 236 switch (frame.hd.type) 237 { 238 case NGHTTP2_DATA: 239 case NGHTTP2_HEADERS: 240 // Check that the client request has finished 241 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) 242 { 243 return onRequestRecv(frame.hd.stream_id); 244 } 245 break; 246 default: 247 break; 248 } 249 return 0; 250 } 251 252 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */, 253 const nghttp2_frame* frame, 254 void* userData) 255 { 256 BMCWEB_LOG_DEBUG("on_frame_recv_callback"); 257 if (userData == nullptr) 258 { 259 BMCWEB_LOG_CRITICAL("user data was null?"); 260 return NGHTTP2_ERR_CALLBACK_FAILURE; 261 } 262 if (frame == nullptr) 263 { 264 BMCWEB_LOG_CRITICAL("frame was null?"); 265 return NGHTTP2_ERR_CALLBACK_FAILURE; 266 } 267 return userPtrToSelf(userData).onFrameRecvCallback(*frame); 268 } 269 270 static self_type& userPtrToSelf(void* userData) 271 { 272 // This method exists to keep the unsafe reinterpret cast in one 273 // place. 274 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 275 return *reinterpret_cast<self_type*>(userData); 276 } 277 278 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */, 279 int32_t streamId, 280 uint32_t /*unused*/, void* userData) 281 { 282 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId); 283 if (userData == nullptr) 284 { 285 BMCWEB_LOG_CRITICAL("user data was null?"); 286 return NGHTTP2_ERR_CALLBACK_FAILURE; 287 } 288 if (userPtrToSelf(userData).streams.erase(streamId) <= 0) 289 { 290 return -1; 291 } 292 return 0; 293 } 294 295 int onHeaderCallback(const nghttp2_frame& frame, 296 std::span<const uint8_t> name, 297 std::span<const uint8_t> value) 298 { 299 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 300 std::string_view nameSv(reinterpret_cast<const char*>(name.data()), 301 name.size()); 302 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 303 std::string_view valueSv(reinterpret_cast<const char*>(value.data()), 304 value.size()); 305 306 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv, 307 valueSv); 308 309 switch (frame.hd.type) 310 { 311 case NGHTTP2_HEADERS: 312 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST) 313 { 314 break; 315 } 316 auto thisStream = streams.find(frame.hd.stream_id); 317 if (thisStream == streams.end()) 318 { 319 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id); 320 close(); 321 return -1; 322 } 323 324 crow::Request& thisReq = thisStream->second.req; 325 326 if (nameSv == ":path") 327 { 328 thisReq.target(valueSv); 329 } 330 else if (nameSv == ":method") 331 { 332 boost::beast::http::verb verb = 333 boost::beast::http::string_to_verb(valueSv); 334 if (verb == boost::beast::http::verb::unknown) 335 { 336 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv); 337 close(); 338 return -1; 339 } 340 thisReq.req.method(verb); 341 } 342 else if (nameSv == ":scheme") 343 { 344 // Nothing to check on scheme 345 } 346 else 347 { 348 thisReq.req.set(nameSv, valueSv); 349 } 350 break; 351 } 352 return 0; 353 } 354 355 static int onHeaderCallbackStatic(nghttp2_session* /* session */, 356 const nghttp2_frame* frame, 357 const uint8_t* name, size_t namelen, 358 const uint8_t* value, size_t vallen, 359 uint8_t /* flags */, void* userData) 360 { 361 if (userData == nullptr) 362 { 363 BMCWEB_LOG_CRITICAL("user data was null?"); 364 return NGHTTP2_ERR_CALLBACK_FAILURE; 365 } 366 if (frame == nullptr) 367 { 368 BMCWEB_LOG_CRITICAL("frame was null?"); 369 return NGHTTP2_ERR_CALLBACK_FAILURE; 370 } 371 if (name == nullptr) 372 { 373 BMCWEB_LOG_CRITICAL("name was null?"); 374 return NGHTTP2_ERR_CALLBACK_FAILURE; 375 } 376 if (value == nullptr) 377 { 378 BMCWEB_LOG_CRITICAL("value was null?"); 379 return NGHTTP2_ERR_CALLBACK_FAILURE; 380 } 381 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen}, 382 {value, vallen}); 383 } 384 385 int onBeginHeadersCallback(const nghttp2_frame& frame) 386 { 387 if (frame.hd.type == NGHTTP2_HEADERS && 388 frame.headers.cat == NGHTTP2_HCAT_REQUEST) 389 { 390 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id); 391 392 Http2StreamData& stream = streams[frame.hd.stream_id]; 393 // http2 is by definition always tls 394 stream.req.isSecure = true; 395 } 396 return 0; 397 } 398 399 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */, 400 const nghttp2_frame* frame, 401 void* userData) 402 { 403 BMCWEB_LOG_DEBUG("on_begin_headers_callback"); 404 if (userData == nullptr) 405 { 406 BMCWEB_LOG_CRITICAL("user data was null?"); 407 return NGHTTP2_ERR_CALLBACK_FAILURE; 408 } 409 if (frame == nullptr) 410 { 411 BMCWEB_LOG_CRITICAL("frame was null?"); 412 return NGHTTP2_ERR_CALLBACK_FAILURE; 413 } 414 return userPtrToSelf(userData).onBeginHeadersCallback(*frame); 415 } 416 417 static void afterWriteBuffer(const std::shared_ptr<self_type>& self, 418 const boost::system::error_code& ec, 419 size_t sendLength) 420 { 421 self->isWriting = false; 422 BMCWEB_LOG_DEBUG("Sent {}", sendLength); 423 if (ec) 424 { 425 self->close(); 426 return; 427 } 428 self->sendBuffer.consume(sendLength); 429 self->writeBuffer(); 430 } 431 432 void writeBuffer() 433 { 434 if (isWriting) 435 { 436 return; 437 } 438 if (sendBuffer.size() <= 0) 439 { 440 return; 441 } 442 isWriting = true; 443 adaptor.async_write_some( 444 sendBuffer.data(), 445 std::bind_front(afterWriteBuffer, shared_from_this())); 446 } 447 448 ssize_t onSendCallback(nghttp2_session* /*session */, const uint8_t* data, 449 size_t length, int /* flags */) 450 { 451 BMCWEB_LOG_DEBUG("On send callback size={}", length); 452 size_t copied = boost::asio::buffer_copy( 453 sendBuffer.prepare(length), boost::asio::buffer(data, length)); 454 sendBuffer.commit(copied); 455 writeBuffer(); 456 return static_cast<ssize_t>(length); 457 } 458 459 static ssize_t onSendCallbackStatic(nghttp2_session* session, 460 const uint8_t* data, size_t length, 461 int flags /* flags */, void* userData) 462 { 463 return userPtrToSelf(userData).onSendCallback(session, data, length, 464 flags); 465 } 466 467 void close() 468 { 469 if constexpr (std::is_same_v<Adaptor, 470 boost::beast::ssl_stream< 471 boost::asio::ip::tcp::socket>>) 472 { 473 adaptor.next_layer().close(); 474 } 475 else 476 { 477 adaptor.close(); 478 } 479 } 480 481 void doRead() 482 { 483 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this)); 484 adaptor.async_read_some( 485 inBuffer.prepare(8192), 486 [this, self(shared_from_this())]( 487 const boost::system::error_code& ec, size_t bytesTransferred) { 488 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this), 489 bytesTransferred); 490 491 if (ec) 492 { 493 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this), 494 ec.message()); 495 close(); 496 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this)); 497 return; 498 } 499 inBuffer.commit(bytesTransferred); 500 501 size_t consumed = 0; 502 for (const auto bufferIt : inBuffer.data()) 503 { 504 std::span<const uint8_t> bufferSpan{ 505 std::bit_cast<const uint8_t*>(bufferIt.data()), 506 bufferIt.size()}; 507 BMCWEB_LOG_DEBUG("http2 is getting {} bytes", 508 bufferSpan.size()); 509 ssize_t readLen = ngSession.memRecv(bufferSpan); 510 if (readLen <= 0) 511 { 512 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", 513 readLen); 514 close(); 515 return; 516 } 517 consumed += static_cast<size_t>(readLen); 518 } 519 inBuffer.consume(consumed); 520 521 doRead(); 522 }); 523 } 524 525 // A mapping from http2 stream ID to Stream Data 526 boost::container::flat_map<int32_t, Http2StreamData> streams; 527 528 boost::beast::multi_buffer sendBuffer; 529 boost::beast::multi_buffer inBuffer; 530 531 Adaptor adaptor; 532 bool isWriting = false; 533 534 nghttp2_session ngSession; 535 536 Handler* handler; 537 std::function<std::string()>& getCachedDateStr; 538 539 using std::enable_shared_from_this< 540 HTTP2Connection<Adaptor, Handler>>::shared_from_this; 541 542 using std::enable_shared_from_this< 543 HTTP2Connection<Adaptor, Handler>>::weak_from_this; 544 }; 545 } // namespace crow 546