diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc index 1834e1c8b4e..ea0c9630d67 100644 --- a/proxy/http2/Http2ClientSession.cc +++ b/proxy/http2/Http2ClientSession.cc @@ -1,6 +1,6 @@ /** @file - Http2ClientSession. + Http2ClientSession.cc @section license License @@ -24,42 +24,11 @@ #include "Http2ClientSession.h" #include "HttpDebugNames.h" #include "tscore/ink_base64.h" - +#include "Http2CommonSessionInternal.h" #include "P_SSLNetVConnection.h" -#define REMEMBER(e, r) \ - { \ - this->remember(MakeSourceLocation(), e, r); \ - } - -#define STATE_ENTER(state_name, event) \ - do { \ - REMEMBER(event, this->recursion) \ - SsnDebug(this, "http2_cs", "[%" PRId64 "] [%s, %s]", this->connection_id(), #state_name, \ - HttpDebugNames::get_event_name(event)); \ - } while (0) - -#define Http2SsnDebug(fmt, ...) SsnDebug(this, "http2_cs", "[%" PRId64 "] " fmt, this->connection_id(), ##__VA_ARGS__) - -#define HTTP2_SET_SESSION_HANDLER(handler) \ - do { \ - REMEMBER(NO_EVENT, this->recursion); \ - this->session_handler = (handler); \ - } while (0) - ClassAllocator http2ClientSessionAllocator("http2ClientSessionAllocator"); -// memcpy the requested bytes from the IOBufferReader, returning how many were -// actually copied. -static inline unsigned -copy_from_buffer_reader(void *dst, IOBufferReader *reader, unsigned nbytes) -{ - char *end; - - end = reader->memcpy(dst, nbytes, 0 /* offset */); - return end - static_cast(dst); -} - static int send_connection_event(Continuation *cont, int event, void *edata) { @@ -88,82 +57,11 @@ Http2ClientSession::free() _vc->do_io_close(); _vc = nullptr; } - - // Make sure the we are at the bottom of the stack - if (connection_state.is_recursing() || this->recursion != 0) { - // Note that we are ready to be cleaned up - // One of the event handlers will catch it - kill_me = true; - return; - } - - REMEMBER(NO_EVENT, this->recursion) - Http2SsnDebug("session free"); - - if (this->_reenable_event) { - this->_reenable_event->cancel(); - this->_reenable_event = nullptr; - } - - // Don't free active ProxySession - ink_release_assert(is_active() == false); - - this->_milestones.mark(Http2SsnMilestone::CLOSE); - ink_hrtime total_time = this->_milestones.elapsed(Http2SsnMilestone::OPEN, Http2SsnMilestone::CLOSE); - - // Slow Log - if (Http2::con_slow_log_threshold != 0 && ink_hrtime_from_msec(Http2::con_slow_log_threshold) < total_time) { - Error("[%" PRIu64 "] Slow H2 Connection: open: %" PRIu64 " close: %.3f", this->con_id, - ink_hrtime_to_msec(this->_milestones[Http2SsnMilestone::OPEN]), - this->_milestones.difference_sec(Http2SsnMilestone::OPEN, Http2SsnMilestone::CLOSE)); + auto mutex_thread = this->mutex->thread_holding; + if (Http2CommonSession::common_free(this)) { + HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT, mutex_thread); + THREAD_FREE(this, http2ClientSessionAllocator, this_ethread()); } - - HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT, this->mutex->thread_holding); - - // Update stats on how we died. May want to eliminate this. Was useful for - // tracking down which cases we were having problems cleaning up. But for general - // use probably not worth the effort - if (cause_of_death != Http2SessionCod::NOT_PROVIDED) { - switch (cause_of_death) { - case Http2SessionCod::HIGH_ERROR_RATE: - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE, this_ethread()); - break; - case Http2SessionCod::NOT_PROVIDED: - // Can't happen but this case is here to not have default case. - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_OTHER, this_ethread()); - break; - } - } else { - switch (dying_event) { - case VC_EVENT_NONE: - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_DEFAULT, this_ethread()); - break; - case VC_EVENT_ACTIVE_TIMEOUT: - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_ACTIVE, this_ethread()); - break; - case VC_EVENT_INACTIVITY_TIMEOUT: - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_INACTIVE, this_ethread()); - break; - case VC_EVENT_ERROR: - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_ERROR, this_ethread()); - break; - case VC_EVENT_EOS: - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_EOS, this_ethread()); - break; - default: - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_OTHER, this_ethread()); - break; - } - } - - ink_release_assert(this->_vc == nullptr); - - delete _h2_pushed_urls; - this->connection_state.destroy(); - - free_MIOBuffer(this->read_buffer); - free_MIOBuffer(this->write_buffer); - THREAD_FREE(this, http2ClientSessionAllocator, this_ethread()); } void @@ -249,46 +147,6 @@ Http2ClientSession::do_io_close(int alerrno) this->do_io_write(this, 0, nullptr); } -void -Http2ClientSession::set_half_close_local_flag(bool flag) -{ - if (!half_close_local && flag) { - Http2SsnDebug("session half-close local"); - } - half_close_local = flag; -} - -int64_t -Http2ClientSession::xmit(const Http2TxFrame &frame, bool flush) -{ - int64_t len = frame.write_to(this->write_buffer); - this->_pending_sending_data_size += len; - // Force flush for some cases - if (!flush) { - // Flush if we already use half of the buffer to avoid adding a new block to the chain. - // A frame size can be 16MB at maximum so blocks can be added, but that's fine. - if (this->_pending_sending_data_size >= this->_write_size_threshold) { - flush = true; - } - } - - if (flush) { - this->flush(); - } - - return len; -} - -void -Http2ClientSession::flush() -{ - if (this->_pending_sending_data_size > 0) { - this->_pending_sending_data_size = 0; - this->_write_buffer_last_flush = Thread::get_hrtime(); - write_reenable(); - } -} - int Http2ClientSession::main_event_handler(int event, void *edata) { @@ -380,226 +238,6 @@ Http2ClientSession::main_event_handler(int event, void *edata) return retval; } -int -Http2ClientSession::state_read_connection_preface(int event, void *edata) -{ - VIO *vio = static_cast(edata); - - STATE_ENTER(&Http2ClientSession::state_read_connection_preface, event); - ink_assert(event == VC_EVENT_READ_COMPLETE || event == VC_EVENT_READ_READY); - - if (this->_read_buffer_reader->read_avail() >= static_cast(HTTP2_CONNECTION_PREFACE_LEN)) { - char buf[HTTP2_CONNECTION_PREFACE_LEN]; - unsigned nbytes; - - nbytes = copy_from_buffer_reader(buf, this->_read_buffer_reader, sizeof(buf)); - ink_release_assert(nbytes == HTTP2_CONNECTION_PREFACE_LEN); - - if (memcmp(HTTP2_CONNECTION_PREFACE, buf, nbytes) != 0) { - Http2SsnDebug("invalid connection preface"); - this->do_io_close(); - return 0; - } - - // Check whether data is read from early data - if (this->read_from_early_data > 0) { - this->read_from_early_data -= this->read_from_early_data > nbytes ? nbytes : this->read_from_early_data; - } - - Http2SsnDebug("received connection preface"); - this->_read_buffer_reader->consume(nbytes); - HTTP2_SET_SESSION_HANDLER(&Http2ClientSession::state_start_frame_read); - - _vc->set_inactivity_timeout(HRTIME_SECONDS(Http2::no_activity_timeout_in)); - _vc->set_active_timeout(HRTIME_SECONDS(Http2::active_timeout_in)); - - // XXX start the write VIO ... - - // If we have unconsumed data, start tranferring frames now. - if (this->_read_buffer_reader->is_read_avail_more_than(0)) { - return this->handleEvent(VC_EVENT_READ_READY, vio); - } - } - - // XXX We don't have enough data to check the connection preface. We should - // reset the accept inactivity - // timeout. We should have a maximum timeout to get the session started - // though. - - vio->reenable(); - return 0; -} - -int -Http2ClientSession::state_start_frame_read(int event, void *edata) -{ - VIO *vio = static_cast(edata); - - STATE_ENTER(&Http2ClientSession::state_start_frame_read, event); - ink_assert(event == VC_EVENT_READ_COMPLETE || event == VC_EVENT_READ_READY); - return do_process_frame_read(event, vio, false); -} - -int -Http2ClientSession::do_start_frame_read(Http2ErrorCode &ret_error) -{ - ret_error = Http2ErrorCode::HTTP2_ERROR_NO_ERROR; - ink_release_assert(this->_read_buffer_reader->read_avail() >= (int64_t)HTTP2_FRAME_HEADER_LEN); - - uint8_t buf[HTTP2_FRAME_HEADER_LEN]; - unsigned nbytes; - - Http2SsnDebug("receiving frame header"); - nbytes = copy_from_buffer_reader(buf, this->_read_buffer_reader, sizeof(buf)); - - this->cur_frame_from_early_data = false; - if (!http2_parse_frame_header(make_iovec(buf), this->current_hdr)) { - Http2SsnDebug("frame header parse failure"); - this->do_io_close(); - return -1; - } - - // Check whether data is read from early data - if (this->read_from_early_data > 0) { - this->read_from_early_data -= this->read_from_early_data > nbytes ? nbytes : this->read_from_early_data; - this->cur_frame_from_early_data = true; - } - - Http2SsnDebug("frame header length=%u, type=%u, flags=0x%x, streamid=%u", (unsigned)this->current_hdr.length, - (unsigned)this->current_hdr.type, (unsigned)this->current_hdr.flags, this->current_hdr.streamid); - - this->_read_buffer_reader->consume(nbytes); - - if (!http2_frame_header_is_valid(this->current_hdr, this->connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE))) { - ret_error = Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; - return -1; - } - - // If we know up front that the payload is too long, nuke this connection. - if (this->current_hdr.length > this->connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)) { - ret_error = Http2ErrorCode::HTTP2_ERROR_FRAME_SIZE_ERROR; - return -1; - } - - // CONTINUATIONs MUST follow behind HEADERS which doesn't have END_HEADERS - Http2StreamId continued_stream_id = this->connection_state.get_continued_stream_id(); - - if (continued_stream_id != 0 && - (continued_stream_id != this->current_hdr.streamid || this->current_hdr.type != HTTP2_FRAME_TYPE_CONTINUATION)) { - ret_error = Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; - return -1; - } - return 0; -} - -int -Http2ClientSession::state_complete_frame_read(int event, void *edata) -{ - VIO *vio = static_cast(edata); - STATE_ENTER(&Http2ClientSession::state_complete_frame_read, event); - ink_assert(event == VC_EVENT_READ_COMPLETE || event == VC_EVENT_READ_READY); - if (this->_read_buffer_reader->read_avail() < this->current_hdr.length) { - if (this->_should_do_something_else()) { - if (this->_reenable_event == nullptr) { - vio->disable(); - this->_reenable_event = mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(1), HTTP2_SESSION_EVENT_REENABLE, vio); - } else { - vio->reenable(); - } - } else { - vio->reenable(); - } - return 0; - } - Http2SsnDebug("completed frame read, %" PRId64 " bytes available", this->_read_buffer_reader->read_avail()); - - return do_process_frame_read(event, vio, true); -} - -int -Http2ClientSession::do_complete_frame_read() -{ - // XXX parse the frame and handle it ... - ink_release_assert(this->_read_buffer_reader->read_avail() >= this->current_hdr.length); - - Http2Frame frame(this->current_hdr, this->_read_buffer_reader, this->cur_frame_from_early_data); - connection_state.rcv_frame(&frame); - - // Check whether data is read from early data - if (this->read_from_early_data > 0) { - this->read_from_early_data -= - this->read_from_early_data > this->current_hdr.length ? this->current_hdr.length : this->read_from_early_data; - } - this->_read_buffer_reader->consume(this->current_hdr.length); - ++(this->_n_frame_read); - - // Set the event handler if there is no more data to process a new frame - HTTP2_SET_SESSION_HANDLER(&Http2ClientSession::state_start_frame_read); - - return 0; -} - -int -Http2ClientSession::do_process_frame_read(int event, VIO *vio, bool inside_frame) -{ - if (inside_frame) { - do_complete_frame_read(); - } - - while (this->_read_buffer_reader->read_avail() >= static_cast(HTTP2_FRAME_HEADER_LEN)) { - // Cancel reading if there was an error or connection is closed - if (connection_state.tx_error_code.code != static_cast(Http2ErrorCode::HTTP2_ERROR_NO_ERROR) || - connection_state.is_state_closed()) { - Http2SsnDebug("reading a frame has been canceled (%u)", connection_state.tx_error_code.code); - break; - } - - Http2ErrorCode err = Http2ErrorCode::HTTP2_ERROR_NO_ERROR; - if (this->connection_state.get_stream_error_rate() > std::min(1.0, Http2::stream_error_rate_threshold * 2.0)) { - ip_port_text_buffer ipb; - const char *client_ip = ats_ip_ntop(get_remote_addr(), ipb, sizeof(ipb)); - SiteThrottledWarning("HTTP/2 session error client_ip=%s session_id=%" PRId64 - " closing a connection, because its stream error rate (%f) exceeded the threshold (%f)", - client_ip, connection_id(), this->connection_state.get_stream_error_rate(), - Http2::stream_error_rate_threshold); - err = Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM; - } - - // Return if there was an error - if (err > Http2ErrorCode::HTTP2_ERROR_NO_ERROR || do_start_frame_read(err) < 0) { - // send an error if specified. Otherwise, just go away - if (err > Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { - if (!this->connection_state.is_state_closed()) { - this->connection_state.send_goaway_frame(this->connection_state.get_latest_stream_id_in(), err); - this->set_half_close_local_flag(true); - } - } - return 0; - } - - // If there is no more data to finish the frame, set up the event handler and reenable - if (this->_read_buffer_reader->read_avail() < this->current_hdr.length) { - HTTP2_SET_SESSION_HANDLER(&Http2ClientSession::state_complete_frame_read); - break; - } - do_complete_frame_read(); - - if (this->_should_do_something_else()) { - if (this->_reenable_event == nullptr) { - vio->disable(); - this->_reenable_event = mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(1), HTTP2_SESSION_EVENT_REENABLE, vio); - return 0; - } - } - } - - // If the client hasn't shut us down, reenable - if (!this->is_peer_closed()) { - vio->reenable(); - } - return 0; -} - void Http2ClientSession::increment_current_active_connections_stat() { @@ -612,19 +250,6 @@ Http2ClientSession::decrement_current_active_connections_stat() HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_COUNT, this_ethread()); } -void -Http2ClientSession::remember(const SourceLocation &location, int event, int reentrant) -{ - this->_history.push_back(location, event, reentrant); -} - -bool -Http2ClientSession::_should_do_something_else() -{ - // Do something else every 128 incoming frames if connection state isn't closed - return (this->_n_frame_read & 0x7F) == 0 && !connection_state.is_state_closed(); -} - sockaddr const * Http2ClientSession::get_remote_addr() const { @@ -637,18 +262,6 @@ Http2ClientSession::get_local_addr() return _vc ? _vc->get_local_addr() : &cached_local_addr.sa; } -int64_t -Http2ClientSession::write_avail() -{ - return this->write_buffer->write_avail(); -} - -void -Http2ClientSession::write_reenable() -{ - write_vio->reenable(); -} - int Http2ClientSession::get_transact_count() const { @@ -692,22 +305,14 @@ Http2ClientSession::protocol_contains(std::string_view prefix) const return retval; } -HTTPVersion -Http2ClientSession::get_version(HTTPHdr &hdr) const +ProxySession * +Http2ClientSession::get_proxy_session() { - return HTTP_2_0; + return this; } -void -Http2ClientSession::add_url_to_pushed_table(const char *url, int url_len) +HTTPVersion +Http2ClientSession::get_version(HTTPHdr &hdr) const { - // Delay std::unordered_set allocation until when it used - if (_h2_pushed_urls == nullptr) { - this->_h2_pushed_urls = new std::unordered_set(); - this->_h2_pushed_urls->reserve(Http2::push_diary_size); - } - - if (_h2_pushed_urls->size() < Http2::push_diary_size) { - _h2_pushed_urls->emplace(url); - } + return HTTP_2_0; } diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h index 73b6dd83d9b..cd4cf0df503 100644 --- a/proxy/http2/Http2ClientSession.h +++ b/proxy/http2/Http2ClientSession.h @@ -1,6 +1,6 @@ /** @file - Http2ClientSession. + Http2ClientSession.h @section license License @@ -23,57 +23,14 @@ #pragma once -#include "HTTP2.h" #include "Plugin.h" -#include "ProxySession.h" -#include "Http2ConnectionState.h" -#include "Http2Frame.h" +#include "Http2CommonSession.h" #include #include "tscore/ink_inet.h" #include "tscore/History.h" #include "Milestones.h" -// Name Edata Description -// HTTP2_SESSION_EVENT_INIT Http2ClientSession * HTTP/2 session is born -// HTTP2_SESSION_EVENT_FINI Http2ClientSession * HTTP/2 session is ended -// HTTP2_SESSION_EVENT_RECV Http2Frame * Received a frame -// HTTP2_SESSION_EVENT_XMIT Http2Frame * Send this frame - -#define HTTP2_SESSION_EVENT_INIT (HTTP2_SESSION_EVENTS_START + 1) -#define HTTP2_SESSION_EVENT_FINI (HTTP2_SESSION_EVENTS_START + 2) -#define HTTP2_SESSION_EVENT_RECV (HTTP2_SESSION_EVENTS_START + 3) -#define HTTP2_SESSION_EVENT_XMIT (HTTP2_SESSION_EVENTS_START + 4) -#define HTTP2_SESSION_EVENT_SHUTDOWN_INIT (HTTP2_SESSION_EVENTS_START + 5) -#define HTTP2_SESSION_EVENT_SHUTDOWN_CONT (HTTP2_SESSION_EVENTS_START + 6) -#define HTTP2_SESSION_EVENT_REENABLE (HTTP2_SESSION_EVENTS_START + 7) - -enum class Http2SessionCod : int { - NOT_PROVIDED, - HIGH_ERROR_RATE, -}; - -enum class Http2SsnMilestone { - OPEN = 0, - CLOSE, - LAST_ENTRY, -}; - -size_t const HTTP2_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; - -/** - @startuml - title HTTP/2 Session Handler - state of reading HTTP/2 frame - hide empty description - - [*] --> state_read_connection_preface : start() - state_read_connection_preface --> state_start_frame_read : receive connection preface - state_start_frame_read --> state_start_frame_read : do_complete_frame_read() - state_start_frame_read --> state_complete_frame_read : reading HTTP/2 frame is halfway but no data in the buffer - state_complete_frame_read --> state_start_frame_read : do_complete_frame_read() - - @enduml - */ -class Http2ClientSession : public ProxySession +class Http2ClientSession : public ProxySession, public Http2CommonSession { public: using super = ProxySession; ///< Parent type. @@ -94,11 +51,6 @@ class Http2ClientSession : public ProxySession void release(ProxyTransaction *trans) override; void free() override; - // more methods - void write_reenable(); - int64_t xmit(const Http2TxFrame &frame, bool flush = true); - void flush(); - //////////////////// // Accessors sockaddr const *get_remote_addr() const override; @@ -111,124 +63,17 @@ class Http2ClientSession : public ProxySession void increment_current_active_connections_stat() override; void decrement_current_active_connections_stat() override; - void set_dying_event(int event); - int get_dying_event() const; - bool ready_to_free() const; - bool is_recursing() const; - void set_half_close_local_flag(bool flag); - bool get_half_close_local_flag() const; - bool is_url_pushed(const char *url, int url_len); - void add_url_to_pushed_table(const char *url, int url_len); - - // Record history from Http2ConnectionState - void remember(const SourceLocation &location, int event, int reentrant = NO_REENTRANT); - - int64_t write_avail(); + ProxySession *get_proxy_session() override; // noncopyable Http2ClientSession(Http2ClientSession &) = delete; Http2ClientSession &operator=(const Http2ClientSession &) = delete; - /////////////////// - // Variables - Http2ConnectionState connection_state; - private: int main_event_handler(int, void *); - // SessionHandler(s) - state of reading frame - int state_read_connection_preface(int, void *); - int state_start_frame_read(int, void *); - int state_complete_frame_read(int, void *); - - // state_start_frame_read and state_complete_frame_read are set up as session event handler. - // Both feed into do_process_frame_read which may iterate if there are multiple frames ready on the wire - int do_process_frame_read(int event, VIO *vio, bool inside_frame); - int do_start_frame_read(Http2ErrorCode &ret_error); - int do_complete_frame_read(); - - bool _should_do_something_else(); - - //////// - // Variables - SessionHandler session_handler = nullptr; - - MIOBuffer *read_buffer = nullptr; - IOBufferReader *_read_buffer_reader = nullptr; - - VIO *write_vio = nullptr; - MIOBuffer *write_buffer = nullptr; - IOBufferReader *_write_buffer_reader = nullptr; - - Http2FrameHeader current_hdr = {0, 0, 0, 0}; - uint32_t _write_size_threshold = 0; - uint32_t _write_time_threshold = 100; - ink_hrtime _write_buffer_last_flush = 0; - IpEndpoint cached_client_addr; IpEndpoint cached_local_addr; - - History _history; - Milestones(Http2SsnMilestone::LAST_ENTRY)> _milestones; - - int dying_event = 0; - bool kill_me = false; - Http2SessionCod cause_of_death = Http2SessionCod::NOT_PROVIDED; - bool half_close_local = false; - int recursion = 0; - - std::unordered_set *_h2_pushed_urls = nullptr; - - Event *_reenable_event = nullptr; - int _n_frame_read = 0; - - uint32_t _pending_sending_data_size = 0; - - int64_t read_from_early_data = 0; - bool cur_frame_from_early_data = false; }; extern ClassAllocator http2ClientSessionAllocator; - -/////////////////////////////////////////////// -// INLINE - -inline bool -Http2ClientSession::ready_to_free() const -{ - return kill_me; -} - -inline void -Http2ClientSession::set_dying_event(int event) -{ - dying_event = event; -} - -inline int -Http2ClientSession::get_dying_event() const -{ - return dying_event; -} - -inline bool -Http2ClientSession::is_recursing() const -{ - return recursion > 0; -} - -inline bool -Http2ClientSession::get_half_close_local_flag() const -{ - return half_close_local; -} - -inline bool -Http2ClientSession::is_url_pushed(const char *url, int url_len) -{ - if (_h2_pushed_urls == nullptr) { - return false; - } - - return _h2_pushed_urls->find(url) != _h2_pushed_urls->end(); -} diff --git a/proxy/http2/Http2CommonSession.cc b/proxy/http2/Http2CommonSession.cc new file mode 100644 index 00000000000..55974b5b0fc --- /dev/null +++ b/proxy/http2/Http2CommonSession.cc @@ -0,0 +1,434 @@ +/** @file + + Http2CommonSession.cc + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "Http2CommonSession.h" +#include "HttpDebugNames.h" + +#define REMEMBER(e, r) \ + { \ + this->remember(MakeSourceLocation(), e, r); \ + } + +#define STATE_ENTER(state_name, event) \ + do { \ + REMEMBER(event, this->recursion) \ + Debug("http2_cs", "[%" PRId64 "] [%s, %s]", this->get_connection_id(), #state_name, HttpDebugNames::get_event_name(event)); \ + } while (0) + +#define Http2SsnDebug(fmt, ...) Debug("http2_cs", "[%" PRId64 "] " fmt, this->get_connection_id(), ##__VA_ARGS__) + +#define HTTP2_SET_SESSION_HANDLER(handler) \ + do { \ + REMEMBER(NO_EVENT, this->recursion); \ + this->session_handler = (handler); \ + } while (0) + +// memcpy the requested bytes from the IOBufferReader, returning how many were +// actually copied. +static inline unsigned +copy_from_buffer_reader(void *dst, IOBufferReader *reader, unsigned nbytes) +{ + char *end; + + end = reader->memcpy(dst, nbytes, 0 /* offset */); + return end - static_cast(dst); +} + +void +Http2CommonSession::remember(const SourceLocation &location, int event, int reentrant) +{ + this->_history.push_back(location, event, reentrant); +} + +bool +Http2CommonSession::common_free(ProxySession *ssn) +{ + if (this->_reenable_event) { + this->_reenable_event->cancel(); + this->_reenable_event = nullptr; + } + + // Make sure the we are at the bottom of the stack + if (this->connection_state.is_recursing() || this->recursion != 0) { + // Note that we are ready to be cleaned up + // One of the event handlers will catch it + this->kill_me = true; + return false; + } + + REMEMBER(NO_EVENT, this->recursion) + Http2SsnDebug("session free"); + + // Don't free active ProxySession + ink_release_assert(ssn->is_active() == false); + + this->_milestones.mark(Http2SsnMilestone::CLOSE); + ink_hrtime total_time = this->_milestones.elapsed(Http2SsnMilestone::OPEN, Http2SsnMilestone::CLOSE); + + // Slow Log + if (Http2::con_slow_log_threshold != 0 && ink_hrtime_from_msec(Http2::con_slow_log_threshold) < total_time) { + Error("[%" PRIu64 "] Slow H2 Connection: open: %" PRIu64 " close: %.3f", ssn->connection_id(), + ink_hrtime_to_msec(this->_milestones[Http2SsnMilestone::OPEN]), + this->_milestones.difference_sec(Http2SsnMilestone::OPEN, Http2SsnMilestone::CLOSE)); + } + // Update stats on how we died. May want to eliminate this. Was useful for + // tracking down which cases we were having problems cleaning up. But for general + // use probably not worth the effort + if (cause_of_death != Http2SessionCod::NOT_PROVIDED) { + switch (cause_of_death) { + case Http2SessionCod::HIGH_ERROR_RATE: + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE, this_ethread()); + break; + case Http2SessionCod::NOT_PROVIDED: + // Can't happen but this case is here to not have default case. + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_OTHER, this_ethread()); + break; + } + } else { + switch (dying_event) { + case VC_EVENT_NONE: + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_DEFAULT, this_ethread()); + break; + case VC_EVENT_ACTIVE_TIMEOUT: + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_ACTIVE, this_ethread()); + break; + case VC_EVENT_INACTIVITY_TIMEOUT: + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_INACTIVE, this_ethread()); + break; + case VC_EVENT_ERROR: + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_ERROR, this_ethread()); + break; + case VC_EVENT_EOS: + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_EOS, this_ethread()); + break; + default: + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_OTHER, this_ethread()); + break; + } + } + + delete _h2_pushed_urls; + _h2_pushed_urls = nullptr; + this->connection_state.destroy(); + + free_MIOBuffer(this->read_buffer); + this->read_buffer = nullptr; + free_MIOBuffer(this->write_buffer); + this->write_buffer = nullptr; + return true; +} + +void +Http2CommonSession::set_half_close_local_flag(bool flag) +{ + if (!half_close_local && flag) { + Http2SsnDebug("session half-close local"); + } + half_close_local = flag; +} + +int64_t +Http2CommonSession::xmit(const Http2TxFrame &frame, bool flush) +{ + int64_t len = frame.write_to(this->write_buffer); + this->_pending_sending_data_size += len; + // Force flush for some cases + if (!flush) { + // Flush if we already use half of the buffer to avoid adding a new block to the chain. + // A frame size can be 16MB at maximum so blocks can be added, but that's fine. + if (this->_pending_sending_data_size >= this->_write_size_threshold) { + flush = true; + } + } + + if (flush) { + this->flush(); + } + + return len; +} + +void +Http2CommonSession::flush() +{ + if (this->_pending_sending_data_size > 0) { + this->_pending_sending_data_size = 0; + this->_write_buffer_last_flush = Thread::get_hrtime(); + write_reenable(); + } +} + +int +Http2CommonSession::state_read_connection_preface(int event, void *edata) +{ + VIO *vio = static_cast(edata); + + STATE_ENTER(&Http2CommonSession::state_read_connection_preface, event); + ink_assert(event == VC_EVENT_READ_COMPLETE || event == VC_EVENT_READ_READY); + + if (this->_read_buffer_reader->read_avail() >= static_cast(HTTP2_CONNECTION_PREFACE_LEN)) { + char buf[HTTP2_CONNECTION_PREFACE_LEN]; + unsigned nbytes; + + nbytes = copy_from_buffer_reader(buf, this->_read_buffer_reader, sizeof(buf)); + ink_release_assert(nbytes == HTTP2_CONNECTION_PREFACE_LEN); + + if (memcmp(HTTP2_CONNECTION_PREFACE, buf, nbytes) != 0) { + Http2SsnDebug("invalid connection preface"); + this->get_proxy_session()->do_io_close(); + return 0; + } + + // Check whether data is read from early data + if (this->read_from_early_data > 0) { + this->read_from_early_data -= this->read_from_early_data > nbytes ? nbytes : this->read_from_early_data; + } + + Http2SsnDebug("received connection preface"); + this->_read_buffer_reader->consume(nbytes); + HTTP2_SET_SESSION_HANDLER(&Http2CommonSession::state_start_frame_read); + + this->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(Http2::no_activity_timeout_in)); + this->get_netvc()->set_active_timeout(HRTIME_SECONDS(Http2::active_timeout_in)); + + // XXX start the write VIO ... + + // If we have unconsumed data, start tranferring frames now. + if (this->_read_buffer_reader->is_read_avail_more_than(0)) { + return this->get_proxy_session()->handleEvent(VC_EVENT_READ_READY, vio); + } + } + + // XXX We don't have enough data to check the connection preface. We should + // reset the accept inactivity + // timeout. We should have a maximum timeout to get the session started + // though. + + vio->reenable(); + return 0; +} + +int +Http2CommonSession::state_start_frame_read(int event, void *edata) +{ + VIO *vio = static_cast(edata); + + STATE_ENTER(&Http2CommonSession::state_start_frame_read, event); + ink_assert(event == VC_EVENT_READ_COMPLETE || event == VC_EVENT_READ_READY); + return do_process_frame_read(event, vio, false); +} + +int +Http2CommonSession::do_start_frame_read(Http2ErrorCode &ret_error) +{ + ret_error = Http2ErrorCode::HTTP2_ERROR_NO_ERROR; + ink_release_assert(this->_read_buffer_reader->read_avail() >= (int64_t)HTTP2_FRAME_HEADER_LEN); + + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + unsigned nbytes; + + Http2SsnDebug("receiving frame header"); + nbytes = copy_from_buffer_reader(buf, this->_read_buffer_reader, sizeof(buf)); + + this->cur_frame_from_early_data = false; + if (!http2_parse_frame_header(make_iovec(buf), this->current_hdr)) { + Http2SsnDebug("frame header parse failure"); + this->get_proxy_session()->do_io_close(); + return -1; + } + + // Check whether data is read from early data + if (this->read_from_early_data > 0) { + this->read_from_early_data -= this->read_from_early_data > nbytes ? nbytes : this->read_from_early_data; + this->cur_frame_from_early_data = true; + } + + Http2SsnDebug("frame header length=%u, type=%u, flags=0x%x, streamid=%u", (unsigned)this->current_hdr.length, + (unsigned)this->current_hdr.type, (unsigned)this->current_hdr.flags, this->current_hdr.streamid); + + this->_read_buffer_reader->consume(nbytes); + + if (!http2_frame_header_is_valid(this->current_hdr, this->connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE))) { + ret_error = Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; + return -1; + } + + // If we know up front that the payload is too long, nuke this connection. + if (this->current_hdr.length > this->connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)) { + ret_error = Http2ErrorCode::HTTP2_ERROR_FRAME_SIZE_ERROR; + return -1; + } + + // CONTINUATIONs MUST follow behind HEADERS which doesn't have END_HEADERS + Http2StreamId continued_stream_id = this->connection_state.get_continued_stream_id(); + + if (continued_stream_id != 0 && + (continued_stream_id != this->current_hdr.streamid || this->current_hdr.type != HTTP2_FRAME_TYPE_CONTINUATION)) { + ret_error = Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; + return -1; + } + return 0; +} + +int +Http2CommonSession::state_complete_frame_read(int event, void *edata) +{ + VIO *vio = static_cast(edata); + STATE_ENTER(&Http2CommonSession::state_complete_frame_read, event); + ink_assert(event == VC_EVENT_READ_COMPLETE || event == VC_EVENT_READ_READY); + if (this->_read_buffer_reader->read_avail() < this->current_hdr.length) { + if (this->_should_do_something_else()) { + if (this->_reenable_event == nullptr) { + vio->disable(); + this->_reenable_event = this->get_mutex()->thread_holding->schedule_in(this->get_proxy_session(), HRTIME_MSECONDS(1), + HTTP2_SESSION_EVENT_REENABLE, vio); + } else { + vio->reenable(); + } + } else { + vio->reenable(); + } + return 0; + } + Http2SsnDebug("completed frame read, %" PRId64 " bytes available", this->_read_buffer_reader->read_avail()); + + return do_process_frame_read(event, vio, true); +} + +int +Http2CommonSession::do_complete_frame_read() +{ + // XXX parse the frame and handle it ... + ink_release_assert(this->_read_buffer_reader->read_avail() >= this->current_hdr.length); + + Http2Frame frame(this->current_hdr, this->_read_buffer_reader, this->cur_frame_from_early_data); + connection_state.rcv_frame(&frame); + + // Check whether data is read from early data + if (this->read_from_early_data > 0) { + this->read_from_early_data -= + this->read_from_early_data > this->current_hdr.length ? this->current_hdr.length : this->read_from_early_data; + } + this->_read_buffer_reader->consume(this->current_hdr.length); + ++(this->_n_frame_read); + + // Set the event handler if there is no more data to process a new frame + HTTP2_SET_SESSION_HANDLER(&Http2CommonSession::state_start_frame_read); + + return 0; +} + +int +Http2CommonSession::do_process_frame_read(int event, VIO *vio, bool inside_frame) +{ + if (inside_frame) { + do_complete_frame_read(); + } + + while (this->_read_buffer_reader->read_avail() >= static_cast(HTTP2_FRAME_HEADER_LEN)) { + // Cancel reading if there was an error or connection is closed + if (connection_state.tx_error_code.code != static_cast(Http2ErrorCode::HTTP2_ERROR_NO_ERROR) || + connection_state.is_state_closed()) { + Http2SsnDebug("reading a frame has been canceled (%u)", connection_state.tx_error_code.code); + break; + } + + Http2ErrorCode err = Http2ErrorCode::HTTP2_ERROR_NO_ERROR; + if (this->connection_state.get_stream_error_rate() > std::min(1.0, Http2::stream_error_rate_threshold * 2.0)) { + ip_port_text_buffer ipb; + const char *client_ip = ats_ip_ntop(this->get_proxy_session()->get_remote_addr(), ipb, sizeof(ipb)); + SiteThrottledWarning("HTTP/2 session error client_ip=%s session_id=%" PRId64 + " closing a connection, because its stream error rate (%f) exceeded the threshold (%f)", + client_ip, this->get_connection_id(), this->connection_state.get_stream_error_rate(), + Http2::stream_error_rate_threshold); + err = Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM; + } + + // Return if there was an error + if (err > Http2ErrorCode::HTTP2_ERROR_NO_ERROR || do_start_frame_read(err) < 0) { + // send an error if specified. Otherwise, just go away + if (err > Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { + if (!this->connection_state.is_state_closed()) { + this->connection_state.send_goaway_frame(this->connection_state.get_latest_stream_id_in(), err); + this->set_half_close_local_flag(true); + } + } + return 0; + } + + // If there is no more data to finish the frame, set up the event handler and reenable + if (this->_read_buffer_reader->read_avail() < this->current_hdr.length) { + HTTP2_SET_SESSION_HANDLER(&Http2CommonSession::state_complete_frame_read); + break; + } + do_complete_frame_read(); + + if (this->_should_do_something_else()) { + if (this->_reenable_event == nullptr) { + vio->disable(); + this->_reenable_event = this->get_mutex()->thread_holding->schedule_in(this->get_proxy_session(), HRTIME_MSECONDS(1), + HTTP2_SESSION_EVENT_REENABLE, vio); + return 0; + } + } + } + + // If the client hasn't shut us down, reenable + if (!this->get_proxy_session()->is_peer_closed()) { + vio->reenable(); + } + return 0; +} + +bool +Http2CommonSession::_should_do_something_else() +{ + // Do something else every 128 incoming frames if connection state isn't closed + return (this->_n_frame_read & 0x7F) == 0 && !connection_state.is_state_closed(); +} + +int64_t +Http2CommonSession::write_avail() +{ + return this->write_buffer->write_avail(); +} + +void +Http2CommonSession::write_reenable() +{ + write_vio->reenable(); +} + +void +Http2CommonSession::add_url_to_pushed_table(const char *url, int url_len) +{ + // Delay std::unordered_set allocation until when it used + if (_h2_pushed_urls == nullptr) { + this->_h2_pushed_urls = new std::unordered_set(); + this->_h2_pushed_urls->reserve(Http2::push_diary_size); + } + + if (_h2_pushed_urls->size() < Http2::push_diary_size) { + _h2_pushed_urls->emplace(url); + } +} diff --git a/proxy/http2/Http2CommonSession.h b/proxy/http2/Http2CommonSession.h new file mode 100644 index 00000000000..0961054fc21 --- /dev/null +++ b/proxy/http2/Http2CommonSession.h @@ -0,0 +1,226 @@ +/** @file + + Http2CommonSession.h + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include "HTTP2.h" +#include "ProxySession.h" +#include "Http2ConnectionState.h" +#include "Http2Frame.h" + +// Name Edata Description +// HTTP2_SESSION_EVENT_INIT Http2CommonSession * HTTP/2 session is born +// HTTP2_SESSION_EVENT_FINI Http2CommonSession * HTTP/2 session is ended +// HTTP2_SESSION_EVENT_RECV Http2Frame * Received a frame +// HTTP2_SESSION_EVENT_XMIT Http2Frame * Send this frame + +#define HTTP2_SESSION_EVENT_INIT (HTTP2_SESSION_EVENTS_START + 1) +#define HTTP2_SESSION_EVENT_FINI (HTTP2_SESSION_EVENTS_START + 2) +#define HTTP2_SESSION_EVENT_RECV (HTTP2_SESSION_EVENTS_START + 3) +#define HTTP2_SESSION_EVENT_XMIT (HTTP2_SESSION_EVENTS_START + 4) +#define HTTP2_SESSION_EVENT_SHUTDOWN_INIT (HTTP2_SESSION_EVENTS_START + 5) +#define HTTP2_SESSION_EVENT_SHUTDOWN_CONT (HTTP2_SESSION_EVENTS_START + 6) +#define HTTP2_SESSION_EVENT_REENABLE (HTTP2_SESSION_EVENTS_START + 7) + +enum class Http2SessionCod : int { + NOT_PROVIDED, + HIGH_ERROR_RATE, +}; + +enum class Http2SsnMilestone { + OPEN = 0, + CLOSE, + LAST_ENTRY, +}; + +size_t const HTTP2_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; + +/** + @startuml + title HTTP/2 Session Handler - state of reading HTTP/2 frame + hide empty description + + [*] --> state_read_connection_preface : start() + state_read_connection_preface --> state_start_frame_read : receive connection preface + state_start_frame_read --> state_start_frame_read : do_complete_frame_read() + state_start_frame_read --> state_complete_frame_read : reading HTTP/2 frame is halfway but no data in the buffer + state_complete_frame_read --> state_start_frame_read : do_complete_frame_read() + + @enduml + */ +class Http2CommonSession +{ +public: + using SessionHandler = int (Http2CommonSession::*)(int, void *); + + ///////////////////// + // Methods + + bool common_free(ProxySession *ssn); + void write_reenable(); + int64_t xmit(const Http2TxFrame &frame, bool flush = true); + void flush(); + + int64_t get_connection_id(); + Ptr &get_mutex(); + NetVConnection *get_netvc(); + void do_clear_session_active(); + + //////////////////// + // Accessors + void set_dying_event(int event); + int get_dying_event() const; + bool ready_to_free() const; + bool is_recursing() const; + void set_half_close_local_flag(bool flag); + bool get_half_close_local_flag() const; + bool is_url_pushed(const char *url, int url_len); + void add_url_to_pushed_table(const char *url, int url_len); + + // Record history from Http2ConnectionState + void remember(const SourceLocation &location, int event, int reentrant = NO_REENTRANT); + + int64_t write_avail(); + + virtual ProxySession *get_proxy_session() = 0; + + /////////////////// + // Variables + Http2ConnectionState connection_state; + +protected: + // SessionHandler(s) - state of reading frame + int state_read_connection_preface(int, void *); + int state_start_frame_read(int, void *); + int state_complete_frame_read(int, void *); + + // state_start_frame_read and state_complete_frame_read are set up as session event handler. + // Both feed into do_process_frame_read which may iterate if there are multiple frames ready on the wire + int do_process_frame_read(int event, VIO *vio, bool inside_frame); + int do_start_frame_read(Http2ErrorCode &ret_error); + int do_complete_frame_read(); + + bool _should_do_something_else(); + + //////// + // Variables + SessionHandler session_handler = nullptr; + + MIOBuffer *read_buffer = nullptr; + IOBufferReader *_read_buffer_reader = nullptr; + + VIO *write_vio = nullptr; + MIOBuffer *write_buffer = nullptr; + IOBufferReader *_write_buffer_reader = nullptr; + + Http2FrameHeader current_hdr = {0, 0, 0, 0}; + uint32_t _write_size_threshold = 0; + uint32_t _write_time_threshold = 100; + ink_hrtime _write_buffer_last_flush = 0; + + History _history; + Milestones(Http2SsnMilestone::LAST_ENTRY)> _milestones; + + int dying_event = 0; + bool kill_me = false; + Http2SessionCod cause_of_death = Http2SessionCod::NOT_PROVIDED; + bool half_close_local = false; + int recursion = 0; + + std::unordered_set *_h2_pushed_urls = nullptr; + + Event *_reenable_event = nullptr; + int _n_frame_read = 0; + + uint32_t _pending_sending_data_size = 0; + + int64_t read_from_early_data = 0; + bool cur_frame_from_early_data = false; +}; + +/////////////////////////////////////////////// +// INLINE + +inline bool +Http2CommonSession::ready_to_free() const +{ + return kill_me; +} + +inline void +Http2CommonSession::set_dying_event(int event) +{ + dying_event = event; +} + +inline int +Http2CommonSession::get_dying_event() const +{ + return dying_event; +} + +inline bool +Http2CommonSession::is_recursing() const +{ + return recursion > 0; +} + +inline bool +Http2CommonSession::get_half_close_local_flag() const +{ + return half_close_local; +} + +inline bool +Http2CommonSession::is_url_pushed(const char *url, int url_len) +{ + if (_h2_pushed_urls == nullptr) { + return false; + } + + return _h2_pushed_urls->find(url) != _h2_pushed_urls->end(); +} + +inline int64_t +Http2CommonSession::get_connection_id() +{ + return get_proxy_session()->connection_id(); +} + +inline Ptr & +Http2CommonSession::get_mutex() +{ + return get_proxy_session()->mutex; +} + +inline NetVConnection * +Http2CommonSession::get_netvc() +{ + return get_proxy_session()->get_netvc(); +} + +inline void +Http2CommonSession::do_clear_session_active() +{ + get_proxy_session()->clear_session_active(); +} diff --git a/proxy/http2/Http2CommonSessionInternal.h b/proxy/http2/Http2CommonSessionInternal.h new file mode 100644 index 00000000000..ffbc9fc6ca3 --- /dev/null +++ b/proxy/http2/Http2CommonSessionInternal.h @@ -0,0 +1,43 @@ +/** @file + + Http2CommonSessionInternal. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +#include "Http2CommonSession.h" + +#define REMEMBER(e, r) \ + { \ + this->remember(MakeSourceLocation(), e, r); \ + } + +#define STATE_ENTER(state_name, event) \ + do { \ + REMEMBER(event, this->recursion) \ + SsnDebug(this, "http2_cs", "[%" PRId64 "] [%s, %s]", this->connection_id(), #state_name, \ + HttpDebugNames::get_event_name(event)); \ + } while (0) + +#define Http2SsnDebug(fmt, ...) SsnDebug(this, "http2_cs", "[%" PRId64 "] " fmt, this->connection_id(), ##__VA_ARGS__) + +#define HTTP2_SET_SESSION_HANDLER(handler) \ + do { \ + REMEMBER(NO_EVENT, this->recursion); \ + this->session_handler = (handler); \ + } while (0) diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index f0a5b810fbf..10a199a7b5a 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -35,18 +35,19 @@ #include #include -#define REMEMBER(e, r) \ - { \ - if (this->ua_session) { \ - this->ua_session->remember(MakeSourceLocation(), e, r); \ - } \ +#define REMEMBER(e, r) \ + { \ + if (this->session) { \ + this->session->remember(MakeSourceLocation(), e, r); \ + } \ } -#define Http2ConDebug(ua_session, fmt, ...) \ - SsnDebug(ua_session, "http2_con", "[%" PRId64 "] " fmt, ua_session->connection_id(), ##__VA_ARGS__); +#define Http2ConDebug(session, fmt, ...) \ + SsnDebug(session->get_proxy_session(), "http2_con", "[%" PRId64 "] " fmt, session->get_connection_id(), ##__VA_ARGS__); -#define Http2StreamDebug(ua_session, stream_id, fmt, ...) \ - SsnDebug(ua_session, "http2_con", "[%" PRId64 "] [%u] " fmt, ua_session->connection_id(), stream_id, ##__VA_ARGS__); +#define Http2StreamDebug(session, stream_id, fmt, ...) \ + SsnDebug(session->get_proxy_session(), "http2_con", "[%" PRId64 "] [%u] " fmt, session->get_connection_id(), stream_id, \ + ##__VA_ARGS__); using http2_frame_dispatch = Http2Error (*)(Http2ConnectionState &, const Http2Frame &); @@ -86,10 +87,10 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) uint8_t pad_length = 0; const uint32_t payload_length = frame.header().length; - Http2StreamDebug(cstate.ua_session, id, "Received DATA frame"); + Http2StreamDebug(cstate.session, id, "Received DATA frame"); if (cstate.get_zombie_event()) { - Warning("Data frame for zombied session %" PRId64, cstate.ua_session->connection_id()); + Warning("Data frame for zombied session %" PRId64, cstate.session->get_connection_id()); } // If a DATA frame is received whose stream identifier field is 0x0, the @@ -172,8 +173,8 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) if (is_debug_tag_set("http2_con")) { uint32_t rwnd = cstate.server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE); - Http2StreamDebug(cstate.ua_session, id, "Received DATA frame: rwnd con=%zd/%" PRId32 " stream=%zd/%" PRId32, - cstate.server_rwnd(), rwnd, stream->server_rwnd(), rwnd); + Http2StreamDebug(cstate.session, id, "Received DATA frame: rwnd con=%zd/%" PRId32 " stream=%zd/%" PRId32, cstate.server_rwnd(), + rwnd, stream->server_rwnd(), rwnd); } const uint32_t unpadded_length = payload_length - pad_length; @@ -230,7 +231,7 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) const Http2StreamId stream_id = frame.header().streamid; const uint32_t payload_length = frame.header().length; - Http2StreamDebug(cstate.ua_session, stream_id, "Received HEADERS frame"); + Http2StreamDebug(cstate.session, stream_id, "Received HEADERS frame"); if (!http2_is_client_streamid(stream_id)) { return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR, @@ -326,7 +327,7 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) stream->priority_node = node; node->t = stream; } else { - Http2StreamDebug(cstate.ua_session, stream_id, "HEADER PRIORITY - dep: %d, weight: %d, excl: %d, tree size: %d", + Http2StreamDebug(cstate.session, stream_id, "HEADER PRIORITY - dep: %d, weight: %d, excl: %d, tree size: %d", params.priority.stream_dependency, params.priority.weight, params.priority.exclusive_flag, cstate.dependency_tree->size()); @@ -399,7 +400,7 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) } else { // NOTE: Expect CONTINUATION Frame. Do NOT change state of stream or decode // Header Blocks. - Http2StreamDebug(cstate.ua_session, stream_id, "No END_HEADERS flag, expecting CONTINUATION frame"); + Http2StreamDebug(cstate.session, stream_id, "No END_HEADERS flag, expecting CONTINUATION frame"); cstate.set_continued_stream_id(stream_id); } @@ -416,10 +417,10 @@ rcv_priority_frame(Http2ConnectionState &cstate, const Http2Frame &frame) const Http2StreamId stream_id = frame.header().streamid; const uint32_t payload_length = frame.header().length; - Http2StreamDebug(cstate.ua_session, stream_id, "Received PRIORITY frame"); + Http2StreamDebug(cstate.session, stream_id, "Received PRIORITY frame"); if (cstate.get_zombie_event()) { - Warning("Priority frame for zombied session %" PRId64, cstate.ua_session->connection_id()); + Warning("Priority frame for zombied session %" PRId64, cstate.session->get_connection_id()); } // If a PRIORITY frame is received with a stream identifier of 0x0, the @@ -461,26 +462,25 @@ rcv_priority_frame(Http2ConnectionState &cstate, const Http2Frame &frame) if (Http2::max_priority_frames_per_minute != 0 && cstate.get_received_priority_frame_count() > Http2::max_priority_frames_per_minute) { HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED, this_ethread()); - Http2StreamDebug(cstate.ua_session, stream_id, - "Observed too frequent priority changes: %u priority changes within a last minute", + Http2StreamDebug(cstate.session, stream_id, "Observed too frequent priority changes: %u priority changes within a last minute", cstate.get_received_priority_frame_count()); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM, "recv priority too frequent priority changes"); } - Http2StreamDebug(cstate.ua_session, stream_id, "PRIORITY - dep: %d, weight: %d, excl: %d, tree size: %d", - priority.stream_dependency, priority.weight, priority.exclusive_flag, cstate.dependency_tree->size()); + Http2StreamDebug(cstate.session, stream_id, "PRIORITY - dep: %d, weight: %d, excl: %d, tree size: %d", priority.stream_dependency, + priority.weight, priority.exclusive_flag, cstate.dependency_tree->size()); Http2DependencyTree::Node *node = cstate.dependency_tree->find(stream_id); if (node != nullptr) { // [RFC 7540] 5.3.3 Reprioritization - Http2StreamDebug(cstate.ua_session, stream_id, "Reprioritize"); + Http2StreamDebug(cstate.session, stream_id, "Reprioritize"); cstate.dependency_tree->reprioritize(node, priority.stream_dependency, priority.exclusive_flag); if (is_debug_tag_set("http2_priority")) { std::stringstream output; cstate.dependency_tree->dump_tree(output); - Debug("http2_priority", "[%" PRId64 "] reprioritize %s", cstate.ua_session->connection_id(), output.str().c_str()); + Debug("http2_priority", "[%" PRId64 "] reprioritize %s", cstate.session->get_connection_id(), output.str().c_str()); } } else { // PRIORITY frame is received before HEADERS frame. @@ -503,7 +503,7 @@ rcv_rst_stream_frame(Http2ConnectionState &cstate, const Http2Frame &frame) char *end; const Http2StreamId stream_id = frame.header().streamid; - Http2StreamDebug(cstate.ua_session, frame.header().streamid, "Received RST_STREAM frame"); + Http2StreamDebug(cstate.session, frame.header().streamid, "Received RST_STREAM frame"); // RST_STREAM frames MUST be associated with a stream. If a RST_STREAM // frame is received with a stream identifier of 0x0, the recipient MUST @@ -546,7 +546,7 @@ rcv_rst_stream_frame(Http2ConnectionState &cstate, const Http2Frame &frame) } if (stream != nullptr) { - Http2StreamDebug(cstate.ua_session, stream_id, "RST_STREAM: Error Code: %u", rst_stream.error_code); + Http2StreamDebug(cstate.session, stream_id, "RST_STREAM: Error Code: %u", rst_stream.error_code); stream->set_rx_error_code({ProxyErrorClass::TXN, static_cast(rst_stream.error_code)}); stream->initiating_close(); @@ -563,10 +563,10 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) unsigned nbytes = 0; const Http2StreamId stream_id = frame.header().streamid; - Http2StreamDebug(cstate.ua_session, stream_id, "Received SETTINGS frame"); + Http2StreamDebug(cstate.session, stream_id, "Received SETTINGS frame"); if (cstate.get_zombie_event()) { - Warning("Setting frame for zombied session %" PRId64, cstate.ua_session->connection_id()); + Warning("Setting frame for zombied session %" PRId64, cstate.session->get_connection_id()); } // Update SETTIGNS frame count per minute @@ -574,7 +574,7 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // Close this connection if its SETTINGS frame count exceeds a limit if (cstate.get_received_settings_frame_count() > Http2::max_settings_frames_per_minute) { HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_SETTINGS_FRAMES_PER_MINUTE_EXCEEDED, this_ethread()); - Http2StreamDebug(cstate.ua_session, stream_id, "Observed too frequent SETTINGS frames: %u frames within a last minute", + Http2StreamDebug(cstate.session, stream_id, "Observed too frequent SETTINGS frames: %u frames within a last minute", cstate.get_received_settings_frame_count()); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM, "recv settings too frequent SETTINGS frames"); @@ -613,7 +613,7 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) while (nbytes < frame.header().length) { if (n_settings >= Http2::max_settings_per_frame) { HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_SETTINGS_PER_FRAME_EXCEEDED, this_ethread()); - Http2StreamDebug(cstate.ua_session, stream_id, "Observed too many settings in a frame"); + Http2StreamDebug(cstate.session, stream_id, "Observed too many settings in a frame"); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM, "recv settings too many settings in a frame"); } @@ -635,7 +635,7 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) } } - Http2StreamDebug(cstate.ua_session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(param.id), param.value); + Http2StreamDebug(cstate.session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(param.id), param.value); // [RFC 7540] 6.9.2. When the value of SETTINGS_INITIAL_WINDOW_SIZE // changes, a receiver MUST adjust the size of all stream flow control @@ -654,7 +654,7 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // Close this connection if its settings count received exceeds a limit if (cstate.get_received_settings_count() > Http2::max_settings_per_minute) { HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_SETTINGS_PER_MINUTE_EXCEEDED, this_ethread()); - Http2StreamDebug(cstate.ua_session, stream_id, "Observed too frequent setting changes: %u settings within a last minute", + Http2StreamDebug(cstate.session, stream_id, "Observed too frequent setting changes: %u settings within a last minute", cstate.get_received_settings_count()); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM, "recv settings too frequent setting changes"); @@ -663,7 +663,7 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // [RFC 7540] 6.5. Once all values have been applied, the recipient MUST // immediately emit a SETTINGS frame with the ACK flag set. Http2SettingsFrame ack_frame(0, HTTP2_FLAGS_SETTINGS_ACK); - cstate.ua_session->xmit(ack_frame); + cstate.session->xmit(ack_frame); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); } @@ -671,7 +671,7 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) static Http2Error rcv_push_promise_frame(Http2ConnectionState &cstate, const Http2Frame &frame) { - Http2StreamDebug(cstate.ua_session, frame.header().streamid, "Received PUSH_PROMISE frame"); + Http2StreamDebug(cstate.session, frame.header().streamid, "Received PUSH_PROMISE frame"); // [RFC 7540] 8.2. A client cannot push. Thus, servers MUST treat the receipt of a // PUSH_PROMISE frame as a connection error of type PROTOCOL_ERROR. @@ -685,7 +685,7 @@ rcv_ping_frame(Http2ConnectionState &cstate, const Http2Frame &frame) uint8_t opaque_data[HTTP2_PING_LEN]; const Http2StreamId stream_id = frame.header().streamid; - Http2StreamDebug(cstate.ua_session, stream_id, "Received PING frame"); + Http2StreamDebug(cstate.session, stream_id, "Received PING frame"); cstate.schedule_zombie_event(); @@ -708,7 +708,7 @@ rcv_ping_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // Close this connection if its ping count received exceeds a limit if (cstate.get_received_ping_frame_count() > Http2::max_ping_frames_per_minute) { HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED, this_ethread()); - Http2StreamDebug(cstate.ua_session, stream_id, "Observed too frequent PING frames: %u PING frames within a last minute", + Http2StreamDebug(cstate.session, stream_id, "Observed too frequent PING frames: %u PING frames within a last minute", cstate.get_received_ping_frame_count()); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM, "recv ping too frequent PING frame"); @@ -735,7 +735,7 @@ rcv_goaway_frame(Http2ConnectionState &cstate, const Http2Frame &frame) unsigned nbytes = 0; const Http2StreamId stream_id = frame.header().streamid; - Http2StreamDebug(cstate.ua_session, stream_id, "Received GOAWAY frame"); + Http2StreamDebug(cstate.session, stream_id, "Received GOAWAY frame"); // An endpoint MUST treat a GOAWAY frame with a stream identifier other // than 0x0 as a connection error of type PROTOCOL_ERROR. @@ -753,11 +753,11 @@ rcv_goaway_frame(Http2ConnectionState &cstate, const Http2Frame &frame) } } - Http2StreamDebug(cstate.ua_session, stream_id, "GOAWAY: last stream id=%d, error code=%d", goaway.last_streamid, + Http2StreamDebug(cstate.session, stream_id, "GOAWAY: last stream id=%d, error code=%d", goaway.last_streamid, static_cast(goaway.error_code)); cstate.rx_error_code = {ProxyErrorClass::SSN, static_cast(goaway.error_code)}; - cstate.ua_session->do_io_close(); + cstate.session->get_proxy_session()->do_io_close(); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); } @@ -772,7 +772,7 @@ rcv_window_update_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // A WINDOW_UPDATE frame with a length other than 4 octets MUST be // treated as a connection error of type FRAME_SIZE_ERROR. if (frame.header().length != HTTP2_WINDOW_UPDATE_LEN) { - Http2StreamDebug(cstate.ua_session, stream_id, "Received WINDOW_UPDATE frame - length incorrect"); + Http2StreamDebug(cstate.session, stream_id, "Received WINDOW_UPDATE frame - length incorrect"); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_FRAME_SIZE_ERROR, "window update bad length"); } @@ -794,7 +794,7 @@ rcv_window_update_frame(Http2ConnectionState &cstate, const Http2Frame &frame) if (stream_id == 0) { // Connection level window update - Http2StreamDebug(cstate.ua_session, stream_id, "Received WINDOW_UPDATE frame - updated to: %zd delta: %u", + Http2StreamDebug(cstate.session, stream_id, "Received WINDOW_UPDATE frame - updated to: %zd delta: %u", (cstate.client_rwnd() + size), size); // A sender MUST NOT allow a flow-control window to exceed 2^31-1 @@ -828,7 +828,7 @@ rcv_window_update_frame(Http2ConnectionState &cstate, const Http2Frame &frame) } } - Http2StreamDebug(cstate.ua_session, stream_id, "Received WINDOW_UPDATE frame - updated to: %zd delta: %u", + Http2StreamDebug(cstate.session, stream_id, "Received WINDOW_UPDATE frame - updated to: %zd delta: %u", (stream->client_rwnd() + size), size); // A sender MUST NOT allow a flow-control window to exceed 2^31-1 @@ -871,7 +871,7 @@ rcv_continuation_frame(Http2ConnectionState &cstate, const Http2Frame &frame) const Http2StreamId stream_id = frame.header().streamid; const uint32_t payload_length = frame.header().length; - Http2StreamDebug(cstate.ua_session, stream_id, "Received CONTINUATION frame"); + Http2StreamDebug(cstate.session, stream_id, "Received CONTINUATION frame"); if (!http2_is_client_streamid(stream_id)) { return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR, @@ -954,7 +954,7 @@ rcv_continuation_frame(Http2ConnectionState &cstate, const Http2Frame &frame) stream->send_request(cstate); } else { // NOTE: Expect another CONTINUATION Frame. Do nothing. - Http2StreamDebug(cstate.ua_session, stream_id, "No END_HEADERS flag, expecting CONTINUATION frame"); + Http2StreamDebug(cstate.session, stream_id, "No END_HEADERS flag, expecting CONTINUATION frame"); } return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); @@ -1040,9 +1040,9 @@ Http2ConnectionState::Http2ConnectionState() : stream_list() } void -Http2ConnectionState::init(Http2ClientSession *ssn) +Http2ConnectionState::init(Http2CommonSession *ssn) { - ua_session = ssn; + session = ssn; this->_server_rwnd = Http2::initial_window_size; local_hpack_handle = new HpackHandle(HTTP2_HEADER_TABLE_SIZE); @@ -1103,8 +1103,8 @@ Http2ConnectionState::destroy() delete remote_hpack_handle; remote_hpack_handle = nullptr; delete dependency_tree; - dependency_tree = nullptr; - this->ua_session = nullptr; + dependency_tree = nullptr; + this->session = nullptr; if (fini_event) { fini_event->cancel(); @@ -1126,7 +1126,7 @@ Http2ConnectionState::rcv_frame(const Http2Frame *frame) // [RFC 7540] 5.5. Extending HTTP/2 // Implementations MUST discard frames that have unknown or unsupported types. if (frame->header().type >= HTTP2_FRAME_TYPE_MAX) { - Http2StreamDebug(ua_session, stream_id, "Discard a frame which has unknown type, type=%x", frame->header().type); + Http2StreamDebug(session, stream_id, "Discard a frame which has unknown type, type=%x", frame->header().type); return; } @@ -1144,8 +1144,7 @@ Http2ConnectionState::rcv_frame(const Http2Frame *frame) if (frame->is_from_early_data() && (frame->header().type == HTTP2_FRAME_TYPE_DATA || frame->header().type == HTTP2_FRAME_TYPE_RST_STREAM || frame->header().type == HTTP2_FRAME_TYPE_PUSH_PROMISE || frame->header().type == HTTP2_FRAME_TYPE_GOAWAY)) { - Http2StreamDebug(ua_session, stream_id, "Discard a frame which is received from early data and has type=%x", - frame->header().type); + Http2StreamDebug(session, stream_id, "Discard a frame which is received from early data and has type=%x", frame->header().type); return; } @@ -1157,14 +1156,14 @@ Http2ConnectionState::rcv_frame(const Http2Frame *frame) if (error.cls != Http2ErrorClass::HTTP2_ERROR_CLASS_NONE) { ip_port_text_buffer ipb; - const char *client_ip = ats_ip_ntop(ua_session->get_remote_addr(), ipb, sizeof(ipb)); + const char *client_ip = ats_ip_ntop(session->get_proxy_session()->get_remote_addr(), ipb, sizeof(ipb)); if (error.cls == Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION) { if (error.msg) { Error("HTTP/2 connection error code=0x%02x client_ip=%s session_id=%" PRId64 " stream_id=%u %s", - static_cast(error.code), client_ip, ua_session->connection_id(), stream_id, error.msg); + static_cast(error.code), client_ip, session->get_connection_id(), stream_id, error.msg); } this->send_goaway_frame(this->latest_streamid_in, error.code); - this->ua_session->set_half_close_local_flag(true); + this->session->set_half_close_local_flag(true); if (fini_event == nullptr) { fini_event = this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_FINI); } @@ -1174,7 +1173,7 @@ Http2ConnectionState::rcv_frame(const Http2Frame *frame) } else if (error.cls == Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM) { if (error.msg) { Error("HTTP/2 stream error code=0x%02x client_ip=%s session_id=%" PRId64 " stream_id=%u %s", static_cast(error.code), - client_ip, ua_session->connection_id(), stream_id, error.msg); + client_ip, session->get_connection_id(), stream_id, error.msg); } this->send_rst_stream_frame(stream_id, error.code); } @@ -1238,22 +1237,22 @@ Http2ConnectionState::main_event_handler(int event, void *edata) } send_goaway_frame(latest_streamid_in, shutdown_reason); // Stop creating new streams - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->set_half_close_local_flag(true); + SCOPED_MUTEX_LOCK(lock, this->session->get_mutex(), this_ethread()); + this->session->set_half_close_local_flag(true); } break; default: - Http2ConDebug(ua_session, "unexpected event=%d edata=%p", event, edata); + Http2ConDebug(session, "unexpected event=%d edata=%p", event, edata); ink_release_assert(0); break; } --recursion; - if (recursion == 0 && ua_session && !ua_session->is_recursing()) { - if (this->ua_session->ready_to_free()) { - MUTEX_TRY_LOCK(lock, this->ua_session->mutex, this_ethread()); + if (recursion == 0 && session && !session->is_recursing()) { + if (this->session->ready_to_free()) { + MUTEX_TRY_LOCK(lock, this->session->get_mutex(), this_ethread()); if (lock.is_locked()) { - this->ua_session->free(); + this->session->get_proxy_session()->free(); // After the free, the Http2ConnectionState object is also freed. // The Http2ConnectionState object is allocated within the Http2ClientSession object } @@ -1283,16 +1282,16 @@ Http2Stream * Http2ConnectionState::create_stream(Http2StreamId new_id, Http2Error &error) { // first check if we've hit the active connection limit - if (!ua_session->get_netvc()->add_to_active_queue()) { + if (!session->get_netvc()->add_to_active_queue()) { error = Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_NO_ERROR, "refused to create new stream, maxed out active connections"); return nullptr; } // In half_close state, TS doesn't create new stream. Because GOAWAY frame is sent to client - if (ua_session->get_half_close_local_flag()) { + if (session->get_half_close_local_flag()) { error = Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_REFUSED_STREAM, - "refused to create new stream, because ua_session is in half_close state"); + "refused to create new stream, because session is in half_close state"); return nullptr; } @@ -1335,7 +1334,7 @@ Http2ConnectionState::create_stream(Http2StreamId new_id, Http2Error &error) } } - Http2Stream *new_stream = THREAD_ALLOC_INIT(http2StreamAllocator, this_ethread(), ua_session, new_id, + Http2Stream *new_stream = THREAD_ALLOC_INIT(http2StreamAllocator, this_ethread(), session->get_proxy_session(), new_id, client_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); ink_assert(nullptr != new_stream); @@ -1463,9 +1462,9 @@ Http2ConnectionState::cleanup_streams() } if (!is_state_closed()) { - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); + SCOPED_MUTEX_LOCK(lock, this->session->get_mutex(), this_ethread()); - UnixNetVConnection *vc = static_cast(ua_session->get_netvc()); + UnixNetVConnection *vc = static_cast(session->get_netvc()); if (vc && vc->active_timeout_in == 0) { vc->add_to_keep_alive_queue(); } @@ -1483,7 +1482,7 @@ Http2ConnectionState::delete_stream(Http2Stream *stream) return false; } - Http2StreamDebug(ua_session, stream->get_id(), "Delete stream"); + Http2StreamDebug(session, stream->get_id(), "Delete stream"); REMEMBER(NO_EVENT, this->recursion); if (Http2::stream_priority_enabled) { @@ -1495,7 +1494,7 @@ Http2ConnectionState::delete_stream(Http2Stream *stream) if (is_debug_tag_set("http2_priority")) { std::stringstream output; dependency_tree->dump_tree(output); - Debug("http2_priority", "[%" PRId64 "] %s", ua_session->connection_id(), output.str().c_str()); + Debug("http2_priority", "[%" PRId64 "] %s", session->get_connection_id(), output.str().c_str()); } dependency_tree->remove(node); // ink_release_assert(dependency_tree->find(stream->get_id()) == nullptr); @@ -1529,28 +1528,28 @@ Http2ConnectionState::release_stream() REMEMBER(NO_EVENT, this->recursion) SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); - if (this->ua_session) { - ink_assert(this->mutex == ua_session->mutex); + if (this->session) { + ink_assert(this->mutex == session->get_mutex()); if (total_client_streams_count == 0) { if (fini_received) { - ua_session->clear_session_active(); + session->do_clear_session_active(); // We were shutting down, go ahead and terminate the session // this is a member of Http2ConnectionState and will be freed - // when ua_session is destroyed - ua_session->destroy(); + // when session is destroyed + session->get_proxy_session()->destroy(); // Can't do this because we just destroyed right here ^, // or we can use a local variable to do it. - // ua_session = nullptr; - } else if (ua_session->is_active()) { - // If the number of clients is 0, HTTP2_SESSION_EVENT_FINI is not received or sent, and ua_session is active, + // session = nullptr; + } else if (session->get_proxy_session()->is_active()) { + // If the number of clients is 0, HTTP2_SESSION_EVENT_FINI is not received or sent, and session is active, // then mark the connection as inactive - ua_session->clear_session_active(); - UnixNetVConnection *vc = static_cast(ua_session->get_netvc()); + session->do_clear_session_active(); + UnixNetVConnection *vc = static_cast(session->get_netvc()); if (vc && vc->active_timeout_in == 0) { - // With heavy traffic, ua_session could be destroyed. Do not touch ua_session after this. + // With heavy traffic, session could be destroyed. Do not touch session after this. vc->add_to_keep_alive_queue(); } } else { @@ -1575,7 +1574,7 @@ Http2ConnectionState::update_initial_rwnd(Http2WindowSize new_size) void Http2ConnectionState::schedule_stream(Http2Stream *stream) { - Http2StreamDebug(ua_session, stream->get_id(), "Scheduled"); + Http2StreamDebug(session, stream->get_id(), "Scheduled"); Http2DependencyTree::Node *node = stream->priority_node; ink_release_assert(node != nullptr); @@ -1603,7 +1602,7 @@ Http2ConnectionState::send_data_frames_depends_on_priority() Http2Stream *stream = static_cast(node->t); ink_release_assert(stream != nullptr); - Http2StreamDebug(ua_session, stream->get_id(), "top node, point=%d", node->point); + Http2StreamDebug(session, stream->get_id(), "top node, point=%d", node->point); size_t len = 0; Http2SendDataFrameResult result = send_a_data_frame(stream, len); @@ -1650,12 +1649,12 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread()); if (!resp_reader) { - Http2StreamDebug(this->ua_session, stream->get_id(), "couldn't get data reader"); + Http2StreamDebug(this->session, stream->get_id(), "couldn't get data reader"); return Http2SendDataFrameResult::ERROR; } - if (this->ua_session->write_avail() == 0) { - Http2StreamDebug(this->ua_session, stream->get_id(), "Not write avail"); + if (this->session->write_avail() == 0) { + Http2StreamDebug(this->session, stream->get_id(), "Not write avail"); return Http2SendDataFrameResult::NOT_WRITE_AVAIL; } @@ -1663,8 +1662,8 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len if (resp_reader->is_read_avail_more_than(0)) { // We only need to check for window size when there is a payload if (window_size <= 0) { - Http2StreamDebug(this->ua_session, stream->get_id(), "No window"); - this->ua_session->flush(); + Http2StreamDebug(this->session, stream->get_id(), "No window"); + this->session->flush(); return Http2SendDataFrameResult::NO_WINDOW; } @@ -1681,8 +1680,8 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len // If we return here, we never send the END_STREAM in the case of a early terminating OS. // OK if there is no body yet. Otherwise continue on to send a DATA frame and delete the stream if (!stream->is_write_vio_done() && payload_length == 0) { - Http2StreamDebug(this->ua_session, stream->get_id(), "No payload"); - this->ua_session->flush(); + Http2StreamDebug(this->session, stream->get_id(), "No payload"); + this->session->flush(); return Http2SendDataFrameResult::NO_PAYLOAD; } @@ -1695,16 +1694,16 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len stream->decrement_client_rwnd(payload_length); // Create frame - Http2StreamDebug(ua_session, stream->get_id(), "Send a DATA frame - client window con: %5zd stream: %5zd payload: %5zd", + Http2StreamDebug(session, stream->get_id(), "Send a DATA frame - client window con: %5zd stream: %5zd payload: %5zd", _client_rwnd, stream->client_rwnd(), payload_length); Http2DataFrame data(stream->get_id(), flags, resp_reader, payload_length); - this->ua_session->xmit(data, flags & HTTP2_FLAGS_DATA_END_STREAM); + this->session->xmit(data, flags & HTTP2_FLAGS_DATA_END_STREAM); stream->update_sent_count(payload_length); if (flags & HTTP2_FLAGS_DATA_END_STREAM) { - Http2StreamDebug(ua_session, stream->get_id(), "END_STREAM"); + Http2StreamDebug(session, stream->get_id(), "END_STREAM"); stream->send_end_stream = true; // Setting to the same state shouldn't be erroneous stream->change_state(HTTP2_FRAME_TYPE_DATA, flags); @@ -1722,7 +1721,7 @@ Http2ConnectionState::send_data_frames(Http2Stream *stream) // a closed stream. So we return without sending if (stream->get_state() == Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL || stream->get_state() == Http2StreamState::HTTP2_STREAM_STATE_CLOSED) { - Http2StreamDebug(this->ua_session, stream->get_id(), "Shutdown half closed local stream"); + Http2StreamDebug(this->session, stream->get_id(), "Shutdown half closed local stream"); stream->initiating_close(); return; } @@ -1737,7 +1736,7 @@ Http2ConnectionState::send_data_frames(Http2Stream *stream) // TODO its should not be deleted for a several time to handling // RST_STREAM and WINDOW_UPDATE. // See 'closed' state written at [RFC 7540] 5.1. - Http2StreamDebug(this->ua_session, stream->get_id(), "Shutdown stream"); + Http2StreamDebug(this->session, stream->get_id(), "Shutdown stream"); stream->initiating_close(); } } @@ -1752,7 +1751,7 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) int payload_length = 0; uint8_t flags = 0x00; - Http2StreamDebug(ua_session, stream->get_id(), "Send HEADERS frame"); + Http2StreamDebug(session, stream->get_id(), "Send HEADERS frame"); HTTPHdr *resp_hdr = &stream->response_header; http2_convert_header_from_1_1_to_2(resp_hdr); @@ -1774,7 +1773,7 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) flags |= HTTP2_FLAGS_HEADERS_END_HEADERS; if ((resp_hdr->presence(MIME_PRESENCE_CONTENT_LENGTH) && resp_hdr->get_content_length() == 0) || (!resp_hdr->expect_final_response() && stream->is_write_vio_done())) { - Http2StreamDebug(ua_session, stream->get_id(), "END_STREAM"); + Http2StreamDebug(session, stream->get_id(), "END_STREAM"); flags |= HTTP2_FLAGS_HEADERS_END_STREAM; stream->send_end_stream = true; } @@ -1786,7 +1785,7 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) // Change stream state if (!stream->change_state(HTTP2_FRAME_TYPE_HEADERS, flags)) { this->send_goaway_frame(this->latest_streamid_in, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR); - this->ua_session->set_half_close_local_flag(true); + this->session->set_half_close_local_flag(true); if (fini_event == nullptr) { fini_event = this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_FINI); } @@ -1795,13 +1794,13 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) } Http2HeadersFrame headers(stream->get_id(), flags, buf, payload_length); - this->ua_session->xmit(headers); + this->session->xmit(headers); uint64_t sent = payload_length; // Send CONTINUATION frames flags = 0; while (sent < header_blocks_size) { - Http2StreamDebug(ua_session, stream->get_id(), "Send CONTINUATION frame"); + Http2StreamDebug(session, stream->get_id(), "Send CONTINUATION frame"); payload_length = std::min(static_cast(BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION])), static_cast(header_blocks_size - sent)); if (sent + payload_length == header_blocks_size) { @@ -1810,7 +1809,7 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) stream->change_state(HTTP2_FRAME_TYPE_CONTINUATION, flags); Http2ContinuationFrame continuation_frame(stream->get_id(), flags, buf + sent, payload_length); - this->ua_session->xmit(continuation_frame); + this->session->xmit(continuation_frame); sent += payload_length; } } @@ -1826,7 +1825,7 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con return false; } - Http2StreamDebug(ua_session, stream->get_id(), "Send PUSH_PROMISE frame"); + Http2StreamDebug(session, stream->get_id(), "Send PUSH_PROMISE frame"); HTTPHdr hdr; ts::PostScript hdr_defer([&]() -> void { hdr.destroy(); }); @@ -1874,13 +1873,13 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con push_promise.promised_streamid = id; Http2PushPromiseFrame push_promise_frame(stream->get_id(), flags, push_promise, buf, payload_length); - this->ua_session->xmit(push_promise_frame); + this->session->xmit(push_promise_frame); uint64_t sent = payload_length; // Send CONTINUATION frames flags = 0; while (sent < header_blocks_size) { - Http2StreamDebug(ua_session, stream->get_id(), "Send CONTINUATION frame"); + Http2StreamDebug(session, stream->get_id(), "Send CONTINUATION frame"); payload_length = std::min(static_cast(BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION])), static_cast(header_blocks_size - sent)); if (sent + payload_length == header_blocks_size) { @@ -1888,7 +1887,7 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con } Http2ContinuationFrame continuation(stream->get_id(), flags, buf + sent, payload_length); - this->ua_session->xmit(continuation); + this->session->xmit(continuation); sent += payload_length; } @@ -1904,7 +1903,7 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con if (node != nullptr) { stream->priority_node = node; } else { - Http2StreamDebug(this->ua_session, id, "PRIORITY - dep: %d, weight: %d, excl: %d, tree size: %d", + Http2StreamDebug(this->session, id, "PRIORITY - dep: %d, weight: %d, excl: %d, tree size: %d", HTTP2_PRIORITY_DEFAULT_STREAM_DEPENDENCY, HTTP2_PRIORITY_DEFAULT_WEIGHT, false, this->dependency_tree->size()); @@ -1924,7 +1923,7 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con void Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec) { - Http2StreamDebug(ua_session, id, "Send RST_STREAM frame"); + Http2StreamDebug(session, id, "Send RST_STREAM frame"); if (ec != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_STREAM_ERRORS_COUNT, this_ethread()); @@ -1937,7 +1936,7 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec) stream->set_tx_error_code({ProxyErrorClass::TXN, static_cast(ec)}); if (!stream->change_state(HTTP2_FRAME_TYPE_RST_STREAM, 0)) { this->send_goaway_frame(this->latest_streamid_in, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR); - this->ua_session->set_half_close_local_flag(true); + this->session->set_half_close_local_flag(true); if (fini_event == nullptr) { fini_event = this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_FINI); } @@ -1947,7 +1946,7 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec) } Http2RstStreamFrame rst_stream(id, static_cast(ec)); - this->ua_session->xmit(rst_stream); + this->session->xmit(rst_stream); } void @@ -1955,7 +1954,7 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set { const Http2StreamId stream_id = 0; - Http2StreamDebug(ua_session, stream_id, "Send SETTINGS frame"); + Http2StreamDebug(session, stream_id, "Send SETTINGS frame"); Http2SettingsParameter params[HTTP2_SETTINGS_MAX]; size_t params_size = 0; @@ -1966,7 +1965,7 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set // Send only difference if (settings_value != server_settings.get(id)) { - Http2StreamDebug(ua_session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(id), settings_value); + Http2StreamDebug(session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(id), settings_value); params[params_size++] = {static_cast(id), settings_value}; @@ -1976,16 +1975,16 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set } Http2SettingsFrame settings(stream_id, HTTP2_FRAME_NO_FLAG, params, params_size); - this->ua_session->xmit(settings); + this->session->xmit(settings); } void Http2ConnectionState::send_ping_frame(Http2StreamId id, uint8_t flag, const uint8_t *opaque_data) { - Http2StreamDebug(ua_session, id, "Send PING frame"); + Http2StreamDebug(session, id, "Send PING frame"); Http2PingFrame ping(id, flag, opaque_data); - this->ua_session->xmit(ping); + this->session->xmit(ping); } // As for graceful shutdown, TS should process outstanding stream as long as possible. @@ -1993,9 +1992,9 @@ Http2ConnectionState::send_ping_frame(Http2StreamId id, uint8_t flag, const uint void Http2ConnectionState::send_goaway_frame(Http2StreamId id, Http2ErrorCode ec) { - ink_assert(this->ua_session != nullptr); + ink_assert(this->session != nullptr); - Http2ConDebug(ua_session, "Send GOAWAY frame, last_stream_id: %d", id); + Http2ConDebug(session, "Send GOAWAY frame, last_stream_id: %d", id); if (ec != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CONNECTION_ERRORS_COUNT, this_ethread()); @@ -2008,17 +2007,17 @@ Http2ConnectionState::send_goaway_frame(Http2StreamId id, Http2ErrorCode ec) goaway.error_code = ec; Http2GoawayFrame frame(goaway); - this->ua_session->xmit(frame); + this->session->xmit(frame); } void Http2ConnectionState::send_window_update_frame(Http2StreamId id, uint32_t size) { - Http2StreamDebug(ua_session, id, "Send WINDOW_UPDATE frame: size=%" PRIu32, size); + Http2StreamDebug(session, id, "Send WINDOW_UPDATE frame: size=%" PRIu32, size); // Create WINDOW_UPDATE frame Http2WindowUpdateFrame window_update(id, size); - this->ua_session->xmit(window_update); + this->session->xmit(window_update); } void @@ -2082,7 +2081,7 @@ Http2ConnectionState::_adjust_concurrent_stream() int64_t current_client_streams = 0; RecGetRawStatSum(http2_rsb, HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, ¤t_client_streams); - Http2ConDebug(ua_session, "current client streams: %" PRId64, current_client_streams); + Http2ConDebug(session, "current client streams: %" PRId64, current_client_streams); if (current_client_streams >= Http2::max_active_streams_in) { if (!Http2::throttling) { diff --git a/proxy/http2/Http2ConnectionState.h b/proxy/http2/Http2ConnectionState.h index 3c55bdfaa3b..caf4a957aef 100644 --- a/proxy/http2/Http2ConnectionState.h +++ b/proxy/http2/Http2ConnectionState.h @@ -33,7 +33,7 @@ #include "Http2DependencyTree.h" #include "Http2FrequencyCounter.h" -class Http2ClientSession; +class Http2CommonSession; class Http2Frame; enum class Http2SendDataFrameResult { @@ -80,7 +80,7 @@ class Http2ConnectionState : public Continuation ProxyError rx_error_code; ProxyError tx_error_code; - Http2ClientSession *ua_session = nullptr; + Http2CommonSession *session = nullptr; HpackHandle *local_hpack_handle = nullptr; HpackHandle *remote_hpack_handle = nullptr; DependencyTree *dependency_tree = nullptr; @@ -90,7 +90,7 @@ class Http2ConnectionState : public Continuation Http2ConnectionSettings server_settings; Http2ConnectionSettings client_settings; - void init(Http2ClientSession *ssn); + void init(Http2CommonSession *ssn); void send_connection_preface(); void destroy(); void rcv_frame(const Http2Frame *frame); @@ -299,7 +299,7 @@ Http2ConnectionState::get_shutdown_reason() const inline bool Http2ConnectionState::is_state_closed() const { - return ua_session == nullptr || fini_received; + return session == nullptr || fini_received; } inline bool diff --git a/proxy/http2/Makefile.am b/proxy/http2/Makefile.am index cb12966f612..301d3119c24 100644 --- a/proxy/http2/Makefile.am +++ b/proxy/http2/Makefile.am @@ -42,6 +42,8 @@ libhttp2_a_SOURCES = \ Http2Frame.h \ Http2ClientSession.cc \ Http2ClientSession.h \ + Http2CommonSession.cc \ + Http2CommonSession.h \ Http2ConnectionState.cc \ Http2ConnectionState.h \ Http2DebugNames.cc \