--- /dev/null
+package main
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ . "fd.io/hs-test/infra"
+)
+
+func init() {
+ RegisterH2Tests(Http2TcpGetTest)
+}
+
+func Http2TcpGetTest(s *H2Suite) {
+ vpp := s.Containers.Vpp.VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+ s.Log(vpp.Vppctl("show session verbose 2"))
+ args := fmt.Sprintf("--max-time 10 --noproxy '*' --http2-prior-knowledge http://%s:80/show/version", serverAddress)
+ writeOut, log := s.RunCurlContainer(s.Containers.Curl, args)
+ s.Log(vpp.Vppctl("show session verbose 2"))
+ s.AssertContains(log, "HTTP/2 200")
+ s.AssertContains(writeOut, "<html>", "<html> not found in the result!")
+ s.AssertContains(writeOut, "</html>", "</html> not found in the result!")
+
+ /* test session cleanup */
+ httpStreamCleanupDone := false
+ tcpSessionCleanupDone := false
+ for nTries := 0; nTries < 30; nTries++ {
+ o := vpp.Vppctl("show session verbose 2")
+ if !strings.Contains(o, "[T] "+serverAddress+":80->") {
+ tcpSessionCleanupDone = true
+ }
+ if !strings.Contains(o, "[H2]") {
+ httpStreamCleanupDone = true
+ }
+ if httpStreamCleanupDone && tcpSessionCleanupDone {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+ s.AssertEqual(true, tcpSessionCleanupDone, "TCP session not cleanup")
+ s.AssertEqual(true, httpStreamCleanupDone, "HTTP/2 stream not cleanup")
+
+ /* test server app stop listen */
+ vpp.Vppctl("http cli server listener del")
+ o := vpp.Vppctl("show session verbose proto http")
+ s.AssertNotContains(o, "LISTEN")
+}
--- /dev/null
+package hst
+
+import (
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+var h2Tests = map[string][]func(s *H2Suite){}
+
+type H2Suite struct {
+ HstSuite
+ Interfaces struct {
+ Tap *NetInterface
+ }
+ Containers struct {
+ Vpp *Container
+ Curl *Container
+ }
+}
+
+func RegisterH2Tests(tests ...func(s *H2Suite)) {
+ h2Tests[getTestFilename()] = tests
+}
+
+func (s *H2Suite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("tap")
+ s.LoadContainerTopology("single")
+ s.Interfaces.Tap = s.GetInterfaceByName("htaphost")
+ s.Containers.Vpp = s.GetContainerByName("vpp")
+ s.Containers.Curl = s.GetContainerByName("curl")
+}
+
+func (s *H2Suite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // Setup test conditions
+ var sessionConfig Stanza
+ sessionConfig.NewStanza("session").Append("enable").Append("use-app-socket-api")
+
+ vpp, _ := s.Containers.Vpp.newVppInstance(s.Containers.Vpp.AllocatedCpus, sessionConfig)
+
+ s.AssertNil(vpp.Start())
+ s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, 1, 1), "failed to create tap interface")
+
+ if *DryRun {
+ s.LogStartedContainers()
+ s.Skip("Dry run mode = true")
+ }
+}
+
+func (s *H2Suite) TearDownTest() {
+ s.HstSuite.TearDownTest()
+}
+
+func (s *H2Suite) VppAddr() string {
+ return s.Interfaces.Tap.Peer.Ip4AddressString()
+}
+
+var _ = PDescribe("Http2Suite", Ordered, ContinueOnFailure, func() {
+ var s H2Suite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range h2Tests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(TestTimeout))
+ }
+ }
+})
http_conn_t *lhc, *hc;
u32 hc_index, thresh;
http_conn_handle_t hc_handle;
+ transport_proto_t tp;
ts_listener = listen_session_get_from_handle (ts->listener_handle);
lhc = http_listener_get (ts_listener->opaque);
hc->state = HTTP_CONN_STATE_ESTABLISHED;
ts->session_state = SESSION_STATE_READY;
- /* TODO: TLS set by ALPN result, TCP: will decide in http_ts_rx_callback */
- hc->version = HTTP_VERSION_1;
+ tp = session_get_transport_proto (ts);
+ if (tp == TRANSPORT_PROTO_TLS)
+ {
+ /* TODO: set by ALPN result */
+ hc->version = HTTP_VERSION_1;
+ }
+ else
+ {
+ /* going to decide in http_ts_rx_callback */
+ hc->version = HTTP_VERSION_NA;
+ }
hc_handle.version = hc->version;
hc_handle.conn_index = hc_index;
ts->opaque = hc_handle.as_u32;
* the fifo is small (under 16K) we set the threshold to it's size, meaning
* a notification will be given when the fifo empties.
*/
- ts = session_get_from_handle (hc->hc_tc_session_handle);
thresh = clib_min (svm_fifo_size (ts->tx_fifo), HTTP_FIFO_THRESH);
svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
if (hc->state < HTTP_CONN_STATE_TRANSPORT_CLOSED)
hc->state = HTTP_CONN_STATE_TRANSPORT_CLOSED;
+ /* in case peer close cleartext connection before send something */
+ if (PREDICT_FALSE (hc->version == HTTP_VERSION_NA))
+ return;
+
http_vfts[hc->version].transport_close_callback (hc);
}
{
http_conn_t *hc;
http_conn_handle_t hc_handle;
+ u32 max_deq;
+ u8 *rx_buf;
hc_handle.as_u32 = ts->opaque;
return 0;
}
- /* TODO: if version is unknown */
+ if (hc_handle.version == HTTP_VERSION_NA)
+ {
+ HTTP_DBG (1, "unknown http version");
+ max_deq = svm_fifo_max_dequeue_cons (ts->rx_fifo);
+ if (max_deq >= http2_conn_preface.len)
+ {
+ rx_buf = http_get_rx_buf (hc);
+ svm_fifo_peek (ts->rx_fifo, 0, http2_conn_preface.len, rx_buf);
+ if (memcmp (rx_buf, http2_conn_preface.base,
+ http2_conn_preface.len) == 0)
+ {
+#if HTTP_2_ENABLE > 0
+ hc->version = HTTP_VERSION_2;
+ http_vfts[hc->version].conn_accept_callback (hc);
+#else
+ svm_fifo_dequeue_drop_all (ts->rx_fifo);
+ http_disconnect_transport (hc);
+ return 0;
+#endif
+ }
+ else
+ hc->version = HTTP_VERSION_1;
+ }
+ else
+ hc->version = HTTP_VERSION_1;
+
+ HTTP_DBG (1, "identified HTTP/%u",
+ hc->version == HTTP_VERSION_1 ? 1 : 2);
+ hc_handle.version = hc->version;
+ ts->opaque = hc_handle.as_u32;
+ }
http_vfts[hc_handle.version].transport_rx_callback (hc);
if (hc->state == HTTP_CONN_STATE_TRANSPORT_CLOSED)
u32 payload_len;
} http2_req_t;
+#define foreach_http2_conn_flags \
+ _ (EXPECT_PREFACE, "expect-preface") \
+ _ (PREFACE_VERIFIED, "preface-verified")
+
+typedef enum http2_conn_flags_bit_
+{
+#define _(sym, str) HTTP2_CONN_F_BIT_##sym,
+ foreach_http2_conn_flags
+#undef _
+} http2_conn_flags_bit_t;
+
+typedef enum http2_conn_flags_
+{
+#define _(sym, str) HTTP2_CONN_F_##sym = 1 << HTTP2_CONN_F_BIT_##sym,
+ foreach_http2_conn_flags
+#undef _
+} __clib_packed http2_conn_flags_t;
+
typedef struct http2_conn_ctx_
{
http2_conn_settings_t peer_settings;
}
}
+always_inline void
+http2_send_server_preface (http_conn_t *hc)
+{
+ u8 *response;
+ http2_main_t *h2m = &http2_main;
+ http2_settings_entry_t *setting, *settings_list = 0;
+
+#define _(v, label, member, min, max, default_value, err_code) \
+ if (h2m->settings.member != default_value) \
+ { \
+ vec_add2 (settings_list, setting, 1); \
+ setting->identifier = HTTP2_SETTINGS_##label; \
+ setting->value = h2m->settings.member; \
+ }
+ foreach_http2_settings
+#undef _
+
+ response = http_get_tx_buf (hc);
+ http2_frame_write_settings (settings_list, &response);
+ http_io_ts_write (hc, response, vec_len (response), 0);
+ http_io_ts_after_write (hc, 0, 0, 1);
+}
+
/*************************************/
/* request state machine handlers RX */
/*************************************/
return HTTP2_ERROR_NO_ERROR;
}
+static_always_inline int
+http2_expect_preface (http_conn_t *hc, http2_conn_ctx_t *h2c)
+{
+ u8 *rx_buf;
+
+ ASSERT (hc->flags & HTTP_CONN_F_IS_SERVER);
+ h2c->flags &= ~HTTP2_CONN_F_EXPECT_PREFACE;
+
+ /* already done in http core */
+ if (h2c->flags & HTTP2_CONN_F_PREFACE_VERIFIED)
+ return 0;
+
+ rx_buf = http_get_rx_buf (hc);
+ http_io_ts_read (hc, rx_buf, http2_conn_preface.len, 1);
+ return memcmp (rx_buf, http2_conn_preface.base, http2_conn_preface.len);
+}
+
/*****************/
/* http core VFT */
/*****************/
u32 to_deq;
u8 *rx_buf;
http2_error_t rv;
+ http2_conn_ctx_t *h2c;
HTTP_DBG (1, "hc [%u]%x", hc->c_thread_index, hc->hc_hc_index);
return;
}
+ h2c = http2_conn_ctx_get_w_thread (hc);
+ if (h2c->flags & HTTP2_CONN_F_EXPECT_PREFACE)
+ {
+ if (to_deq < http2_conn_preface.len)
+ {
+ HTTP_DBG (1, "to_deq %u is less than conn preface size", to_deq);
+ http_disconnect_transport (hc);
+ return;
+ }
+ if (http2_expect_preface (hc, h2c))
+ {
+ HTTP_DBG (1, "conn preface verification failed");
+ http_disconnect_transport (hc);
+ return;
+ }
+ http2_send_server_preface (hc);
+ http_io_ts_drain (hc, http2_conn_preface.len);
+ to_deq -= http2_conn_preface.len;
+ if (to_deq == 0)
+ return;
+ }
+
if (PREDICT_FALSE (to_deq < HTTP2_FRAME_HEADER_SIZE))
{
HTTP_DBG (1, "to_deq %u is less than frame header size", to_deq);
/* TODO */
}
+static void
+http2_conn_accept_callback (http_conn_t *hc)
+{
+ http2_conn_ctx_t *h2c;
+
+ HTTP_DBG (1, "hc [%u]%x", hc->c_thread_index, hc->hc_hc_index);
+ h2c = http2_conn_ctx_alloc_w_thread (hc);
+ h2c->flags |= HTTP2_CONN_F_EXPECT_PREFACE;
+ /* already done in http core */
+ if (http_get_transport_proto (hc) == TRANSPORT_PROTO_TCP)
+ h2c->flags |= HTTP2_CONN_F_PREFACE_VERIFIED;
+}
+
static void
http2_conn_cleanup_callback (http_conn_t *hc)
{
.transport_reset_callback = http2_transport_reset_callback,
.transport_conn_reschedule_callback =
http2_transport_conn_reschedule_callback,
+ .conn_accept_callback = http2_conn_accept_callback,
.conn_cleanup_callback = http2_conn_cleanup_callback,
.enable_callback = http2_enable_callback,
.unformat_cfg_callback = http2_unformat_config_callback,
clib_warning ("http/2 enabled");
h2m->settings = http2_default_conn_settings;
+ h2m->settings.max_concurrent_streams = 100; /* by default unlimited */
http_register_engine (&http2_engine, HTTP_VERSION_2);
return 0;
#define HTTP_FIFO_THRESH (16 << 10)
+static const http_token_t http2_conn_preface = { http_token_lit (
+ "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") };
+
typedef union
{
struct
void (*transport_close_callback) (http_conn_t *hc);
void (*transport_reset_callback) (http_conn_t *hc);
void (*transport_conn_reschedule_callback) (http_conn_t *hc);
+ void (*conn_accept_callback) (http_conn_t *hc); /* optional */
void (*conn_cleanup_callback) (http_conn_t *hc);
void (*enable_callback) (void); /* optional */
uword (*unformat_cfg_callback) (unformat_input_t *input); /* optional */