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