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