1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 #include "bmcweb_config.h" 5 6 #include "async_resp.hpp" 7 #include "authentication.hpp" 8 #include "complete_response_fields.hpp" 9 #include "forward_unauthorized.hpp" 10 #include "http_body.hpp" 11 #include "http_request.hpp" 12 #include "http_response.hpp" 13 #include "logging.hpp" 14 15 // NOLINTNEXTLINE(misc-include-cleaner) 16 #include "nghttp2_adapters.hpp" 17 18 #include <nghttp2/nghttp2.h> 19 #include <unistd.h> 20 21 #include <boost/asio/buffer.hpp> 22 #include <boost/asio/ip/tcp.hpp> 23 #include <boost/asio/ssl/stream.hpp> 24 #include <boost/beast/core/error.hpp> 25 #include <boost/beast/http/field.hpp> 26 #include <boost/beast/http/fields.hpp> 27 #include <boost/beast/http/message.hpp> 28 #include <boost/beast/http/verb.hpp> 29 #include <boost/optional/optional.hpp> 30 #include <boost/system/error_code.hpp> 31 32 #include <array> 33 #include <bit> 34 #include <cstddef> 35 #include <cstdint> 36 #include <functional> 37 #include <map> 38 #include <memory> 39 #include <optional> 40 #include <span> 41 #include <string> 42 #include <string_view> 43 #include <utility> 44 #include <vector> 45 46 namespace crow 47 { 48 49 struct Http2StreamData 50 { 51 std::shared_ptr<Request> req = std::make_shared<Request>(); 52 std::optional<bmcweb::HttpBody::reader> reqReader; 53 std::string accept; 54 Response res; 55 std::optional<bmcweb::HttpBody::writer> writer; 56 }; 57 58 template <typename Adaptor, typename Handler> 59 class HTTP2Connection : 60 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>> 61 { 62 using self_type = HTTP2Connection<Adaptor, Handler>; 63 64 public: HTTP2Connection(Adaptor && adaptorIn,Handler * handlerIn,std::function<std::string ()> & getCachedDateStrF)65 HTTP2Connection(Adaptor&& adaptorIn, Handler* handlerIn, 66 std::function<std::string()>& getCachedDateStrF) : 67 adaptor(std::move(adaptorIn)), ngSession(initializeNghttp2Session()), 68 handler(handlerIn), getCachedDateStr(getCachedDateStrF) 69 {} 70 start()71 void start() 72 { 73 // Create the control stream 74 streams[0]; 75 76 if (sendServerConnectionHeader() != 0) 77 { 78 BMCWEB_LOG_ERROR("send_server_connection_header failed"); 79 return; 80 } 81 doRead(); 82 } 83 sendServerConnectionHeader()84 int sendServerConnectionHeader() 85 { 86 BMCWEB_LOG_DEBUG("send_server_connection_header()"); 87 88 uint32_t maxStreams = 4; 89 std::array<nghttp2_settings_entry, 2> iv = { 90 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams}, 91 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}}; 92 int rv = ngSession.submitSettings(iv); 93 if (rv != 0) 94 { 95 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv)); 96 return -1; 97 } 98 writeBuffer(); 99 return 0; 100 } 101 fileReadCallback(nghttp2_session *,int32_t streamId,uint8_t * buf,size_t length,uint32_t * dataFlags,nghttp2_data_source *,void * userPtr)102 static ssize_t fileReadCallback( 103 nghttp2_session* /* session */, int32_t streamId, uint8_t* buf, 104 size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/, 105 void* userPtr) 106 { 107 self_type& self = userPtrToSelf(userPtr); 108 109 auto streamIt = self.streams.find(streamId); 110 if (streamIt == self.streams.end()) 111 { 112 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 113 } 114 Http2StreamData& stream = streamIt->second; 115 BMCWEB_LOG_DEBUG("File read callback length: {}", length); 116 if (!stream.writer) 117 { 118 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 119 } 120 boost::beast::error_code ec; 121 boost::optional<std::pair<boost::asio::const_buffer, bool>> out = 122 stream.writer->getWithMaxSize(ec, length); 123 if (ec) 124 { 125 BMCWEB_LOG_CRITICAL("Failed to get buffer"); 126 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 127 } 128 if (!out) 129 { 130 BMCWEB_LOG_ERROR("Empty file, setting EOF"); 131 *dataFlags |= NGHTTP2_DATA_FLAG_EOF; 132 return 0; 133 } 134 135 BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size()); 136 if (length < out->first.size()) 137 { 138 BMCWEB_LOG_CRITICAL( 139 "Buffer overflow that should never happen happened"); 140 // Should never happen because of length limit on get() above 141 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 142 } 143 boost::asio::mutable_buffer writeableBuf(buf, length); 144 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size()); 145 size_t copied = boost::asio::buffer_copy(writeableBuf, out->first); 146 if (copied != out->first.size()) 147 { 148 BMCWEB_LOG_ERROR( 149 "Couldn't copy all {} bytes into buffer, only copied {}", 150 out->first.size(), copied); 151 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 152 } 153 154 if (!out->second) 155 { 156 BMCWEB_LOG_DEBUG("Setting EOF flag"); 157 *dataFlags |= NGHTTP2_DATA_FLAG_EOF; 158 } 159 return static_cast<ssize_t>(copied); 160 } 161 headerFromStringViews(std::string_view name,std::string_view value,uint8_t flags)162 nghttp2_nv headerFromStringViews(std::string_view name, 163 std::string_view value, uint8_t flags) 164 { 165 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data()); 166 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data()); 167 return {nameData, valueData, name.size(), value.size(), flags}; 168 } 169 sendResponse(Response & completedRes,int32_t streamId)170 int sendResponse(Response& completedRes, int32_t streamId) 171 { 172 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId); 173 174 auto it = streams.find(streamId); 175 if (it == streams.end()) 176 { 177 close(); 178 return -1; 179 } 180 Http2StreamData& stream = it->second; 181 Response& res = stream.res; 182 res = std::move(completedRes); 183 184 completeResponseFields(stream.accept, res); 185 res.addHeader(boost::beast::http::field::date, getCachedDateStr()); 186 res.preparePayload(); 187 188 boost::beast::http::fields& fields = res.fields(); 189 std::string code = std::to_string(res.resultInt()); 190 std::vector<nghttp2_nv> hdr; 191 hdr.emplace_back( 192 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE)); 193 for (const boost::beast::http::fields::value_type& header : fields) 194 { 195 hdr.emplace_back(headerFromStringViews( 196 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE)); 197 } 198 http::response<bmcweb::HttpBody>& fbody = res.response; 199 stream.writer.emplace(fbody.base(), fbody.body()); 200 201 nghttp2_data_provider dataPrd{ 202 .source = {.fd = 0}, 203 .read_callback = fileReadCallback, 204 }; 205 206 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd); 207 if (rv != 0) 208 { 209 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv)); 210 close(); 211 return -1; 212 } 213 writeBuffer(); 214 215 return 0; 216 } 217 initializeNghttp2Session()218 nghttp2_session initializeNghttp2Session() 219 { 220 nghttp2_session_callbacks callbacks; 221 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic); 222 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic); 223 callbacks.setOnHeaderCallback(onHeaderCallbackStatic); 224 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic); 225 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic); 226 227 nghttp2_session session(callbacks); 228 session.setUserData(this); 229 230 return session; 231 } 232 onRequestRecv(int32_t streamId)233 int onRequestRecv(int32_t streamId) 234 { 235 BMCWEB_LOG_DEBUG("on_request_recv"); 236 237 auto it = streams.find(streamId); 238 if (it == streams.end()) 239 { 240 close(); 241 return -1; 242 } 243 auto& reqReader = it->second.reqReader; 244 if (reqReader) 245 { 246 boost::beast::error_code ec; 247 bmcweb::HttpBody::reader::finish(ec); 248 if (ec) 249 { 250 BMCWEB_LOG_CRITICAL("Failed to finalize payload"); 251 close(); 252 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 253 } 254 } 255 crow::Request& thisReq = *it->second.req; 256 thisReq.ioService = static_cast<decltype(thisReq.ioService)>( 257 &adaptor.get_executor().context()); 258 259 it->second.accept = thisReq.getHeaderValue("Accept"); 260 261 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq), 262 thisReq.url().encoded_path()); 263 264 crow::Response& thisRes = it->second.res; 265 266 thisRes.setCompleteRequestHandler( 267 [this, streamId](Response& completeRes) { 268 BMCWEB_LOG_DEBUG("res.completeRequestHandler called"); 269 if (sendResponse(completeRes, streamId) != 0) 270 { 271 close(); 272 return; 273 } 274 }); 275 auto asyncResp = 276 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res)); 277 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH) 278 { 279 thisReq.session = crow::authentication::authenticate( 280 {}, asyncResp->res, thisReq.method(), thisReq.req, nullptr); 281 if (!crow::authentication::isOnAllowlist(thisReq.url().path(), 282 thisReq.method()) && 283 thisReq.session == nullptr) 284 { 285 BMCWEB_LOG_WARNING("Authentication failed"); 286 forward_unauthorized::sendUnauthorized( 287 thisReq.url().encoded_path(), 288 thisReq.getHeaderValue("X-Requested-With"), 289 thisReq.getHeaderValue("Accept"), asyncResp->res); 290 return 0; 291 } 292 } 293 std::string_view expected = 294 thisReq.getHeaderValue(boost::beast::http::field::if_none_match); 295 BMCWEB_LOG_DEBUG("Setting expected hash {}", expected); 296 if (!expected.empty()) 297 { 298 asyncResp->res.setExpectedHash(expected); 299 } 300 handler->handle(it->second.req, asyncResp); 301 return 0; 302 } 303 onDataChunkRecvCallback(uint8_t,int32_t streamId,const uint8_t * data,size_t len)304 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId, 305 const uint8_t* data, size_t len) 306 { 307 auto thisStream = streams.find(streamId); 308 if (thisStream == streams.end()) 309 { 310 BMCWEB_LOG_ERROR("Unknown stream{}", streamId); 311 close(); 312 return -1; 313 } 314 315 std::optional<bmcweb::HttpBody::reader>& reqReader = 316 thisStream->second.reqReader; 317 if (!reqReader) 318 { 319 reqReader.emplace( 320 bmcweb::HttpBody::reader(thisStream->second.req->req.base(), 321 thisStream->second.req->req.body())); 322 } 323 boost::beast::error_code ec; 324 reqReader->put(boost::asio::const_buffer(data, len), ec); 325 if (ec) 326 { 327 BMCWEB_LOG_CRITICAL("Failed to write payload"); 328 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 329 } 330 return 0; 331 } 332 onDataChunkRecvStatic(nghttp2_session *,uint8_t flags,int32_t streamId,const uint8_t * data,size_t len,void * userData)333 static int onDataChunkRecvStatic( 334 nghttp2_session* /* session */, uint8_t flags, int32_t streamId, 335 const uint8_t* data, size_t len, void* userData) 336 { 337 BMCWEB_LOG_DEBUG("on_frame_recv_callback"); 338 if (userData == nullptr) 339 { 340 BMCWEB_LOG_CRITICAL("user data was null?"); 341 return NGHTTP2_ERR_CALLBACK_FAILURE; 342 } 343 return userPtrToSelf(userData).onDataChunkRecvCallback( 344 flags, streamId, data, len); 345 } 346 onFrameRecvCallback(const nghttp2_frame & frame)347 int onFrameRecvCallback(const nghttp2_frame& frame) 348 { 349 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type)); 350 switch (frame.hd.type) 351 { 352 case NGHTTP2_DATA: 353 case NGHTTP2_HEADERS: 354 // Check that the client request has finished 355 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) 356 { 357 return onRequestRecv(frame.hd.stream_id); 358 } 359 break; 360 default: 361 break; 362 } 363 return 0; 364 } 365 onFrameRecvCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,void * userData)366 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */, 367 const nghttp2_frame* frame, 368 void* userData) 369 { 370 BMCWEB_LOG_DEBUG("on_frame_recv_callback"); 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 return userPtrToSelf(userData).onFrameRecvCallback(*frame); 382 } 383 userPtrToSelf(void * userData)384 static self_type& userPtrToSelf(void* userData) 385 { 386 // This method exists to keep the unsafe reinterpret cast in one 387 // place. 388 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 389 return *reinterpret_cast<self_type*>(userData); 390 } 391 onStreamCloseCallbackStatic(nghttp2_session *,int32_t streamId,uint32_t,void * userData)392 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */, 393 int32_t streamId, 394 uint32_t /*unused*/, void* userData) 395 { 396 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId); 397 if (userData == nullptr) 398 { 399 BMCWEB_LOG_CRITICAL("user data was null?"); 400 return NGHTTP2_ERR_CALLBACK_FAILURE; 401 } 402 if (userPtrToSelf(userData).streams.erase(streamId) <= 0) 403 { 404 return -1; 405 } 406 return 0; 407 } 408 onHeaderCallback(const nghttp2_frame & frame,std::span<const uint8_t> name,std::span<const uint8_t> value)409 int onHeaderCallback(const nghttp2_frame& frame, 410 std::span<const uint8_t> name, 411 std::span<const uint8_t> value) 412 { 413 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 414 std::string_view nameSv(reinterpret_cast<const char*>(name.data()), 415 name.size()); 416 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 417 std::string_view valueSv(reinterpret_cast<const char*>(value.data()), 418 value.size()); 419 420 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv, 421 valueSv); 422 if (frame.hd.type != NGHTTP2_HEADERS) 423 { 424 return 0; 425 } 426 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST) 427 { 428 return 0; 429 } 430 auto thisStream = streams.find(frame.hd.stream_id); 431 if (thisStream == streams.end()) 432 { 433 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id); 434 close(); 435 return -1; 436 } 437 438 crow::Request& thisReq = *thisStream->second.req; 439 440 if (nameSv == ":path") 441 { 442 thisReq.target(valueSv); 443 } 444 else if (nameSv == ":method") 445 { 446 boost::beast::http::verb verb = 447 boost::beast::http::string_to_verb(valueSv); 448 if (verb == boost::beast::http::verb::unknown) 449 { 450 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv); 451 verb = boost::beast::http::verb::trace; 452 } 453 thisReq.method(verb); 454 } 455 else if (nameSv == ":scheme") 456 { 457 // Nothing to check on scheme 458 } 459 else 460 { 461 thisReq.addHeader(nameSv, valueSv); 462 } 463 return 0; 464 } 465 onHeaderCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t vallen,uint8_t,void * userData)466 static int onHeaderCallbackStatic( 467 nghttp2_session* /* session */, const nghttp2_frame* frame, 468 const uint8_t* name, size_t namelen, const uint8_t* value, 469 size_t vallen, uint8_t /* flags */, void* userData) 470 { 471 if (userData == nullptr) 472 { 473 BMCWEB_LOG_CRITICAL("user data was null?"); 474 return NGHTTP2_ERR_CALLBACK_FAILURE; 475 } 476 if (frame == nullptr) 477 { 478 BMCWEB_LOG_CRITICAL("frame was null?"); 479 return NGHTTP2_ERR_CALLBACK_FAILURE; 480 } 481 if (name == nullptr) 482 { 483 BMCWEB_LOG_CRITICAL("name was null?"); 484 return NGHTTP2_ERR_CALLBACK_FAILURE; 485 } 486 if (value == nullptr) 487 { 488 BMCWEB_LOG_CRITICAL("value was null?"); 489 return NGHTTP2_ERR_CALLBACK_FAILURE; 490 } 491 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen}, 492 {value, vallen}); 493 } 494 onBeginHeadersCallback(const nghttp2_frame & frame)495 int onBeginHeadersCallback(const nghttp2_frame& frame) 496 { 497 if (frame.hd.type == NGHTTP2_HEADERS && 498 frame.headers.cat == NGHTTP2_HCAT_REQUEST) 499 { 500 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id); 501 502 streams.emplace(frame.hd.stream_id, Http2StreamData()); 503 } 504 return 0; 505 } 506 onBeginHeadersCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,void * userData)507 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */, 508 const nghttp2_frame* frame, 509 void* userData) 510 { 511 BMCWEB_LOG_DEBUG("on_begin_headers_callback"); 512 if (userData == nullptr) 513 { 514 BMCWEB_LOG_CRITICAL("user data was null?"); 515 return NGHTTP2_ERR_CALLBACK_FAILURE; 516 } 517 if (frame == nullptr) 518 { 519 BMCWEB_LOG_CRITICAL("frame was null?"); 520 return NGHTTP2_ERR_CALLBACK_FAILURE; 521 } 522 return userPtrToSelf(userData).onBeginHeadersCallback(*frame); 523 } 524 afterWriteBuffer(const std::shared_ptr<self_type> & self,const boost::system::error_code & ec,size_t sendLength)525 static void afterWriteBuffer(const std::shared_ptr<self_type>& self, 526 const boost::system::error_code& ec, 527 size_t sendLength) 528 { 529 self->isWriting = false; 530 BMCWEB_LOG_DEBUG("Sent {}", sendLength); 531 if (ec) 532 { 533 self->close(); 534 return; 535 } 536 self->writeBuffer(); 537 } 538 writeBuffer()539 void writeBuffer() 540 { 541 if (isWriting) 542 { 543 return; 544 } 545 std::span<const uint8_t> data = ngSession.memSend(); 546 if (data.empty()) 547 { 548 return; 549 } 550 isWriting = true; 551 boost::asio::async_write( 552 adaptor, boost::asio::const_buffer(data.data(), data.size()), 553 std::bind_front(afterWriteBuffer, shared_from_this())); 554 } 555 close()556 void close() 557 { 558 if constexpr (std::is_same_v<Adaptor, 559 boost::asio::ssl::stream< 560 boost::asio::ip::tcp::socket>>) 561 { 562 adaptor.next_layer().close(); 563 } 564 else 565 { 566 adaptor.close(); 567 } 568 } 569 afterDoRead(const std::shared_ptr<self_type> &,const boost::system::error_code & ec,size_t bytesTransferred)570 void afterDoRead(const std::shared_ptr<self_type>& /*self*/, 571 const boost::system::error_code& ec, 572 size_t bytesTransferred) 573 { 574 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this), 575 bytesTransferred); 576 577 if (ec) 578 { 579 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this), 580 ec.message()); 581 close(); 582 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this)); 583 return; 584 } 585 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred}; 586 587 ssize_t readLen = ngSession.memRecv(bufferSpan); 588 if (readLen < 0) 589 { 590 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen); 591 close(); 592 return; 593 } 594 writeBuffer(); 595 596 doRead(); 597 } 598 doRead()599 void doRead() 600 { 601 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this)); 602 adaptor.async_read_some( 603 boost::asio::buffer(inBuffer), 604 std::bind_front(&self_type::afterDoRead, this, shared_from_this())); 605 } 606 607 // A mapping from http2 stream ID to Stream Data 608 std::map<int32_t, Http2StreamData> streams; 609 610 std::array<uint8_t, 8192> inBuffer{}; 611 612 Adaptor adaptor; 613 bool isWriting = false; 614 615 nghttp2_session ngSession; 616 617 Handler* handler; 618 std::function<std::string()>& getCachedDateStr; 619 620 using std::enable_shared_from_this< 621 HTTP2Connection<Adaptor, Handler>>::shared_from_this; 622 623 using std::enable_shared_from_this< 624 HTTP2Connection<Adaptor, Handler>>::weak_from_this; 625 }; 626 } // namespace crow 627