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 Response::string_response* body = 110 boost::variant2::get_if<Response::string_response>(&res.response); 111 Response::file_response* fbody = 112 boost::variant2::get_if<Response::file_response>(&res.response); 113 114 size_t size = res.size(); 115 BMCWEB_LOG_DEBUG("total: {} send_sofar: {}", size, stream.sentSofar); 116 117 size_t toSend = std::min(size - stream.sentSofar, length); 118 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", toSend); 119 120 if (body != nullptr) 121 { 122 std::string::const_iterator bodyBegin = body->body().begin(); 123 std::advance(bodyBegin, stream.sentSofar); 124 125 memcpy(buf, &*bodyBegin, toSend); 126 } 127 else if (fbody != nullptr) 128 { 129 boost::system::error_code ec; 130 131 size_t nread = fbody->body().file().read(buf, toSend, ec); 132 if (ec || nread != toSend) 133 { 134 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 135 } 136 } 137 else 138 { 139 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 140 } 141 142 stream.sentSofar += toSend; 143 144 if (stream.sentSofar >= size) 145 { 146 BMCWEB_LOG_DEBUG("Setting OEF flag"); 147 *dataFlags |= NGHTTP2_DATA_FLAG_EOF; 148 //*dataFlags |= NGHTTP2_DATA_FLAG_NO_COPY; 149 } 150 return static_cast<ssize_t>(toSend); 151 } 152 153 nghttp2_nv headerFromStringViews(std::string_view name, 154 std::string_view value) 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(), 159 NGHTTP2_NV_FLAG_NONE}; 160 } 161 162 int sendResponse(Response& completedRes, int32_t streamId) 163 { 164 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId); 165 166 auto it = streams.find(streamId); 167 if (it == streams.end()) 168 { 169 close(); 170 return -1; 171 } 172 Response& thisRes = it->second.res; 173 thisRes = std::move(completedRes); 174 crow::Request& thisReq = it->second.req; 175 std::vector<nghttp2_nv> hdr; 176 177 completeResponseFields(thisReq, thisRes); 178 thisRes.addHeader(boost::beast::http::field::date, getCachedDateStr()); 179 180 boost::beast::http::fields& fields = thisRes.fields(); 181 std::string code = std::to_string(thisRes.resultInt()); 182 hdr.emplace_back(headerFromStringViews(":status", code)); 183 for (const boost::beast::http::fields::value_type& header : fields) 184 { 185 hdr.emplace_back( 186 headerFromStringViews(header.name_string(), header.value())); 187 } 188 Http2StreamData& stream = it->second; 189 stream.sentSofar = 0; 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 ngSession.send(); 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.setSendCallback(onSendCallbackStatic); 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 234 crow::Request& thisReq = it->second.req; 235 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq), 236 thisReq.url().encoded_path()); 237 238 crow::Response& thisRes = it->second.res; 239 240 thisRes.setCompleteRequestHandler( 241 [this, streamId](Response& completeRes) { 242 BMCWEB_LOG_DEBUG("res.completeRequestHandler called"); 243 if (sendResponse(completeRes, streamId) != 0) 244 { 245 close(); 246 return; 247 } 248 }); 249 auto asyncResp = 250 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res)); 251 handler->handle(thisReq, asyncResp); 252 253 return 0; 254 } 255 256 int onFrameRecvCallback(const nghttp2_frame& frame) 257 { 258 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type)); 259 switch (frame.hd.type) 260 { 261 case NGHTTP2_DATA: 262 case NGHTTP2_HEADERS: 263 // Check that the client request has finished 264 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) 265 { 266 return onRequestRecv(frame.hd.stream_id); 267 } 268 break; 269 default: 270 break; 271 } 272 return 0; 273 } 274 275 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */, 276 const nghttp2_frame* frame, 277 void* userData) 278 { 279 BMCWEB_LOG_DEBUG("on_frame_recv_callback"); 280 if (userData == nullptr) 281 { 282 BMCWEB_LOG_CRITICAL("user data was null?"); 283 return NGHTTP2_ERR_CALLBACK_FAILURE; 284 } 285 if (frame == nullptr) 286 { 287 BMCWEB_LOG_CRITICAL("frame was null?"); 288 return NGHTTP2_ERR_CALLBACK_FAILURE; 289 } 290 return userPtrToSelf(userData).onFrameRecvCallback(*frame); 291 } 292 293 static self_type& userPtrToSelf(void* userData) 294 { 295 // This method exists to keep the unsafe reinterpret cast in one 296 // place. 297 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 298 return *reinterpret_cast<self_type*>(userData); 299 } 300 301 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */, 302 int32_t streamId, 303 uint32_t /*unused*/, void* userData) 304 { 305 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId); 306 if (userData == nullptr) 307 { 308 BMCWEB_LOG_CRITICAL("user data was null?"); 309 return NGHTTP2_ERR_CALLBACK_FAILURE; 310 } 311 if (userPtrToSelf(userData).streams.erase(streamId) <= 0) 312 { 313 return -1; 314 } 315 return 0; 316 } 317 318 int onHeaderCallback(const nghttp2_frame& frame, 319 std::span<const uint8_t> name, 320 std::span<const uint8_t> value) 321 { 322 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 323 std::string_view nameSv(reinterpret_cast<const char*>(name.data()), 324 name.size()); 325 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 326 std::string_view valueSv(reinterpret_cast<const char*>(value.data()), 327 value.size()); 328 329 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv, 330 valueSv); 331 332 switch (frame.hd.type) 333 { 334 case NGHTTP2_HEADERS: 335 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST) 336 { 337 break; 338 } 339 auto thisStream = streams.find(frame.hd.stream_id); 340 if (thisStream == streams.end()) 341 { 342 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id); 343 close(); 344 return -1; 345 } 346 347 crow::Request& thisReq = thisStream->second.req; 348 349 if (nameSv == ":path") 350 { 351 thisReq.target(valueSv); 352 } 353 else if (nameSv == ":method") 354 { 355 boost::beast::http::verb verb = 356 boost::beast::http::string_to_verb(valueSv); 357 if (verb == boost::beast::http::verb::unknown) 358 { 359 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv); 360 close(); 361 return -1; 362 } 363 thisReq.req.method(verb); 364 } 365 else if (nameSv == ":scheme") 366 { 367 // Nothing to check on scheme 368 } 369 else 370 { 371 thisReq.req.set(nameSv, valueSv); 372 } 373 break; 374 } 375 return 0; 376 } 377 378 static int onHeaderCallbackStatic(nghttp2_session* /* session */, 379 const nghttp2_frame* frame, 380 const uint8_t* name, size_t namelen, 381 const uint8_t* value, size_t vallen, 382 uint8_t /* flags */, void* userData) 383 { 384 if (userData == nullptr) 385 { 386 BMCWEB_LOG_CRITICAL("user data was null?"); 387 return NGHTTP2_ERR_CALLBACK_FAILURE; 388 } 389 if (frame == nullptr) 390 { 391 BMCWEB_LOG_CRITICAL("frame was null?"); 392 return NGHTTP2_ERR_CALLBACK_FAILURE; 393 } 394 if (name == nullptr) 395 { 396 BMCWEB_LOG_CRITICAL("name was null?"); 397 return NGHTTP2_ERR_CALLBACK_FAILURE; 398 } 399 if (value == nullptr) 400 { 401 BMCWEB_LOG_CRITICAL("value was null?"); 402 return NGHTTP2_ERR_CALLBACK_FAILURE; 403 } 404 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen}, 405 {value, vallen}); 406 } 407 408 int onBeginHeadersCallback(const nghttp2_frame& frame) 409 { 410 if (frame.hd.type == NGHTTP2_HEADERS && 411 frame.headers.cat == NGHTTP2_HCAT_REQUEST) 412 { 413 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id); 414 415 Http2StreamData& stream = streams[frame.hd.stream_id]; 416 // http2 is by definition always tls 417 stream.req.isSecure = true; 418 } 419 return 0; 420 } 421 422 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */, 423 const nghttp2_frame* frame, 424 void* userData) 425 { 426 BMCWEB_LOG_DEBUG("on_begin_headers_callback"); 427 if (userData == nullptr) 428 { 429 BMCWEB_LOG_CRITICAL("user data was null?"); 430 return NGHTTP2_ERR_CALLBACK_FAILURE; 431 } 432 if (frame == nullptr) 433 { 434 BMCWEB_LOG_CRITICAL("frame was null?"); 435 return NGHTTP2_ERR_CALLBACK_FAILURE; 436 } 437 return userPtrToSelf(userData).onBeginHeadersCallback(*frame); 438 } 439 440 static void afterWriteBuffer(const std::shared_ptr<self_type>& self, 441 const boost::system::error_code& ec, 442 size_t sendLength) 443 { 444 self->isWriting = false; 445 BMCWEB_LOG_DEBUG("Sent {}", sendLength); 446 if (ec) 447 { 448 self->close(); 449 return; 450 } 451 self->sendBuffer.consume(sendLength); 452 self->writeBuffer(); 453 } 454 455 void writeBuffer() 456 { 457 if (isWriting) 458 { 459 return; 460 } 461 if (sendBuffer.size() <= 0) 462 { 463 return; 464 } 465 isWriting = true; 466 adaptor.async_write_some( 467 sendBuffer.data(), 468 std::bind_front(afterWriteBuffer, shared_from_this())); 469 } 470 471 ssize_t onSendCallback(nghttp2_session* /*session */, const uint8_t* data, 472 size_t length, int /* flags */) 473 { 474 BMCWEB_LOG_DEBUG("On send callback size={}", length); 475 size_t copied = boost::asio::buffer_copy( 476 sendBuffer.prepare(length), boost::asio::buffer(data, length)); 477 sendBuffer.commit(copied); 478 writeBuffer(); 479 return static_cast<ssize_t>(length); 480 } 481 482 static ssize_t onSendCallbackStatic(nghttp2_session* session, 483 const uint8_t* data, size_t length, 484 int flags /* flags */, void* userData) 485 { 486 return userPtrToSelf(userData).onSendCallback(session, data, length, 487 flags); 488 } 489 490 void close() 491 { 492 if constexpr (std::is_same_v<Adaptor, 493 boost::beast::ssl_stream< 494 boost::asio::ip::tcp::socket>>) 495 { 496 adaptor.next_layer().close(); 497 } 498 else 499 { 500 adaptor.close(); 501 } 502 } 503 504 void doRead() 505 { 506 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this)); 507 adaptor.async_read_some( 508 inBuffer.prepare(8192), 509 [this, self(shared_from_this())]( 510 const boost::system::error_code& ec, size_t bytesTransferred) { 511 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this), 512 bytesTransferred); 513 514 if (ec) 515 { 516 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this), 517 ec.message()); 518 close(); 519 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this)); 520 return; 521 } 522 inBuffer.commit(bytesTransferred); 523 524 size_t consumed = 0; 525 for (const auto bufferIt : inBuffer.data()) 526 { 527 std::span<const uint8_t> bufferSpan{ 528 std::bit_cast<const uint8_t*>(bufferIt.data()), 529 bufferIt.size()}; 530 BMCWEB_LOG_DEBUG("http2 is getting {} bytes", 531 bufferSpan.size()); 532 ssize_t readLen = ngSession.memRecv(bufferSpan); 533 if (readLen <= 0) 534 { 535 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", 536 readLen); 537 close(); 538 return; 539 } 540 consumed += static_cast<size_t>(readLen); 541 } 542 inBuffer.consume(consumed); 543 544 doRead(); 545 }); 546 } 547 548 // A mapping from http2 stream ID to Stream Data 549 boost::container::flat_map<int32_t, Http2StreamData> streams; 550 551 boost::beast::multi_buffer sendBuffer; 552 boost::beast::multi_buffer inBuffer; 553 554 Adaptor adaptor; 555 bool isWriting = false; 556 557 nghttp2_session ngSession; 558 559 Handler* handler; 560 std::function<std::string()>& getCachedDateStr; 561 562 using std::enable_shared_from_this< 563 HTTP2Connection<Adaptor, Handler>>::shared_from_this; 564 565 using std::enable_shared_from_this< 566 HTTP2Connection<Adaptor, Handler>>::weak_from_this; 567 }; 568 } // namespace crow 569