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