http: starting http/2 with prior knowledge 78/43478/2
authorMatus Fabian <[email protected]>
Fri, 25 Jul 2025 09:39:44 +0000 (05:39 -0400)
committerFlorin Coras <[email protected]>
Sat, 26 Jul 2025 03:13:59 +0000 (03:13 +0000)
Added flags member to transport_endpt_cfg_http_t where client app can
set HTTP_ENDPT_CFG_F_HTTP2_PRIOR_KNOWLEDGE when it want to use
HTTP/2 connection over cleartext TCP.

Type: improvement

Change-Id: Ib904a5cbdd34c6838d029a46c388e31a3329d399
Signed-off-by: Matus Fabian <[email protected]>
extras/hs-test/http2_test.go
extras/hs-test/resources/nginx/nginx_server.conf
src/plugins/hs_apps/http_client.c
src/plugins/http/http.c
src/plugins/http/http.h
src/plugins/http/http_private.h

index 26605f2..3cc0304 100644 (file)
@@ -13,7 +13,8 @@ import (
 
 func init() {
        RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest, Http2ServerMemLeakTest,
-               Http2ClientGetTest, Http2ClientPostTest, Http2ClientPostPtrTest, Http2ClientGetRepeatTest, Http2ClientMultiplexingTest)
+               Http2ClientGetTest, Http2ClientPostTest, Http2ClientPostPtrTest, Http2ClientGetRepeatTest, Http2ClientMultiplexingTest,
+               Http2ClientH2cTest)
        RegisterH2MWTests(Http2MultiplexingMWTest, Http2ClientMultiplexingMWTest)
        RegisterVethTests(Http2CliTlsTest, Http2ClientContinuationTest)
 }
