This is an automated email from the ASF dual-hosted git repository.

masaori pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new b46f5d061a Add http2.incomplete_header_timeout_in (#11354)
b46f5d061a is described below

commit b46f5d061af455dc75956346da3308788381b30d
Author: Masaori Koshiba <masa...@apache.org>
AuthorDate: Fri May 24 08:00:11 2024 +0900

    Add http2.incomplete_header_timeout_in (#11354)
---
 doc/admin-guide/files/records.yaml.en.rst |  7 +++++++
 include/proxy/http2/HTTP2.h               |  1 +
 include/proxy/http2/Http2CommonSession.h  |  1 +
 src/proxy/http2/HTTP2.cc                  | 33 +++++++++++++++++--------------
 src/proxy/http2/Http2ConnectionState.cc   | 24 ++++++++++++++++++++++
 src/proxy/http2/Http2Stream.cc            | 29 ++++++++++++++++++++++++++-
 src/records/RecordsConfig.cc              |  2 ++
 7 files changed, 81 insertions(+), 16 deletions(-)

diff --git a/doc/admin-guide/files/records.yaml.en.rst 
b/doc/admin-guide/files/records.yaml.en.rst
index ba94d190b7..00fde245e1 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -4500,6 +4500,13 @@ HTTP/2 Configuration
    Specifies how long |TS| keeps connections to origins open if a
    transaction stalls.
 
+.. ts:cv:: CONFIG proxy.config.http2.incomplete_header_timeout_in INT 10
+   :reloadable:
+   :units: seconds
+
+   Specifies how long |TS| keeps streams to clients open after they start 
sending HTTP headers. If a client doesn't send all
+   headers within this time, the stream and connection will be closed.
+
 .. ts:cv:: CONFIG proxy.config.http2.zombie_debug_timeout_in INT 0
    :reloadable:
 
diff --git a/include/proxy/http2/HTTP2.h b/include/proxy/http2/HTTP2.h
index 5cabf2b2cd..11c60ec35f 100644
--- a/include/proxy/http2/HTTP2.h
+++ b/include/proxy/http2/HTTP2.h
@@ -410,6 +410,7 @@ public:
   static uint32_t               accept_no_activity_timeout;
   static uint32_t               no_activity_timeout_in;
   static uint32_t               active_timeout_in;
+  static uint32_t               incomplete_header_timeout_in;
   static uint32_t               push_diary_size;
   static uint32_t               zombie_timeout_in;
 
diff --git a/include/proxy/http2/Http2CommonSession.h 
b/include/proxy/http2/Http2CommonSession.h
index 5507a3a07f..cb61107fe9 100644
--- a/include/proxy/http2/Http2CommonSession.h
+++ b/include/proxy/http2/Http2CommonSession.h
@@ -47,6 +47,7 @@
 #define HTTP2_SESSION_EVENT_SHUTDOWN_INIT (HTTP2_SESSION_EVENTS_START + 7)
 #define HTTP2_SESSION_EVENT_SHUTDOWN_CONT (HTTP2_SESSION_EVENTS_START + 8)
 #define HTTP2_SESSION_EVENT_REENABLE      (HTTP2_SESSION_EVENTS_START + 9)
+#define HTTP2_SESSION_EVENT_ERROR         (HTTP2_SESSION_EVENTS_START + 10)
 
 enum class Http2SessionCod : int {
   NOT_PROVIDED,
diff --git a/src/proxy/http2/HTTP2.cc b/src/proxy/http2/HTTP2.cc
index 3de6bbab04..646da22050 100644
--- a/src/proxy/http2/HTTP2.cc
+++ b/src/proxy/http2/HTTP2.cc
@@ -459,21 +459,23 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t 
*buf_start, const uint32_
 }
 
 // Initialize this subsystem with librecords configs (for now)
-uint32_t               Http2::max_concurrent_streams_in  = 100;
-uint32_t               Http2::min_concurrent_streams_in  = 10;
-uint32_t               Http2::max_active_streams_in      = 0;
-bool                   Http2::throttling                 = false;
-uint32_t               Http2::stream_priority_enabled    = 0;
-uint32_t               Http2::initial_window_size_in     = 65535;
-Http2FlowControlPolicy Http2::flow_control_policy_in     = 
Http2FlowControlPolicy::STATIC_SESSION_AND_STATIC_STREAM;
-uint32_t               Http2::max_frame_size             = 16384;
-uint32_t               Http2::header_table_size          = 4096;
-uint32_t               Http2::max_header_list_size       = 4294967295;
-uint32_t               Http2::accept_no_activity_timeout = 120;
-uint32_t               Http2::no_activity_timeout_in     = 120;
-uint32_t               Http2::active_timeout_in          = 0;
-uint32_t               Http2::push_diary_size            = 256;
-uint32_t               Http2::zombie_timeout_in          = 0;
+uint32_t               Http2::max_concurrent_streams_in = 100;
+uint32_t               Http2::min_concurrent_streams_in = 10;
+uint32_t               Http2::max_active_streams_in     = 0;
+bool                   Http2::throttling                = false;
+uint32_t               Http2::stream_priority_enabled   = 0;
+uint32_t               Http2::initial_window_size_in    = 65535;
+Http2FlowControlPolicy Http2::flow_control_policy_in    = 
Http2FlowControlPolicy::STATIC_SESSION_AND_STATIC_STREAM;
+uint32_t               Http2::max_frame_size            = 16384;
+uint32_t               Http2::header_table_size         = 4096;
+uint32_t               Http2::max_header_list_size      = 4294967295;
+
+uint32_t Http2::accept_no_activity_timeout   = 120;
+uint32_t Http2::no_activity_timeout_in       = 120;
+uint32_t Http2::active_timeout_in            = 0;
+uint32_t Http2::incomplete_header_timeout_in = 10;
+uint32_t Http2::push_diary_size              = 256;
+uint32_t Http2::zombie_timeout_in            = 0;
 
 uint32_t               Http2::max_concurrent_streams_out = 100;
 uint32_t               Http2::min_concurrent_streams_out = 10;
@@ -537,6 +539,7 @@ Http2::init()
   REC_EstablishStaticConfigInt32U(no_activity_timeout_in, 
"proxy.config.http2.no_activity_timeout_in");
   REC_EstablishStaticConfigInt32U(no_activity_timeout_out, 
"proxy.config.http2.no_activity_timeout_out");
   REC_EstablishStaticConfigInt32U(active_timeout_in, 
"proxy.config.http2.active_timeout_in");
+  REC_EstablishStaticConfigInt32U(incomplete_header_timeout_in, 
"proxy.config.http2.incomplete_header_timeout_in");
   REC_EstablishStaticConfigInt32U(push_diary_size, 
"proxy.config.http2.push_diary_size");
   REC_EstablishStaticConfigInt32U(zombie_timeout_in, 
"proxy.config.http2.zombie_debug_timeout_in");
   REC_EstablishStaticConfigFloat(stream_error_rate_threshold, 
"proxy.config.http2.stream_error_rate_threshold");
diff --git a/src/proxy/http2/Http2ConnectionState.cc 
b/src/proxy/http2/Http2ConnectionState.cc
index 4baaa52b52..ae739e9c3a 100644
--- a/src/proxy/http2/Http2ConnectionState.cc
+++ b/src/proxy/http2/Http2ConnectionState.cc
@@ -36,6 +36,7 @@
 #include "iocore/net/TLSSNISupport.h"
 
 #include "tscore/ink_assert.h"
+#include "tscore/ink_hrtime.h"
 #include "tscore/ink_memory.h"
 #include "tsutil/PostScript.h"
 #include "tsutil/LocalBuffer.h"
@@ -493,6 +494,7 @@ Http2ConnectionState::rcv_headers_frame(const Http2Frame 
&frame)
     if (!stream->is_outbound_connection() && 
!stream->trailing_header_is_possible()) {
       SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread());
       stream->mark_milestone(Http2StreamMilestone::START_TXN);
+      stream->cancel_active_timeout();
       stream->new_transaction(frame.is_from_early_data());
       // Send request header to SM
       stream->send_headers(*this);
@@ -1518,6 +1520,23 @@ Http2ConnectionState::main_event_handler(int event, void 
*edata)
     this->session->flush();
   } break;
 
+  case HTTP2_SESSION_EVENT_ERROR: {
+    REMEMBER(event, this->recursion);
+
+    Http2ErrorCode error_code = Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR;
+    if (edata != nullptr) {
+      Http2Error *error = static_cast<Http2Error *>(edata);
+      error_code        = error->code;
+    }
+
+    SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
+    this->send_goaway_frame(this->latest_streamid_in, error_code);
+    this->session->set_half_close_local_flag(true);
+    if (fini_event == nullptr) {
+      this->fini_event = 
this_ethread()->schedule_imm_local(static_cast<Continuation *>(this), 
HTTP2_SESSION_EVENT_FINI);
+    }
+  } break;
+
   // Initiate a graceful shutdown
   case HTTP2_SESSION_EVENT_SHUTDOWN_INIT: {
     REMEMBER(event, this->recursion);
@@ -1820,6 +1839,11 @@ Http2ConnectionState::create_stream(Http2StreamId 
new_id, Http2Error &error)
   }
   increment_stream_requests();
 
+  // Set incomplete header timeout
+  //   Client should send END_HEADERS flag within the 
http2.incomplete_header_timeout_in.
+  //   The active timeout of this stream will be reset by HttpSM with 
http.transction_active_timeout_in when a HTTP TXN is started
+  
new_stream->set_active_timeout(HRTIME_SECONDS(Http2::incomplete_header_timeout_in));
+
   // Clear the session timeout.  Let the transaction timeouts reign
   session->get_proxy_session()->cancel_inactivity_timeout();
   session->get_proxy_session()->set_session_active();
diff --git a/src/proxy/http2/Http2Stream.cc b/src/proxy/http2/Http2Stream.cc
index 2540b7b2b2..44d50e703b 100644
--- a/src/proxy/http2/Http2Stream.cc
+++ b/src/proxy/http2/Http2Stream.cc
@@ -28,6 +28,7 @@
 #include "proxy/http2/Http2ServerSession.h"
 #include "proxy/http/HttpDebugNames.h"
 #include "proxy/http/HttpSM.h"
+#include "tscore/Diags.h"
 #include "tscore/HTTPVersion.h"
 #include "tscore/ink_assert.h"
 
@@ -196,10 +197,36 @@ Http2Stream::main_event_handler(int event, void *edata)
   switch (event) {
   case VC_EVENT_ACTIVE_TIMEOUT:
   case VC_EVENT_INACTIVITY_TIMEOUT:
-    if (_sm && read_vio.ntodo() > 0) {
+    if (_sm == nullptr && closed != true) {
+      // TIMEOUT without HttpSM - assuming incomplete header timeout
+      Http2StreamDebug("timeout event=%d", event);
+
+      ip_port_text_buffer ipb;
+      const char         *remote_ip = 
ats_ip_ntop(this->_proxy_ssn->get_remote_addr(), ipb, sizeof(ipb));
+
+      Error("HTTP/2 stream error timeout remote_ip=%s session_id=%" PRId64 " 
stream_id=%u event=%d", remote_ip,
+            this->_proxy_ssn->connection_id(), this->_id, event);
+
+      // Close stream
+      do_io_close();
+      terminate_stream = true;
+
+      // Close connection because this stream doesn't read 
HEADERS/CONTINUATION frames anymore that makes HPACK dynamic table
+      // out of sync.
+      Http2ConnectionState &connection_state = get_connection_state();
+      {
+        SCOPED_MUTEX_LOCK(lock, connection_state.mutex, this_ethread());
+        Http2Error error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, 
Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR,
+                         "stream timeout");
+        connection_state.handleEvent(HTTP2_SESSION_EVENT_ERROR, &error);
+      }
+    } else if (_sm && read_vio.ntodo() > 0) {
       this->signal_read_event(event);
     } else if (_sm && write_vio.ntodo() > 0) {
       this->signal_write_event(event);
+    } else {
+      Warning("HTTP/2 unknown case of %d event - session_id=%" PRId64 " 
stream_id=%u", event, this->_proxy_ssn->connection_id(),
+              this->_id);
     }
     break;
   case VC_EVENT_WRITE_READY:
diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc
index d80af3aff1..4673cdebc6 100644
--- a/src/records/RecordsConfig.cc
+++ b/src/records/RecordsConfig.cc
@@ -1294,6 +1294,8 @@ static const RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.http2.active_timeout_in", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
+  {RECT_CONFIG, "proxy.config.http2.incomplete_header_timeout_in", RECD_INT, 
"10", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+  ,
   {RECT_CONFIG, "proxy.config.http2.push_diary_size", RECD_INT, "256", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.http2.zombie_debug_timeout_in", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}

Reply via email to