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