@@ -191,6 +192,32 @@ func Http2ClientGetTest(s *Http2Suite) {
        s.Log(o)
        s.AssertContains(o, "HTTP/2 200 OK")
        s.AssertContains(o, "10000000 bytes saved to file")
+
+       logPath := s.Containers.NginxServer.GetHostWorkDir() + "/" + s.Containers.NginxServer.Name + "-access.log"
+       logContents, err := exechelper.Output("cat " + logPath)
+       s.AssertNil(err)
+       s.AssertContains(string(logContents), "HTTP/2")
+       s.AssertContains(string(logContents), "scheme=https conn=")
+}
+
+func Http2ClientH2cTest(s *Http2Suite) {
+       vpp := s.Containers.Vpp.VppInstance
+       serverAddress := s.HostAddr() + ":" + s.Ports.Port1
+
+       s.CreateNginxServer()
+       s.AssertNil(s.Containers.NginxServer.Start())
+
+       uri := "http://" + serverAddress + "/httpTestFile"
+       o := vpp.Vppctl("http client http2 save-to response.txt verbose uri " + uri)
+       s.Log(o)
+       s.AssertContains(o, "HTTP/2 200 OK")
+       s.AssertContains(o, "10000000 bytes saved to file")
+
+       logPath := s.Containers.NginxServer.GetHostWorkDir() + "/" + s.Containers.NginxServer.Name + "-access.log"
+       logContents, err := exechelper.Output("cat " + logPath)
+       s.AssertNil(err)
+       s.AssertContains(string(logContents), "HTTP/2")
+       s.AssertContains(string(logContents), "scheme=http conn=")
 }
 
 func http2ClientPostFile(s *Http2Suite, usePtr bool, fileSize int) {
index d161e3c..f5e4f9e 100644 (file)
@@ -15,7 +15,8 @@ events {
 http {
   log_format access_log_fmt '$remote_addr - $remote_user [$time_local] '
                             '"$request" $status $body_bytes_sent '
-                            '"$http_referer" "$http_user_agent" conn=$connection conn_reqs=$connection_requests';
+                            '"$http_referer" "$http_user_agent" '
+                            'scheme=$scheme conn=$connection conn_reqs=$connection_requests';
   keepalive_timeout 300s;
   keepalive_requests 1000000;
   client_body_timeout {{.Timeout}}s;
index 40eb1d8..8a047fc 100644 (file)
@@ -839,17 +839,13 @@ hc_connect ()
   hc_main_t *hcm = &hc_main;
   vnet_connect_args_t *a = 0;
   transport_endpt_ext_cfg_t *ext_cfg;
-  transport_endpt_cfg_http_t http_cfg = { (u32) hcm->timeout, 0 };
+  transport_endpt_cfg_http_t http_cfg = { (u32) hcm->timeout, 0, 0 };
 
   vec_validate (a, 0);
   clib_memset (a, 0, sizeof (a[0]));
   clib_memcpy (&a->sep_ext, &hcm->connect_sep, sizeof (hcm->connect_sep));
   a->app_index = hcm->app_index;
 
-  ext_cfg = session_endpoint_add_ext_cfg (
-    &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_HTTP, sizeof (http_cfg));
-  clib_memcpy (ext_cfg->data, &http_cfg, sizeof (http_cfg));
-
   if (hcm->connect_sep.flags & SESSION_ENDPT_CFG_F_SECURE)
     {
       ext_cfg = session_endpoint_add_ext_cfg (
@@ -868,6 +864,15 @@ hc_connect ()
          break;
        }
     }
+  else
+    {
+      if (hcm->http_version == HTTP_VERSION_2)
+       http_cfg.flags |= HTTP_ENDPT_CFG_F_HTTP2_PRIOR_KNOWLEDGE;
+    }
+
+  ext_cfg = session_endpoint_add_ext_cfg (
+    &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_HTTP, sizeof (http_cfg));
+  clib_memcpy (ext_cfg->data, &http_cfg, sizeof (http_cfg));
 
   session_send_rpc_evt_to_thread_force (transport_cl_thread (), hc_connect_rpc,
                                        a);
index fc1fc81..b387b61 100644 (file)
@@ -163,7 +163,7 @@ http_add_postponed_ho_cleanups (u32 ho_hc_index)
   vec_add1 (hm->postponed_ho_free, ho_hc_index);
 }
 
-static inline http_conn_t *
+http_conn_t *
 http_ho_conn_get (u32 ho_hc_index)
 {
   http_main_t *hm = &http_main;
@@ -535,9 +535,6 @@ http_ts_connected_callback (u32 http_app_index, u32 ho_hc_index, session_t *ts,
 
   clib_memcpy_fast (hc, ho_hc, sizeof (*hc));
 
-  /* in chain with TLS there is race on half-open cleanup */
-  __atomic_fetch_or (&ho_hc->flags, HTTP_CONN_F_HO_DONE, __ATOMIC_RELEASE);
-
   hc->timer_handle = HTTP_TIMER_HANDLE_INVALID;
   hc->c_thread_index = ts->thread_index;
   hc->hc_tc_session_handle = session_handle (ts);
@@ -546,6 +543,7 @@ http_ts_connected_callback (u32 http_app_index, u32 ho_hc_index, session_t *ts,
   hc->state = HTTP_CONN_STATE_ESTABLISHED;
   ts->session_state = SESSION_STATE_READY;
   hc->flags |= HTTP_CONN_F_NO_APP_SESSION;
+  hc->ho_index = ho_hc_index;
   tp = session_get_transport_proto (ts);
   /* TLS set by ALPN result, TCP: prior knowledge (set in ho) */
   if (tp == TRANSPORT_PROTO_TLS)
@@ -556,7 +554,6 @@ http_ts_connected_callback (u32 http_app_index, u32 ho_hc_index, session_t *ts,
        {
        case TLS_ALPN_PROTO_HTTP_2:
          hc->version = HTTP_VERSION_2;
-         http_vfts[hc->version].conn_accept_callback (hc);
          break;
        case TLS_ALPN_PROTO_HTTP_1_1:
        case TLS_ALPN_PROTO_NONE:
@@ -579,6 +576,7 @@ http_ts_connected_callback (u32 http_app_index, u32 ho_hc_index, session_t *ts,
   if ((rv = http_vfts[hc->version].transport_connected_callback (hc)))
     {
       clib_warning ("transport_connected_callback failed, rv=%d", rv);
+      __atomic_fetch_or (&ho_hc->flags, HTTP_CONN_F_HO_DONE, __ATOMIC_RELEASE);
       return rv;
     }
 
@@ -898,6 +896,11 @@ http_connect_connection (session_endpoint_cfg_t *sep)
        (transport_endpt_cfg_http_t *) ext_cfg->data;
       HTTP_DBG (1, "app set timeout %u", http_cfg->timeout);
       hc->timeout = http_cfg->timeout;
+      if (http_cfg->flags & HTTP_ENDPT_CFG_F_HTTP2_PRIOR_KNOWLEDGE)
+       {
+         HTTP_DBG (1, "app want http2 with prior knowledge");
+         hc->version = HTTP_VERSION_2;
+       }
     }
 
   ext_cfg = session_endpoint_get_ext_cfg (sep, TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
index 61c387b..c106038 100644 (file)
@@ -46,10 +46,28 @@ typedef enum http_udp_tunnel_mode_
   HTTP_UDP_TUNNEL_DGRAM,   /**< convert capsule to datagram (zc proxy) */
 } http_udp_tunnel_mode_t;
 
+#define foreach_http_endpt_cfg_flags                                          \
+  _ (HTTP2_PRIOR_KNOWLEDGE) /**< HTTP/2 connections over cleartext TCP */
+
+typedef enum http_endpt_cfg_flags_bit_
+{
+#define _(sym) HTTP_ENDPT_CFG_F_BIT_##sym,
+  foreach_http_endpt_cfg_flags
+#undef _
+} http_endpt_cfg_flags_bit_t;
+
+typedef enum http_endpt_cfg_flags_
+{
+#define _(sym) HTTP_ENDPT_CFG_F_##sym = 1 << HTTP_ENDPT_CFG_F_BIT_##sym,
+  foreach_http_endpt_cfg_flags
+#undef _
+} __clib_packed http_endpt_cfg_flags_t;
+
 typedef struct transport_endpt_cfg_http
 {
   u32 timeout; /**< HTTP session timeout in seconds */
   http_udp_tunnel_mode_t udp_tunnel_mode; /**< connect-udp mode */
+  u8 flags;
 } transport_endpt_cfg_http_t;
 
 typedef struct
index f6666af..b76f4f6 100644 (file)
@@ -201,6 +201,7 @@ typedef struct http_tc_
   u32 timer_handle;
   u32 timeout;
   u32 app_rx_fifo_size;
+  u32 ho_index;
   u8 *app_name;
   u8 *host;
   http_conn_flags_t flags;
@@ -325,6 +326,8 @@ u8 *format_http_time_now (u8 *s, va_list *args);
 http_conn_t *http_conn_get_w_thread (u32 hc_index,
                                     clib_thread_index_t thread_index);
 
+http_conn_t *http_ho_conn_get (u32 ho_hc_index);
+
 /**
  * @brief Find the first occurrence of the string in the vector.
  *
@@ -884,8 +887,13 @@ http_conn_established (http_conn_t *hc, http_req_t *req,
   session_t *as;
   app_worker_t *app_wrk;
   session_t *ts;
+  http_conn_t *ho_hc;
   int rv;
 
+  ho_hc = http_ho_conn_get (hc->ho_index);
+  /* in chain with TLS there is race on half-open cleanup */
+  __atomic_fetch_or (&ho_hc->flags, HTTP_CONN_F_HO_DONE, __ATOMIC_RELEASE);
+
   /* allocate app session and initialize */
   as = session_alloc (hc->c_thread_index);
   HTTP_DBG (1, "allocated session 0x%lx", session_handle (as));