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