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_body.hpp" 8 #include "http_response.hpp" 9 #include "http_utility.hpp" 10 #include "logging.hpp" 11 #include "mutual_tls.hpp" 12 #include "nghttp2_adapters.hpp" 13 #include "ssl_key_handler.hpp" 14 #include "utility.hpp" 15 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/http/error.hpp> 21 #include <boost/beast/http/parser.hpp> 22 #include <boost/beast/http/read.hpp> 23 #include <boost/beast/http/serializer.hpp> 24 #include <boost/beast/http/write.hpp> 25 #include <boost/beast/websocket.hpp> 26 #include <boost/system/error_code.hpp> 27 28 #include <array> 29 #include <atomic> 30 #include <chrono> 31 #include <functional> 32 #include <memory> 33 #include <string> 34 #include <vector> 35 36 namespace crow 37 { 38 39 struct Http2StreamData 40 { 41 std::shared_ptr<Request> req = std::make_shared<Request>(); 42 std::optional<bmcweb::HttpBody::reader> reqReader; 43 std::string accept; 44 Response res; 45 std::optional<bmcweb::HttpBody::writer> writer; 46 }; 47 48 template <typename Adaptor, typename Handler> 49 class HTTP2Connection : 50 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>> 51 { 52 using self_type = HTTP2Connection<Adaptor, Handler>; 53 54 public: 55 HTTP2Connection(Adaptor&& adaptorIn, Handler* handlerIn, 56 std::function<std::string()>& getCachedDateStrF) : 57 adaptor(std::move(adaptorIn)), ngSession(initializeNghttp2Session()), 58 handler(handlerIn), getCachedDateStr(getCachedDateStrF) 59 {} 60 61 void start() 62 { 63 // Create the control stream 64 streams[0]; 65 66 if (sendServerConnectionHeader() != 0) 67 { 68 BMCWEB_LOG_ERROR("send_server_connection_header failed"); 69 return; 70 } 71 doRead(); 72 } 73 74 int sendServerConnectionHeader() 75 { 76 BMCWEB_LOG_DEBUG("send_server_connection_header()"); 77 78 uint32_t maxStreams = 4; 79 std::array<nghttp2_settings_entry, 2> iv = { 80 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams}, 81 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}}; 82 int rv = ngSession.submitSettings(iv); 83 if (rv != 0) 84 { 85 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv)); 86 return -1; 87 } 88 writeBuffer(); 89 return 0; 90 } 91 92 static ssize_t 93 fileReadCallback(nghttp2_session* /* session */, int32_t streamId, 94 uint8_t* buf, size_t length, uint32_t* dataFlags, 95 nghttp2_data_source* /*source*/, void* userPtr) 96 { 97 self_type& self = userPtrToSelf(userPtr); 98 99 auto streamIt = self.streams.find(streamId); 100 if (streamIt == self.streams.end()) 101 { 102 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 103 } 104 Http2StreamData& stream = streamIt->second; 105 BMCWEB_LOG_DEBUG("File read callback length: {}", length); 106 if (!stream.writer) 107 { 108 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 109 } 110 boost::beast::error_code ec; 111 boost::optional<std::pair<boost::asio::const_buffer, bool>> out = 112 stream.writer->getWithMaxSize(ec, length); 113 if (ec) 114 { 115 BMCWEB_LOG_CRITICAL("Failed to get buffer"); 116 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 117 } 118 if (!out) 119 { 120 BMCWEB_LOG_ERROR("Empty file, setting EOF"); 121 *dataFlags |= NGHTTP2_DATA_FLAG_EOF; 122 return 0; 123 } 124 125 BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size()); 126 if (length < out->first.size()) 127 { 128 BMCWEB_LOG_CRITICAL( 129 "Buffer overflow that should never happen happened"); 130 // Should never happen because of length limit on get() above 131 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 132 } 133 boost::asio::mutable_buffer writeableBuf(buf, length); 134 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size()); 135 size_t copied = boost::asio::buffer_copy(writeableBuf, out->first); 136 if (copied != out->first.size()) 137 { 138 BMCWEB_LOG_ERROR( 139 "Couldn't copy all {} bytes into buffer, only copied {}", 140 out->first.size(), copied); 141 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 142 } 143 144 if (!out->second) 145 { 146 BMCWEB_LOG_DEBUG("Setting EOF flag"); 147 *dataFlags |= NGHTTP2_DATA_FLAG_EOF; 148 } 149 return static_cast<ssize_t>(copied); 150 } 151 152 nghttp2_nv headerFromStringViews(std::string_view name, 153 std::string_view value, uint8_t flags) 154 { 155 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data()); 156 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data()); 157 return {nameData, valueData, name.size(), value.size(), flags}; 158 } 159 160 int sendResponse(Response& completedRes, int32_t streamId) 161 { 162 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId); 163 164 auto it = streams.find(streamId); 165 if (it == streams.end()) 166 { 167 close(); 168 return -1; 169 } 170 Http2StreamData& stream = it->second; 171 Response& res = stream.res; 172 res = std::move(completedRes); 173 174 completeResponseFields(stream.accept, res); 175 res.addHeader(boost::beast::http::field::date, getCachedDateStr()); 176 res.preparePayload(); 177 178 boost::beast::http::fields& fields = res.fields(); 179 std::string code = std::to_string(res.resultInt()); 180 std::vector<nghttp2_nv> hdr; 181 hdr.emplace_back( 182 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE)); 183 for (const boost::beast::http::fields::value_type& header : fields) 184 { 185 hdr.emplace_back(headerFromStringViews( 186 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE)); 187 } 188 http::response<bmcweb::HttpBody>& fbody = res.response; 189 stream.writer.emplace(fbody.base(), fbody.body()); 190 191 nghttp2_data_provider dataPrd{ 192 .source = {.fd = 0}, 193 .read_callback = fileReadCallback, 194 }; 195 196 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd); 197 if (rv != 0) 198 { 199 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv)); 200 close(); 201 return -1; 202 } 203 writeBuffer(); 204 205 return 0; 206 } 207 208 nghttp2_session initializeNghttp2Session() 209 { 210 nghttp2_session_callbacks callbacks; 211 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic); 212 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic); 213 callbacks.setOnHeaderCallback(onHeaderCallbackStatic); 214 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic); 215 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic); 216 217 nghttp2_session session(callbacks); 218 session.setUserData(this); 219 220 return session; 221 } 222 223 int onRequestRecv(int32_t streamId) 224 { 225 BMCWEB_LOG_DEBUG("on_request_recv"); 226 227 auto it = streams.find(streamId); 228 if (it == streams.end()) 229 { 230 close(); 231 return -1; 232 } 233 auto& reqReader = it->second.reqReader; 234 if (reqReader) 235 { 236 boost::beast::error_code ec; 237 reqReader->finish(ec); 238 if (ec) 239 { 240 BMCWEB_LOG_CRITICAL("Failed to finalize payload"); 241 close(); 242 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 243 } 244 } 245 crow::Request& thisReq = *it->second.req; 246 thisReq.ioService = static_cast<decltype(thisReq.ioService)>( 247 &adaptor.get_executor().context()); 248 249 it->second.accept = thisReq.getHeaderValue("Accept"); 250 251 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq), 252 thisReq.url().encoded_path()); 253 254 crow::Response& thisRes = it->second.res; 255 256 thisRes.setCompleteRequestHandler( 257 [this, streamId](Response& completeRes) { 258 BMCWEB_LOG_DEBUG("res.completeRequestHandler called"); 259 if (sendResponse(completeRes, streamId) != 0) 260 { 261 close(); 262 return; 263 } 264 }); 265 auto asyncResp = 266 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res)); 267 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH) 268 { 269 thisReq.session = crow::authentication::authenticate( 270 {}, asyncResp->res, thisReq.method(), thisReq.req, nullptr); 271 if (!crow::authentication::isOnAllowlist(thisReq.url().path(), 272 thisReq.method()) && 273 thisReq.session == nullptr) 274 { 275 BMCWEB_LOG_WARNING("Authentication failed"); 276 forward_unauthorized::sendUnauthorized( 277 thisReq.url().encoded_path(), 278 thisReq.getHeaderValue("X-Requested-With"), 279 thisReq.getHeaderValue("Accept"), asyncResp->res); 280 return 0; 281 } 282 } 283 std::string_view expected = 284 thisReq.getHeaderValue(boost::beast::http::field::if_none_match); 285 BMCWEB_LOG_DEBUG("Setting expected hash {}", expected); 286 if (!expected.empty()) 287 { 288 asyncResp->res.setExpectedHash(expected); 289 } 290 handler->handle(it->second.req, asyncResp); 291 return 0; 292 } 293 294 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId, 295 const uint8_t* data, size_t len) 296 { 297 auto thisStream = streams.find(streamId); 298 if (thisStream == streams.end()) 299 { 300 BMCWEB_LOG_ERROR("Unknown stream{}", streamId); 301 close(); 302 return -1; 303 } 304 305 std::optional<bmcweb::HttpBody::reader>& reqReader = 306 thisStream->second.reqReader; 307 if (!reqReader) 308 { 309 reqReader.emplace( 310 bmcweb::HttpBody::reader(thisStream->second.req->req.base(), 311 thisStream->second.req->req.body())); 312 } 313 boost::beast::error_code ec; 314 reqReader->put(boost::asio::const_buffer(data, len), ec); 315 if (ec) 316 { 317 BMCWEB_LOG_CRITICAL("Failed to write payload"); 318 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 319 } 320 return 0; 321 } 322 323 static int onDataChunkRecvStatic( 324 nghttp2_session* /* session */, uint8_t flags, int32_t streamId, 325 const uint8_t* data, size_t len, void* userData) 326 { 327 BMCWEB_LOG_DEBUG("on_frame_recv_callback"); 328 if (userData == nullptr) 329 { 330 BMCWEB_LOG_CRITICAL("user data was null?"); 331 return NGHTTP2_ERR_CALLBACK_FAILURE; 332 } 333 return userPtrToSelf(userData).onDataChunkRecvCallback( 334 flags, streamId, data, len); 335 } 336 337 int onFrameRecvCallback(const nghttp2_frame& frame) 338 { 339 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type)); 340 switch (frame.hd.type) 341 { 342 case NGHTTP2_DATA: 343 case NGHTTP2_HEADERS: 344 // Check that the client request has finished 345 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) 346 { 347 return onRequestRecv(frame.hd.stream_id); 348 } 349 break; 350 default: 351 break; 352 } 353 return 0; 354 } 355 356 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */, 357 const nghttp2_frame* frame, 358 void* userData) 359 { 360 BMCWEB_LOG_DEBUG("on_frame_recv_callback"); 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 return userPtrToSelf(userData).onFrameRecvCallback(*frame); 372 } 373 374 static self_type& userPtrToSelf(void* userData) 375 { 376 // This method exists to keep the unsafe reinterpret cast in one 377 // place. 378 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 379 return *reinterpret_cast<self_type*>(userData); 380 } 381 382 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */, 383 int32_t streamId, 384 uint32_t /*unused*/, void* userData) 385 { 386 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId); 387 if (userData == nullptr) 388 { 389 BMCWEB_LOG_CRITICAL("user data was null?"); 390 return NGHTTP2_ERR_CALLBACK_FAILURE; 391 } 392 if (userPtrToSelf(userData).streams.erase(streamId) <= 0) 393 { 394 return -1; 395 } 396 return 0; 397 } 398 399 int onHeaderCallback(const nghttp2_frame& frame, 400 std::span<const uint8_t> name, 401 std::span<const uint8_t> value) 402 { 403 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 404 std::string_view nameSv(reinterpret_cast<const char*>(name.data()), 405 name.size()); 406 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 407 std::string_view valueSv(reinterpret_cast<const char*>(value.data()), 408 value.size()); 409 410 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv, 411 valueSv); 412 if (frame.hd.type != NGHTTP2_HEADERS) 413 { 414 return 0; 415 } 416 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST) 417 { 418 return 0; 419 } 420 auto thisStream = streams.find(frame.hd.stream_id); 421 if (thisStream == streams.end()) 422 { 423 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id); 424 close(); 425 return -1; 426 } 427 428 crow::Request& thisReq = *thisStream->second.req; 429 430 if (nameSv == ":path") 431 { 432 thisReq.target(valueSv); 433 } 434 else if (nameSv == ":method") 435 { 436 boost::beast::http::verb verb = 437 boost::beast::http::string_to_verb(valueSv); 438 if (verb == boost::beast::http::verb::unknown) 439 { 440 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv); 441 verb = boost::beast::http::verb::trace; 442 } 443 thisReq.method(verb); 444 } 445 else if (nameSv == ":scheme") 446 { 447 // Nothing to check on scheme 448 } 449 else 450 { 451 thisReq.addHeader(nameSv, valueSv); 452 } 453 return 0; 454 } 455 456 static int onHeaderCallbackStatic( 457 nghttp2_session* /* session */, const nghttp2_frame* frame, 458 const uint8_t* name, size_t namelen, const uint8_t* value, 459 size_t vallen, uint8_t /* flags */, void* userData) 460 { 461 if (userData == nullptr) 462 { 463 BMCWEB_LOG_CRITICAL("user data was null?"); 464 return NGHTTP2_ERR_CALLBACK_FAILURE; 465 } 466 if (frame == nullptr) 467 { 468 BMCWEB_LOG_CRITICAL("frame was null?"); 469 return NGHTTP2_ERR_CALLBACK_FAILURE; 470 } 471 if (name == nullptr) 472 { 473 BMCWEB_LOG_CRITICAL("name was null?"); 474 return NGHTTP2_ERR_CALLBACK_FAILURE; 475 } 476 if (value == nullptr) 477 { 478 BMCWEB_LOG_CRITICAL("value was null?"); 479 return NGHTTP2_ERR_CALLBACK_FAILURE; 480 } 481 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen}, 482 {value, vallen}); 483 } 484 485 int onBeginHeadersCallback(const nghttp2_frame& frame) 486 { 487 if (frame.hd.type == NGHTTP2_HEADERS && 488 frame.headers.cat == NGHTTP2_HCAT_REQUEST) 489 { 490 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id); 491 492 streams.emplace(frame.hd.stream_id, Http2StreamData()); 493 } 494 return 0; 495 } 496 497 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */, 498 const nghttp2_frame* frame, 499 void* userData) 500 { 501 BMCWEB_LOG_DEBUG("on_begin_headers_callback"); 502 if (userData == nullptr) 503 { 504 BMCWEB_LOG_CRITICAL("user data was null?"); 505 return NGHTTP2_ERR_CALLBACK_FAILURE; 506 } 507 if (frame == nullptr) 508 { 509 BMCWEB_LOG_CRITICAL("frame was null?"); 510 return NGHTTP2_ERR_CALLBACK_FAILURE; 511 } 512 return userPtrToSelf(userData).onBeginHeadersCallback(*frame); 513 } 514 515 static void afterWriteBuffer(const std::shared_ptr<self_type>& self, 516 const boost::system::error_code& ec, 517 size_t sendLength) 518 { 519 self->isWriting = false; 520 BMCWEB_LOG_DEBUG("Sent {}", sendLength); 521 if (ec) 522 { 523 self->close(); 524 return; 525 } 526 self->writeBuffer(); 527 } 528 529 void writeBuffer() 530 { 531 if (isWriting) 532 { 533 return; 534 } 535 std::span<const uint8_t> data = ngSession.memSend(); 536 if (data.empty()) 537 { 538 return; 539 } 540 isWriting = true; 541 boost::asio::async_write( 542 adaptor, boost::asio::const_buffer(data.data(), data.size()), 543 std::bind_front(afterWriteBuffer, shared_from_this())); 544 } 545 546 void close() 547 { 548 if constexpr (std::is_same_v<Adaptor, 549 boost::asio::ssl::stream< 550 boost::asio::ip::tcp::socket>>) 551 { 552 adaptor.next_layer().close(); 553 } 554 else 555 { 556 adaptor.close(); 557 } 558 } 559 560 void afterDoRead(const std::shared_ptr<self_type>& /*self*/, 561 const boost::system::error_code& ec, 562 size_t bytesTransferred) 563 { 564 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this), 565 bytesTransferred); 566 567 if (ec) 568 { 569 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this), 570 ec.message()); 571 close(); 572 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this)); 573 return; 574 } 575 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred}; 576 577 ssize_t readLen = ngSession.memRecv(bufferSpan); 578 if (readLen < 0) 579 { 580 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen); 581 close(); 582 return; 583 } 584 writeBuffer(); 585 586 doRead(); 587 } 588 589 void doRead() 590 { 591 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this)); 592 adaptor.async_read_some( 593 boost::asio::buffer(inBuffer), 594 std::bind_front(&self_type::afterDoRead, this, shared_from_this())); 595 } 596 597 // A mapping from http2 stream ID to Stream Data 598 std::map<int32_t, Http2StreamData> streams; 599 600 std::array<uint8_t, 8192> inBuffer{}; 601 602 Adaptor adaptor; 603 bool isWriting = false; 604 605 nghttp2_session ngSession; 606 607 Handler* handler; 608 std::function<std::string()>& getCachedDateStr; 609 610 using std::enable_shared_from_this< 611 HTTP2Connection<Adaptor, Handler>>::shared_from_this; 612 613 using std::enable_shared_from_this< 614 HTTP2Connection<Adaptor, Handler>>::weak_from_this; 615 }; 616 } // namespace crow 617