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